第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Linux下C语言实现TCP文件传输

Linux下C语言实现TCP文件传输

时间:2023-06-17 00:00:37

相关推荐

Linux下C语言实现TCP文件传输

开发环境:

Linux,GCC

功能介绍:

客户端和服务端的TCP文件传输,客户端可以上传文件到服务端,也可以从服务端下载文件,还可以查看和修改服务端的工作目录(临时),查看客户端当前目录。

代码下载:

码云:传送门 GitHub:传送门

效果图参考页尾,那么话不多说,直接上码(只有部分,完整请访问码云)

服务端:server.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <dirent.h>#include <fcntl.h>#include <pthread.h>#include "tools.h"#define pf printfchar cmd[20] = {};// 开始运行void *start_run(void *arg);// 客户端上传void c_up(int *clifd);// 客户端下载void c_down(int *clifd);// 返回文件列表void c_list(int *clifd);typedef struct LS{char mode[15];// 文件的模式int dir_num;// 是否目录或目录中包含目录的数量char user[20];// 文件的用户名char group[20]; // 文件的组名long size;// 文件的字节数char time[30];// 文件的最后修改时间int st_mode;// 文件类型和权限char name[20];// 文件名} LS;// 主函数int main(){pf("[%s] 服务器创建socket...\n", get_time(2));int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (0 > sockfd){perror("socket");return -1;}pf("[%s] 准备地址...\n", get_time(2));struct sockaddr_in addr = {};addr.sin_family = AF_INET;// 端口 IP 自行修改addr.sin_port = htons(60000);addr.sin_addr.s_addr = inet_addr("127.0.0.1");socklen_t len = sizeof(addr);pf("[%s] 绑定socket与地址...\n", get_time(2));if (bind(sockfd, (struct sockaddr *)&addr, len)){perror("bind");return -1;}pf("[%s] 设置监听...\n", get_time(2));if (listen(sockfd, 5)){perror("listen");return -1;}pf("[%s] 等待客户端连接...\n", get_time(2));for (;;){struct sockaddr_in addrcli = {};// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);int *clifd = (int*)malloc(sizeof(int));*clifd = accept(sockfd, (struct sockaddr *)&addrcli, &len);if (0 > *clifd){perror("accept");continue;}pthread_t pid;// 创建线程函数 int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);//第一个参数为指向线程标识符的指针。//第二个参数用来设置线程属性。//第三个参数是线程运行函数的地址。//最后一个参数是运行函数的参数。pthread_create(&pid, NULL, start_run, (void *)clifd);}return 0;}// 开始运行void *start_run(void *arg){int *clifd = (int *)arg;char up[20] = "我想上你";char down[20] = "我想下你";char see[20] = "我想看你";char quit[20] = "我要走了";int c_size = 0;for (;;){c_size = read(*clifd, cmd, sizeof(cmd));if(-1 == c_size){pf("[%s] read函数出错!\n", get_time(2));}if (strcmp(up, cmd) == 0){pf("[%s] 收到客户端的上传指令\n", get_time(2));c_up(clifd);memset(cmd, 0, 20);}else if (strcmp(down, cmd) == 0){pf("[%s] 收到客户端的下载指令\n", get_time(2));c_down(clifd);memset(cmd, 0, 20);}else if (strcmp(see, cmd) == 0){pf("[%s] 收到客户端的目录指令\n", get_time(2));c_list(clifd);memset(cmd, 0, 20);}else if (strcmp(quit, cmd) == 0){pf("[%s] 收到服务端的退出指令\n", get_time(2));pthread_exit(0);return (void *)NULL;}}//char *str = "我死了";//pthread_exit(str);}// 上传void c_up(int *clifd){int flag = 0;int r_size = 0;int w_size = 0;char buf[1024] = {};w_size = write(*clifd, "success", 8);read(*clifd, buf, 10);if(strncmp(buf, "error", 10) == 0){printf("[%s] 收到客户端返回error,接收终止\n", get_time(2));return;}else if(strncmp(buf, "success", 10) == 0){printf("[%s] 收到客户端返回success,继续接收\n", get_time(2));}else{printf("[%s] 收到客户端异常数据:%s,接收终止\n", get_time(2), buf);return;}// 用于存储文件名,长度50,不够则需要自行加长char filename[50] = {};memset(filename, 0, sizeof(filename));// 发送success给客户端,告知客户端可以传输文件名w_size = write(*clifd, "success", 8);int f_size = read(*clifd, filename, sizeof(filename));if(-1 == f_size){pf("[%s] read函数出错!\n", get_time(2));}pf("[%s] 收到文件名:%s\n", get_time(2), filename);usleep(1000);// 传回文件名,用于数据可靠性校验w_size = write(*clifd, filename, strlen(filename) + 1);// 读取客户端返回的结果r_size = read(*clifd, buf, sizeof(buf));if(strncmp(buf, "success", 7) == 0){pf("[%s] 文件名校验成功,准备接收文件\n", get_time(2));}else{pf("[%s] 文件名校验失败,终止接收文件\n", get_time(2));return;}// 发送success给客户端,告知可以开始文件传输w_size = write(*clifd, "success", 8);pf("[%s] 发送success给客户端,可以开始文件传输\n", get_time(2));int fd = open(filename, O_CREAT | O_RDWR, 0777);do{memset(buf, 0, sizeof(buf));r_size = read(*clifd, buf, sizeof(buf));pf("[收到字节数:%d ", r_size);w_size = write(fd, buf, r_size);pf("写入文件字节数:%d ", w_size);usleep(10000);w_size = write(*clifd, "success", 8);pf("发送success给客户端 ]\n");flag++;} while (r_size == 1024);sleep(1);if (flag > 0){char result[20] = "success";pf("[%s]文件传输完毕 返回客户端success\n\n", get_time(2));write(*clifd, result, strlen(result) + 1);}else{char result[20] = "error";pf("[%s]文件传输失败 返回客户端error\n\n", get_time(2));write(*clifd, result, strlen(result) + 1);}close(fd);return;}// 下载void c_down(int *clifd){DIR *dir;dir = opendir(".");char list[1024] = {};struct dirent *dirent;int r_size = 0;int w_size = 0;char buf[1024] = {};char buf2[20] = {};char filename[50] = {};char filename2[51] = {};usleep(10000);w_size = write(*clifd, "success", 8);r_size = read(*clifd, buf, sizeof(buf));if(strncmp(buf, "success", 8) == 0){pf("[%s] 收到客户端success信息,可以进行目录列表发送\n", get_time(2));}// 获取目录下所有文件名while ((dirent = readdir(dir)) != NULL){strcat(list, dirent->d_name);strcat(list, " ");}pf("当前目录列表:%s\n", list);pf("strlen(list):%d\n", (int)strlen(list));int l_size = write(*clifd, list, strlen(list)+1);if(-1 == l_size){pf("[%s] read函数出错!\n", get_time(2));}pf("[%s] 发送当前下载目录列表给客户端\n", get_time(2));pf("[%s] 等待接收文件名...\n", get_time(2));int f_size = read(*clifd, filename, sizeof(filename));if(-1 == f_size){pf("[%s] read函数出错!\n", get_time(2));}//pf("filename:%s\n", filename);strncpy(filename2, filename, 50);strcat(filename2, " ");if (strstr(list, filename2) == NULL || strncmp(filename2, " ", 1) == 0 || strncmp(filename2, " ", 2) == 0){char result[6] = "error";pf("[%s] 文件:%s 不存在,下载终止\n", get_time(2), filename);write(*clifd, result, strlen(result));return;}else{char result[8] = "success";pf("[%s] 文件:%s 存在,开始传输文件内容\n", get_time(2), filename);write(*clifd, result, strlen(result));memset(buf, 0, sizeof(buf));snprintf(buf, 150, "ls -ll %s | awk '{print $5}'", filename);FILE* temp_fp = NULL;temp_fp = popen(buf, "r");if (temp_fp == NULL){pf("[%s] 获取文件大小失败\n", get_time(2));}memset(buf, 0, sizeof(buf));fscanf(temp_fp, "%s", buf);pf("[%s] 文件大小:%sB\n", get_time(2), buf);pclose(temp_fp);memset(buf, 0, sizeof(buf));int fd = open(filename, O_RDONLY);//设置文件读写位置为文件尾部lseek(fd, 0, SEEK_END);// 获取文件字节数(尾部位置)off_t end_pos = lseek(fd, 0, SEEK_CUR);//pf("end_pos:%d\n", end_pos);//设置文件读写位置为文件头部lseek(fd, 0, SEEK_SET);usleep(1000000);do{memset(buf, 0, sizeof(buf));r_size = read(fd, buf, sizeof(buf));pf("[读取文件字节数:%d ", r_size);w_size = write(*clifd, buf, r_size);pf("发送字节数:%d ", w_size);read(*clifd, result, sizeof(result));if(strncmp(result, "success", 10) == 0){pf("成功收到客户端端返回的success]\n");}usleep(10000);off_t cur_pos = lseek(fd, 0, SEEK_CUR);//pf("cur_pos:%d\n", cur_pos);if(cur_pos == end_pos && w_size == 1024){char end[1] = "\0";pf("[读取文件字节数:1 ");w_size = write(*clifd, end, sizeof(end));pf("发送字节数:%d ", w_size);read(*clifd, buf2, sizeof(buf2));if(strncmp(buf2, "success", 10) == 0){pf("成功收到客户端端返回的success]\n");}else{pf("收到客户端返回的异常数据:%s]\n", buf2);}break;}} while (r_size == 1024);usleep(1000000);pf("[%s]文件:%s 发送完毕\n", get_time(2), filename);close(fd);}return;}// 文件列表void c_list(int *clifd){DIR *dir;dir = opendir(".");char list[1024] = {};struct dirent *dirent;while ((dirent = readdir(dir)) != NULL){strcat(list, dirent->d_name);strcat(list, " ");}int l_size = write(*clifd, list, strlen(list) + 1);if(-1 == l_size){pf("[%s] write函数出错!\n", get_time(2));}memset(list, 0, 1024);char dirname[20] = {};int d_size = read(*clifd, dirname, sizeof(dirname));if(-1 == d_size){pf("[%s] read函数出错!\n", get_time(2));}pf("[%s] 收到客户端的数据:%s\n", get_time(2), dirname);if(strncmp(dirname, "...", 20) == 0){closedir(dir);return;}if (strncmp(dirname, ".", 20) == 0){dir = opendir(".");while ((dirent = readdir(dir)) != NULL){strcat(list, dirent->d_name);strcat(list, " ");}l_size = write(*clifd, list, strlen(list) + 1);}else if (strncmp(dirname, "..", 20) == 0){chdir("..");dir = opendir(".");while ((dirent = readdir(dir)) != NULL){strcat(list, dirent->d_name);strcat(list, " ");}l_size = write(*clifd, list, strlen(list) + 1);}else{int re = chdir(dirname);if (re == -1){char result[20] = "目录名错误";int err = write(*clifd, result, strlen(result) + 1);if(-1 == err){pf("[%s] write函数出错!\n", get_time(2));}}else{dir = opendir(".");while ((dirent = readdir(dir)) != NULL){strcat(list, dirent->d_name);strcat(list, " ");}l_size = write(*clifd, list, strlen(list) + 1);}}closedir(dir);return;}

