Linux网络编程之TCP文件传输 您所在的位置:网站首页 socket传输文件目录 Linux网络编程之TCP文件传输

Linux网络编程之TCP文件传输

2024-05-29 14:03| 来源: 网络整理| 查看: 265

1. 要求

在Linux环境下,编程实现文件的上传和下载,即客户端可以发送文件给服务器,服务器将文件写到服务器端文件系统中;客户端请求下载文件时服务器读取文件内容,发送给客户端,客户端接收内容并写入本地文件。要求

(1)源代码格式化良好并适当注释;

(2)除上述核心功能外,尽量完善程序,比如使其使用方便等;

(3)提交报告,报告中包括程序源代码和测试效果截图。

2. 基本设计思路 2.1 服务器和客户端的socket连接

服务器通过对socket进行监听listen(),等待客户端的主动连接connect()。

服务器在建立连接后通过pork()函数赋值进程,使子进程关闭服务器的socket监听并向客户端提供服务;父进程则继续监听socket,继续与其他客户端进行连接。

为什么客户端connect前不需要进行bind?

操作系统会自动选择一个可用的本地 IP 地址和端口号来作为客户端套接字的地址,并将这个地址作为连接请求的源地址发送给服务器。这个过程称为“自动绑定”

2.2 客户端的服务请求

在建立了与服务端的socket连接后,客户端通过向服务端发送相关的指令来请求相对应的服务。

通过输入指令发送到服务端,使服务端解析指令,并向客户端返回相对应的数据包。客户端接收并解析数据包来获取相关信息。

2.3 服务端的服务提供

服务端在接收到客户端发来的指令后,根据指令的不同,执行不同的操作。

若接收到客户端下载文件的指令,则将相对应的文件封装成数据包,发送到客户端。

若接收到客户端上传文件的指令,则调用recv()函数来接收由客户端发送过来的数据包,并解析内容写入文本中。

