北京市建设城乡建设委员会官方网站/北京seo运营推广
Socket文件传输1
在socket多线程实现多客户端连接服务器进行聊天的基础上,添加文件传输功能。
fix1
- 在server.c中添加file_info结构体,用于单独接受某次客户端发送的fileinfo信息(其中包含文件名、文件大小)
- 修改server.c中worker函数,为其添加
recv(fileinfo)
的过程,并将文件名与文件大小进行输出。 - 修改client.c中命令行解析部分的内容,添加
argv[3]
参数filename - 在client.c中添加file_info结构体,并在命令行解析后完成对fileinfo结构体的传输信息设置
- 在client.c中将文件信息与文件内容分开发送给服务端(如果合并发送则不便于分离)。
//1.server.c
#include "head.h"
#include "common.h"#define MAXUSER 100#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)struct Data {int fd;struct sockaddr_in client;
};/* 传输文件的信息 */
struct file_info {char filename[50];ssize_t size;//传输文件大小
};void *worker(void *arg) {struct Data data = *(struct Data *)arg;//进行参数的类型转换int sockfd = data.fd;struct sockaddr_in client = data.client;//将参数进行解封装//1.文件info信息的接收struct file_info info;recv(sockfd, (void *)&info, sizeof(info), 0);printf("filename = %s filesize = %ldbyte\n", info.filename, info.size);while (1) {//2.文件内容的接收char buff[1024] = {0};ssize_t rsize = recv(sockfd, buff, sizeof(buff), 0);if (rsize > 0) {printf("<receive %ldbyte> %s:%d: \n%s\n", rsize, inet_ntoa(client.sin_addr), ntohs(client.sin_port), buff);} else {close(sockfd);break;}}printf("<server> : %s:%d has left!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
}int main(int argc, char *argv[]) {//./a.out -p port//1.命令行解析if (argc != 3) {fprintf(stderr, "Usage : %s -p port", argv[0]);exit(1);}int opt;int port;int sockfd;while ((opt = getopt(argc, argv, "p:")) != -1) {switch (opt) {case 'p':port = atoi(optarg);break;default:fprintf(stderr, "Usage : %s -p port\n", argv[0]);exit(1);}}//2.创建socketif ((sockfd = socketCreate(port)) < 0) handle_error("socketCreate");//3.accept循环的接受客户端对server的连接pthread_t tid[MAXUSER + 5];//线程tid数组 用于解决线程竞争struct Data data[MAXUSER + 5];//文件描述符与client的结构体数组 用于解决文件描述符竞争while (1) {int newfd;//新建的文件描述符struct sockaddr_in client;//用于存放临时建立连接的客户端的信息socklen_t len = sizeof(client);if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");printf("<accept> %s:%d: accept a client!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));//4.多线程recv接受消息data[newfd].client = client;data[newfd].fd = newfd;//这样所使用的文件描述符 就是当前进程所打开的文件描述符中最小、未被使用的fd//创建线程进行work工作涉及传参问题:需要使用结构体将参数封装后,将结构体指针传给线程pthread_create(&tid[newfd], NULL, worker, (void *)&data[newfd]);//成功解决tid与fd竞争的问题}return 0;
}
//2.client.c
#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int sockfd;struct file_info {char filename[50];ssize_t size;//文件大小
};//ctrl+c信号处理
void closeSock(int signum) {send(sockfd, "I am leaving...", 27, 0);close(sockfd);//关闭客户端文件描述符exit(0);
}int main(int argc, char *argv[]) {//./a.out ip port filenameif (argc != 4) {fprintf(stderr, "Usage : %s ip port filename\n", argv[0]);exit(1);}signal(SIGINT, closeSock);//1.建立连接connectif ((sockfd = socketConnect(argv[1], atoi(argv[2]))) < 0) handle_error("socketConnect");printf("connect sccuess!\n");//2.确定传输文件的相关信息infostruct file_info info;//确定传输的文件名strcpy(info.filename, argv[3]);//确定传输的文件大小FILE *fp = fopen(argv[3], "r");fseek(fp, 0, SEEK_END);info.size == ftell(fp);fseek(fp, 0, SEEK_SET);//将指针重新定位到文件的开始//3.send发送文件info信息send(sockfd, (void *)&info, sizeof(info), 0);//4.send发送文件内容信息while (1) {//循环发送消息char buff[1024] = {0};ssize_t size;if ((size = fread(buff, 1, 1024, fp)) == 0) break;//读不到数据则直接break循环//只要向文件描述符中写入 tcp服务就会帮助发送消息//With a zero flags argument, send is equivalent to write(2).send(sockfd, buff, /*strlen(buff)*/sizeof(buff), 0);//buff的最后一个字符没有设置为\0不可使用strlen(buff)}fclose(fp);close(sockfd);//断开连接return 0;
}
客户端将1.server.c的文件内容消息进行发送:
服务端成功读取到客户端发送的消息并进行输出:
基本功能大致实现。
bug
发现bug:
-
文件超过设置的buff大小1024,则将发送的文件分为多次发送,但是最后一次发送的剩余文件内容并没有达到1024byte字节大小,发送了多余的长度内容(一种资源的浪费)。
-
对问题bug进行放大:如果直接发送一个200byte以内的文件,服务端会发生什么情况?测试结果如下:vim test.txt
//test.txt this is a filetransfer test! the following are the content: today is good day. i would like to go outside for a walk.
fix2
每次发送1024buff的,最后一次发送文件剩余部分内容不足1024byte也居然占用了1024byte字节的问题:
定位问题到client.c文件中的59行send(sockfd, buff, sizeof(buff), 0);
,sizeof(buff)
造成信息浪费。
解决方案1:
- 将buff的1023个字符设置为
\0
字符串结束标志, - 将fread部分修改为每次读入1023个字节。
- 将
sizeof(buff)
改为strlen(buff)
,
buff[1023] = '\0';
if ((size = fread(buff, 1, 1023, fp)) == 0) break;//将文件数据读入到buff中 读不到数据则直接break循环
send(sockfd, buff, strlen(buff), 0);
//2.client.c修改后
#include "head.h"
#include "common.h"#define handle_error(msg) \do { perror(msg); exit(EXIT_FAILURE); } while (0)int sockfd;struct file_info {char filename[50];ssize_t size;//文件大小
};//ctrl+c信号处理
void closeSock(int signum) {send(sockfd, "I am leaving...", 27, 0);close(sockfd);//关闭客户端文件描述符exit(0);
}int main(int argc, char *argv[]) {//./a.out ip port filenameif (argc != 4) {fprintf(stderr, "Usage : %s ip port filename\n", argv[0]);exit(1);}signal(SIGINT, closeSock);//1.建立连接connectif ((sockfd = socketConnect(argv[1], atoi(argv[2]))) < 0) handle_error("socketConnect");printf("connect sccuess!\n");//2.确定传输文件的相关信息infostruct file_info info;//确定传输的文件名strcpy(info.filename, argv[3]);//确定传输的文件大小FILE *fp = fopen(argv[3], "r");fseek(fp, 0, SEEK_END);info.size = ftell(fp);fseek(fp, 0, SEEK_SET);//将指针重新定位到文件的开始//3.send发送文件info信息send(sockfd, (void *)&info, sizeof(info), 0);//4.send发送文件内容信息while (1) {//循环发送消息char buff[1024] = {0};buff[1023] = '\0';ssize_t size;if ((size = fread(buff, 1, 1023, fp)) == 0) break;//将文件数据读入到buff中 读不到数据则直接break循环//只要向文件描述符中写入 tcp服务就会帮助发送消息//With a zero flags argument, send is equivalent to write(2).send(sockfd, buff, /*strlen(buff)*/strlen(buff), 0);//buff的最后一个字符没有设置为\0不可使用strlen(buff)}fclose(fp);close(sockfd);//断开连接return 0;
}
正常接受到文件传输的信息:
被分割的buff中的最后一个buff的文件大小为809byte,没有资源浪费,bug成功解决。
对200byte以内的文件进行测试,server接受到的为118byte,没有出现资源浪费。