客户端:client.c

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/stat.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <dirent.h>#include <fcntl.h>#include <errno.h>#include "tools.h"#define pf printfint sockfd = 0;typedef struct LS{char mode[15];// 文件的模式int dir_num;// 是否目录或目录中包含目录的数量char user[20];// 文件的用户名char group[20]; // 文件的组名long size;// 文件的字节数char time[30];// 文件的最后修改时间int st_mode;// 文件类型和权限char name[20];// 文件名} LS;// 菜单void menu(void);// 上传void upload(void);// 下载void download(void);// 显示服务器目录和文件void s_list(void);// 显示客户端目录和文件void c_list(void);// 退出程序void quit(void);// 主函数int main(){pf("[%s] 服务器创建socket...\n", get_time(2));sockfd = socket(AF_INET, SOCK_STREAM, 0);if (0 > sockfd){perror("socket");return -1;}pf("[%s] 准备地址...\n", get_time(2));struct sockaddr_in addr = {};addr.sin_family = AF_INET;// 端口自己修改addr.sin_port = htons(60000);// IP自行修改addr.sin_addr.s_addr = inet_addr("127.0.0.1");socklen_t len = sizeof(addr);pf("[%s] 绑定连接服务器...\n", get_time(2));if (connect(sockfd, (struct sockaddr *)&addr, len)){perror("connect");return -1;}menu(); // 加载菜单close(sockfd);return 0;}// 加载菜单void menu(void){for (;;){system("clear");pf("*** 局域网文件传输客户端 ***\n");pf("1、上传\n");pf("2、下载\n");pf("3、查看/修改服务端目录\n");pf("4、查看客户端目录\n");pf("0、退出\n");pf("--------------------------\n");// 我的自定义函数 在tools.c里面 get_cmdswitch (get_cmd('0', '4')){case '1':upload();break;case '2':download();break;case '3':s_list();break;case '4':c_list();break;case '0':quit();return;}}}// 上传void upload(void){char up[20] = "我想上你";write(sockfd, up, strlen(up) + 1);// 打印当前目录文件c_list();int r_size = 0;int w_size = 0;char buf[1024] = {};char buf2[20] = {};r_size = read(sockfd, buf, sizeof(buf));if(strncmp(buf, "success", 10) != 0){pf("[%s] 收到服务端异常数据\n", get_time(2));getch();return;}char pathname[100] = {};char *filename = malloc(50);memset(filename, 0, 50);while(1){pf("请输入文件名:");get_str(pathname, 100);if(!strncmp(pathname, "..", 3) || !strncmp(pathname, ".", 3)){pf(".或..不是普通文件,请重新输入!\n");continue;}break;}int fd = open(pathname, O_RDONLY);// struct stat stat = {};// int fs = fstat(fd, &stat);// long file_size = 0;// file_size = stat.st_size;if (fd == -1){pf("[%s] 文件不存在\n", get_time(2));write(sockfd, "error", 6);getch();}else{write(sockfd, "success", 8);r_size = read(sockfd, buf, sizeof(buf));if(strncmp(buf, "success", 8) == 0){pf("[%s] 服务端已经可以接收文件名,准备发送文件名\n", get_time(2));}if (strrchr(pathname, '/') == NULL){strncpy(filename, pathname, 50);}else{filename = strrchr(pathname, '/');filename += 1;}pf("[%s] 发送文件名:%s 至服务端\n", get_time(2), filename);write(sockfd, filename, strlen(filename) + 1);// 读取服务端返回的文件名,用于判断服务端是否获取到正确的用户名r_size = read(sockfd, buf, sizeof(buf));if(strncmp(buf, filename, strlen(filename)) == 0){// 发送success给服务端,准备接收文件数据write(sockfd, "success", 8);pf("[%s] 校验服务端接收到的文件名成功,准备开始文件传输\n", get_time(2));}else{// 发送failed给服务端,终止接收文件write(sockfd, "failed", 7);pf("[%s] 校验服务端接收到的文件名失败,文件传输终止\n", get_time(2));return;}r_size = 0;memset(buf, 0, sizeof(buf));r_size = read(sockfd, buf, sizeof(buf));if(strncmp(buf, "success", 10) != 0){pf("[%s] 收到服务端异常数据\n", get_time(2));getch();return;}else{pf("[%s] 收到服务端返回success,可以开始文件传输\n", get_time(2));}sleep(1);memset(buf, 0, sizeof(buf));snprintf(buf, 150, "ls -ll %s | awk '{print $5}'", pathname);FILE* temp_fp = NULL;temp_fp = popen(buf, "r");if (temp_fp == NULL){pf("[%s] 获取文件大小失败\n", get_time(2));}memset(buf, 0, sizeof(buf));fscanf(temp_fp, "%s", buf);pf("[%s] 文件大小:%sB\n", get_time(2), buf);pclose(temp_fp);memset(buf, 0, sizeof(buf));//设置文件读写位置为文件尾部lseek(fd, 0, SEEK_END);// 获取文件字节数(尾部位置)off_t end_pos = lseek(fd, 0, SEEK_CUR);//pf("end_pos:%d\n", end_pos);//设置文件读写位置为文件头部lseek(fd, 0, SEEK_SET);do{//pf(" 进入while循环...\n");r_size = read(fd, buf, 1024);pf("[读取文件字节数:%d ", r_size);w_size = write(sockfd, buf, r_size);pf("发送字节数:%d ", w_size);read(sockfd, buf2, sizeof(buf2));if(strncmp(buf2, "success", 10) == 0){pf("成功收到服务端返回的success]\n");}usleep(10000);off_t cur_pos = lseek(fd, 0, SEEK_CUR);//pf("cur_pos:%d\n", cur_pos);if(cur_pos == end_pos && w_size == 1024){char end[1] = "\0";pf("[读取文件字节数:1 ");w_size = write(sockfd, end, sizeof(end));pf("发送字节数:%d ", w_size);read(sockfd, buf2, sizeof(buf2));if(strncmp(buf2, "success", 10) == 0){pf("成功收到服务端返回的success]\n");}else{pf("收到服务端返回的异常数据:%s]\n", buf2);}break;}} while (r_size == 1024);close(fd);char result[20] = {};read(sockfd, result, sizeof(result));if(strncmp(buf2, "success", 10) == 0){pf("[%s] 成功收到服务端返回值:%s,服务器接收文件成功\n", get_time(2), result);}else if(strncmp(buf2, "error", 10) == 0){pf("[%s] 成功收到服务端返回值:%s,服务器接收文件异常\n", get_time(2), result);}else{pf("[%s] 收到服务端返回值:%s,数据异常\n", get_time(2), result);}getch();}return;}// 下载void download(void){int r_size = 0;int w_size = 0;char buf[1024] = {};char filename[50] = {};char list[1024] = {};char down[20] = "我想下你";write(sockfd, down, strlen(down) + 1);r_size = read(sockfd, buf, sizeof(buf));if(strncmp(buf, "success", 10) != 0){pf("[%s] 收到服务端异常数据\n", get_time(2));getch();return;}else{pf("[%s] 服务端成功接收命令\n", get_time(2));}// 发送给服务端success,告知可以开始目录列表的发送write(sockfd, "success", 8);read(sockfd, list, sizeof(list));pf("服务端目录列表:%s\n", list);usleep(1000);while(1){pf("请输入要下载的文件名:");get_str(filename, 50);if(!strncmp(filename, "..", 3) || !strncmp(filename, ".", 3)){pf(".或..不是普通文件,无法下载,请重新输入!\n");continue;}break;}write(sockfd, filename, strlen(filename) + 1);char result[20] = {};read(sockfd, result, sizeof(result));if(strncmp(result, "success", 8) == 0){pf("[%s] 收到服务端发送的数据:%s 文件准备下载\n", get_time(2), result);}else if(strncmp(result, "error", 8) == 0){pf("[%s] 收到服务端发送的数据:%s 文件不存在\n", get_time(2), result);getch();return;}else{pf("[%s] 收到服务端发送的数据:%s 数据异常,下载终止\n", get_time(2), result);getch();return;}int fd = open(filename, O_CREAT | O_RDWR, 0777);do{usleep(500);memset(buf, 0, sizeof(buf));r_size = read(sockfd, buf, sizeof(buf));pf("[收到字节数:%d ", r_size);w_size = write(fd, buf, r_size);pf("写入文件字节数:%d ", w_size);w_size = write(sockfd, "success", 8);pf("发送success给服务端]\n");} while (r_size == 1024);usleep(1000000);pf("[%s]文件:%s 下载完毕\n", get_time(2), filename);close(fd);getch();return;}// 服务端文件列表void s_list(void){char see[20] = "我想看你";write(sockfd, see, strlen(see) + 1);char list[1024] = {};read(sockfd, list, sizeof(list));pf("服务端目录列表: %s\n", list);pf("输入cd+空格+目录,修改服务器工作目录,否则返回上一级\n");char cmd[50] = {};get_str(cmd, 50);if (strstr(cmd, "cd ") == NULL){pf("非cd指令,按任意键返回主界面\n");snprintf(cmd, 20, "...");write(sockfd, cmd, strlen(cmd));getch();}else{char *dir = malloc(20);dir = strrchr(cmd, ' ');dir += 1;write(sockfd, dir, strlen(dir) + 1);read(sockfd, list, sizeof(list));pf("服务端目录列表: %s\n", list);getch();}return;}// 客户端文件列表void c_list(void){pf("当前目录列表:");DIR *dir;dir = opendir(".");struct dirent *dirent;while ((dirent = readdir(dir)) != NULL){pf(" %s ", dirent->d_name);}pf("\n");closedir(dir);getch();return;}// 退出程序void quit(void){// char buf[100] = {};pf("[%s] 告知服务端,我要走了\n", get_time(2));char quit[20] = "我要走了";write(sockfd, quit, strlen(quit) + 1);usleep(10000);pf("[%s] 程序退出\n", get_time(2));return;}

效果图:

1、首先我们make编译

2·然后我们把客户端放到1这个文件夹中,为了方便测试

接着我们运行服务端和客户端

3、我们先试试上传文件

可以看到我们的文件上传成功了,因为篇幅比较大,就简要展示了。

4、那么我们再试试下载功能

5、最后测试下我们的目录功能

我们将服务端的工作目录修改到上一级目录(也就是客户端同级目录)

指令要求(cd+空格+路径),那么我们就输入cd ..

看下效果

6、多线程

多线程奔溃问题已经做了相应处理,简单测试暂无问题,不过内存泄露倒是个问题0.0

那么功能就介绍完毕了,希望能给大家带来帮助(程序可能有些不足之处,如有建议,望赐教)

新变更

新增get_time函数,用于时间获取。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。