3. 源代码 3.1 server.cpp #include #include #include #include #include #include #include #include #include #include #include #include #define port 8888 #define backlog 5 #define MAXBUFF 4096 #define MAXNAMELEN 100 void process_con_server(int s); void analyzeCommand(char *command, int s); void sendMessage(int s); char** splitString(char* str, int& num); void sendFile(int s, char *fname); void recvFile(int s, char *fname); /*文件信息*/ typedef struct FileMess{ unsigned long fileLen; char fileName[100]; } FileMess; /*数据包*/ typedef struct DataPack{ char type; //'D'表示数据,'M'表示文件信息, 'E'表示错误数据包 int packSize; //整个数据包的大小 char content[MAXBUFF]; //数据包携带数据缓冲区 int contentLen; //数据包中数据的长度 unsigned long position; //数据在文件中的字节位置 FileMess fileMess; //文件信息 } DataPack; /*目录信息数据包*/ typedef struct DirPack{ int flag; //标志位,1表示包含目录文件名,0表示结束目录发送的空数据包 char content[100]; } DirPack; int main(int argc, char *argv[]){ int ss, sc; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int err; pid_t pid; /*创建套接字socket*/ ss = socket(AF_INET, SOCK_STREAM, 0); if(ss printf("Bind server address error!!!\n"); return -1; } /*启动监听*/ err = listen(ss, backlog); //设置最大排队数量为backlog if(err socklen_t addrlen = sizeof(struct sockaddr); /*接收来自客户端client的套接字socket*/ sc = accept(ss, (struct sockaddr*)&client_addr, &addrlen); if(sc //父进程的pid永远不为0,未初始化时为随机值 close(ss); //在子进程中关闭服务器监听 process_con_server(sc); //向客户端提供服务 } else{ close(sc); } } return 0; } /*服务器处理与客户端的连接服务*/ void process_con_server(int s){ ssize_t size; char buffer[1024]; //使用1024bytes的缓冲区来接收用户指令 while(1){ memset(buffer, 0, sizeof(buffer)); size = recv(s, buffer, sizeof(buffer), 0); //会读取回车\n if(size == 0) return; write(STDOUT_FILENO, buffer, size); analyzeCommand(buffer, s); //解析用户指令 } close(s); } /*解析客户端指令*/ void analyzeCommand(char *command, int s){ int substrNum; char** substr = splitString(command, substrNum); /*显示可下载的文件列表, 将列表通过socket传输到客户端*/ if(!strcmp(substr[0], "ls\n")){ sendMessage(s); } /*下载指定文件*/ else if(!strcmp(substr[0], "download")){ sendFile(s, substr[1]); } /*接收来自客户端上传的文件*/ else if(!strcmp(substr[0], "send")){ recvFile(s, substr[1]); } else{ char *mes = "Please write down the right command!!!\n"; write(s, mes, strlen(mes)); } } /*以一个或多个空格分割字符串*/ /*返回值:指针数组*/ /*一般输入的指令是 指令+文件名的形式 例如"send test.txt"*/ char** splitString(char* str, int& num) { int len = strlen(str); char** result = new char*[len]; int count = 0; int start = 0; /*遍历分割字符串*/ for (int i = 0; i if (i - start > 0) { int size = i - start; result[count] = new char[size + 1]; //size+1是为了尾部添加'\0'作为截至 strncpy(result[count], &str[start], size); result[count][size] = '\0'; ++count; } start = i + 1; } } /*若字符串不以空格结尾,则需要将字符串尾部添加*/ if (len - start > 0) { int size = len - start; result[count] = new char[size + 1]; strncpy(result[count], &str[start], size); result[count][size] = '\0'; ++count; } num = count; return result; } /*服务器向客户端发送文件,即客户端下载文件*/ void sendFile(int s, char *fname){ //fname含有'\n'回车 DataPack dataPack; char path[100] = "./resources/"; //去除字符串fname末尾的回车 char *fName = (char*)malloc(strlen(fname)-1); strncpy(fName, fname, strlen(fname)-1); //拼接文件名与资源路径得到文件路径 strncat(path, fName, strlen(fname)-1); //判断用户获取的资源文件是否存在 int st = access(path, F_OK); if(-1 == st){ /*设置错误信息,并以错误信息类型发送数据包*/ dataPack.type = 'E'; dataPack.packSize = sizeof(DataPack); char *buffer = "The file isn't exist!!!\n"; strncpy(dataPack.content, buffer, strlen(buffer)); dataPack.contentLen = strlen(buffer); dataPack.position = 0; send(s, &dataPack, dataPack.packSize, 0); return; } /*获取并发送文件信息*/ struct stat statbuf; stat(path, &statbuf); dataPack.type = 'M'; dataPack.packSize = sizeof(DataPack); dataPack.fileMess.fileLen = statbuf.st_size; strncpy(dataPack.fileMess.fileName, fName, strlen(fName)); unsigned long sRe = send(s, &dataPack, dataPack.packSize, 0); printf("成功发送文件信息数据包!\n"); /*发送文件内容*/ unsigned long sendedCount = 0; //记录已发送的数据大小 int fd = open(path, O_RDONLY); //打开文件 while(sendedCount sendedCount += filedata.contentLen; } printf("成功发送数据:%ld bytes\n", sendedCount); } //构造结束标志数据包,标志文件传输完毕 memset(&dataPack, 0, sizeof(DataPack)); dataPack.type = 'E'; send(s, &dataPack, sizeof(DataPack), 0); close(fd); return; } /*服务器接收文件,即客户端上传文件*/ void recvFile(int s, char *fname){ unsigned long fileSize = 0; //记录接收文件的大小 unsigned long recvedCount = 0; //记录已接收的数据量大小 int fd = 0; DataPack *dataPack = (DataPack *)malloc(sizeof(DataPack)); while(1){ memset(dataPack, 0, sizeof(DataPack)); unsigned long recvBytes = recv(s, dataPack, sizeof(DataPack), 0); //'E'类型的数据包为错误数据包 //在文件传输完成后,会发送一个空内容(content)的错误数据包,表示文件传输完成 //也可以选择重新定义一个新的类型的数据包作为结束数据包类型 if(dataPack->type == 'E'){ //该数据包为错误数据包 write(STDOUT_FILENO, dataPack->content, dataPack->contentLen); break; } //'M'类型的数据包为文件信息数据包,根据相应的信息创建文件 else if(dataPack->type == 'M'){ char path[200] = "./upload/"; strcat(path, dataPack->fileMess.fileName); //无论什么时候都创新创建文件 fd = open(path, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU); fileSize = dataPack->fileMess.fileLen; } //'D'类型数据包为文件内容数据包,解析数据包内容,并写入相应文件的相应位置 else if(dataPack->type = 'D'){ lseek(fd, recvedCount, SEEK_SET); write(fd, dataPack->content, dataPack->contentLen); recvedCount += dataPack->contentLen; } } if (fd != 0){ close(fd); //关闭文件描述符 } } void sendMessage(int s){ DIR *dirp; struct dirent *direntp; int count = 0; DirPack *dirpack = (DirPack *)malloc(sizeof(DirPack)); memset(dirpack, 0, sizeof(DirPack)); dirpack->flag = 1; // 打开当前目录 dirp = opendir("./resources/"); // 读取目录内容 while ((direntp = readdir(dirp)) != NULL) { // 忽略当前目录和父目录 if (strcmp(direntp->d_name, ".") == 0 || strcmp(direntp->d_name, "..") == 0) { continue; } // 将文件名发送到客户端中 char *dirfName = strdup(direntp->d_name); //获取文件名 strncpy(dirpack->content, dirfName, strlen(dirfName)); send(s, dirpack, sizeof(DirPack), 0); // 清空数据包的内容 memset(dirpack->content, 0, sizeof(dirpack->content)); } //构造结束数据包 dirpack->flag = 0; send(s, dirpack, sizeof(DirPack), 0); // 关闭目录 closedir(dirp); } 3.2 client.cpp #include #include #include #include #include #include #include #include #include #define port 8888 #define MAXBUFF 4096 void process_con_client(int s); //处理与服务器的连接服务 char** splitString(char* str, int& num); //以一个或多个空格分割字符串 void analyzeCommand(char *command, int s); //解析用户输入的命令 void recvFile(int s, char *fname); //接收来自服务器的文件数据包 void sendFile(int s, char *fname); //向服务器上传文件 void recvMessage(int s); //接收可下载文件目录信息 /*文件信息*/ typedef struct FileMess{ unsigned long fileLen; char fileName[100]; } FileMess; /*文件数据包*/ typedef struct DataPack{ char type; //'D'表示数据,'M'表示文件信息, 'E'表示错误数据包 int packSize; //整个数据包的大小 char content[MAXBUFF]; //数据包携带数据缓冲区 int contentLen; //数据包中数据的长度 unsigned long position; //数据在文件中的字节位置 FileMess fileMess; //文件信息 } DataPack; /*目录信息数据包*/ typedef struct DirPack{ int flag; //标志位,1表示包含目录文件名,0表示结束目录发送的空数据包 char content[100]; } DirPack; int main(int argc, char *argv[]){ int s; struct sockaddr_in server_addr; int err; /*创建套接字socket*/ s = socket(AF_INET, SOCK_STREAM, 0); if(s printf("Connect error!!!\n"); return -1; } process_con_client(s); //请求服务 close(s); //关闭连接 return 0; } /*处理客户端与服务器的连接*/ void process_con_client(int s){ ssize_t size = 0; char buffer[1024]; //反复处理用户输入的命令 while(1){ memset(buffer, 0, sizeof(buffer)); size = read(STDIN_FILENO, buffer, 1024); if(size > 0){ write(s, buffer, size); analyzeCommand(buffer, s); } } close(s); } /*以一个或多个空格分割字符串*/ /*返回值:指针数组*/ /*num:传出参数,字符串分割成的子串个数*/ char** splitString(char* str, int& num) { int len = strlen(str); char** result = new char*[len]; int count = 0; int start = 0; /*遍历分割字符串*/ for (int i = 0; i if (i - start > 0) { int size = i - start; result[count] = new char[size + 1]; //size+1是为了尾部添加'\0'作为截至 strncpy(result[count], &str[start], size); result[count][size] = '\0'; ++count; } start = i + 1; } } /*若字符串不以空格结尾,则需要将字符串尾部添加*/ if (len - start > 0) { int size = len - start; result[count] = new char[size + 1]; strncpy(result[count], &str[start], size); result[count][size] = '\0'; ++count; } num = count; return result; } /*解析客户端用户输入的指令*/ void analyzeCommand(char *command, int s){ int substrNum; char** substr = splitString(command, substrNum); /*判断用户输入的指令是否符合格式*/ /*此处仅提供三种指令,ls、download [filename]、send [filename]*/ if(substrNum > 2){ char *buffer = "The format of command is error!!!\n"; write(STDOUT_FILENO, buffer, strlen(buffer)); } /*显示可下载的文件列表, 接收来自服务器传输过来的信息*/ if(!strcmp(substr[0], "ls\n")){ recvMessage(s); } /*下载指定文件*/ if(!strcmp(substr[0], "download")){ recvFile(s, substr[1]); } /*上传指定的文件到服务器*/ if(!strcmp(substr[0], "send")){ sendFile(s, substr[1]); } } /*执行文件下载命令,接收服务器发来的数据包*/ void recvFile(int s, char *fname){ unsigned long fileSize = 0; //记录接收文件的大小 unsigned long recvedCount = 0; //记录已接收的数据量大小 int fd = 0; DataPack *dataPack = (DataPack *)malloc(sizeof(DataPack)); //动态分配内存 while(1){ memset(dataPack, 0, sizeof(DataPack)); unsigned long recvBytes = recv(s, dataPack, sizeof(DataPack), 0); //'E'类型的数据包为错误数据包 //在文件传输完成后,会发送一个空内容(content)的错误数据包,表示文件传输完成 //也可以选择重新定义一个新的类型的数据包作为结束数据包类型 if(dataPack->type == 'E'){ write(STDOUT_FILENO, dataPack->content, dataPack->contentLen); break; } //'M'类型的数据包为文件信息数据包,根据相应的信息创建文件 else if(dataPack->type == 'M'){ char path[200] = "./download/"; char *fName = (char*)malloc(strlen(fname)-1); strncpy(fName, fname, strlen(fname)-1); strncat(path, fName, strlen(fname)-1); //无论什么时候都创新创建文件 fd = open(path, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU); fileSize = dataPack->fileMess.fileLen; } //'D'类型数据包为文件内容数据包,解析数据包内容,并写入相应文件的相应位置 else if(dataPack->type = 'D'){ lseek(fd, recvedCount, SEEK_SET); write(fd, dataPack->content, dataPack->contentLen); recvedCount += dataPack->contentLen; } } if (fd != 0){ close(fd); //关闭文件描述符 } } /*接收服务器发来的可下载文件目录数据包*/ void recvMessage(int s){ DirPack *dirpack = (DirPack *)malloc(sizeof(DirPack)); //动态分配内存 memset(dirpack, 0, sizeof(DirPack)); int count = 0; while(1){ recv(s, dirpack, sizeof(DirPack), 0); ++count; //接收完毕目录后会发现一个flag标志为0的数据包,表示目录数据包发送完毕 if(dirpack->flag == 0){ memset(dirpack, 0, sizeof(DirPack)); break; } write(STDOUT_FILENO, dirpack->content, sizeof(dirpack->content)); //排布显示 if(count%4 == 0){ write(STDOUT_FILENO, "\n", 1); } else { write(STDOUT_FILENO, "\t", 1); } memset(dirpack, 0, sizeof(DirPack)); } free(dirpack); } /*执行上传文件命令,向服务器发送文件信息和内容数据包*/ void sendFile(int s, char *fname){ //fname含有'\n'回车 DataPack dataPack; //去除字符串fname末尾的回车 char *fName = (char*)malloc(strlen(fname)-1); strncpy(fName, fname, strlen(fname)-1); //判断是否存在相应名称的文件 int st = access(fName, F_OK); if(-1 == st){ /*设置错误信息,并以错误信息类型发送数据包*/ dataPack.type = 'E'; dataPack.packSize = sizeof(DataPack); char *buffer = "The file isn't exist!!!\n"; strncpy(dataPack.content, buffer, strlen(buffer)); dataPack.contentLen = strlen(buffer); dataPack.position = 0; send(s, &dataPack, dataPack.packSize, 0); return; } /*获取并发送文件信息*/ struct stat statbuf; stat(fName, &statbuf); dataPack.type = 'M'; dataPack.packSize = sizeof(DataPack); dataPack.fileMess.fileLen = statbuf.st_size; strncpy(dataPack.fileMess.fileName, fName, strlen(fName)); unsigned long sRe = send(s, &dataPack, dataPack.packSize, 0); if(sRe > 0){ printf("成功发送文件信息数据包!\n"); } /*发送文件内容*/ unsigned long sendedCount = 0; //记录已发送的数据大小 int fd = open(fName, O_RDONLY); //打开文件 //当发送的数据量小于文件大小时则继续发送数据 while(sendedCount sendedCount += filedata.contentLen; printf("成功发送数据:%ld bytes\n", sendedCount); } } /*发送传输结束数据包*/ memset(&dataPack, 0, sizeof(DataPack)); dataPack.type = 'E'; send(s, &dataPack, sizeof(DataPack), 0); close(fd); return; } 4. 结果测试 4.1 g++编译源代码 server.cpp g++ server.cpp -o server

image-20230307205135652

client.cpp g++ client.cpp -o client

image-20230307205623115

4.2 相应文件内容 客户端

image-20230307212414638

服务端

image-20230307212450792

4.3 运行效果 服务端网络配置

image-20230307205931447

client

image-20230307210343897

server

image-20230307210400687

运行流程:

服务端运行server程序,启动监听;客户端启动client程序,主动与服务端建立连接;

客户端向服务端发送‘ls’指令,服务端收到‘ls’指令后,打印指令,并向客户端提供当前目录下的resources文件夹中所包含的文件目录;

客户端收到文件目录后,打印文件目录;

客户端向服务端发送‘download test3.txt’指令,服务端收到‘download test3.txt’指令后,打印指令,并打开resource文件夹中中test3.txt文件,读取内容与文件信息,打包成数据包发送到客户端。

客户端收到数据包后,解析数据包内容,并在download文件夹中创建相应类型的文件,将数据包的内容写入文件中。

客户端向服务端发送‘download test2.txt’指令,服务端收到‘download test2.txt’指令后,打印指令,并打开resource文件夹中中test2.txt文件,读取文件信息,打包成数据包发送到客户端;读取内容,发现内容为空,故不发送文件内容数据包。

客户端收到文件信息数据包,解析数据包内容,并在download文件夹中创建相应类型的文件。由于没有文件内容数据包,此时边不执行文件内容写入操作;

客户端向服务端发送‘send sdtos.txt’指令,服务端收到‘send sdtos.txt’指令后,打印指令。

客户端读取当前目录下的sdtos.txt文件的文件信息和文件内容,将其封装成数据包,发送到服务端;

在收到客户端发送过来的数据包后,在当前目录的upload文件夹中创建相应的文件,并向文件中写入相应的文件内容。

客户端

image-20230307212145671

服务端

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有