网站 关键词 多少个,全局代理ip,新手如何建设网站,wordpress添加logo代码上一篇实现了进程池的小文件传输#xff0c;使用自定义的协议#xff0c;数据长度数据本身#xff0c;类似小火车的形式#xff0c;可以很好的解决TCP“粘包”的问题。
【Linux C | 网络编程】进程池小文件传输的实现详解#xff08;二#xff09; 当文件的内容大小少于…上一篇实现了进程池的小文件传输使用自定义的协议数据长度数据本身类似小火车的形式可以很好的解决TCP“粘包”的问题。
【Linux C | 网络编程】进程池小文件传输的实现详解二 当文件的内容大小少于小火车车厢的时候上述代码的表现是非常完美的。但是如果一旦文件长度大于火车车厢大小那么上述代码就无能为力了。 那么当传出大文件的时候有哪些办法呢 1.使用循环来传输 最自然的思路解决大文件问题就是使用循环机制发送方使用一个循环来读取文件内容每当读取一定字节的数据之后将这些数据的大小和内容填充进小火车当中接收方就不断的使用 recv 接收小火车的火车头和车厢先读取4 个字节的火车头再根据车厢长度接收后续内容。 对于大文件的传输先要获取大文件的长度的信息这里可以使用fstat()函数。 服务端发送大文件的流程 1.获取大文件的长度信息
2.使用一个小火车先发送大文件的文件名长度文件名内容
3.发送大文件的内容先发文件内容的长度信息然后使用小火车循环发送文件的内容
#include process_pool.h#define FILENAME bigfile.avi//sendn函数可以发送确定的字节数
//sockfd:通信套接字buff要发送的内容len要发送的内容字节数
int sendn(int sockfd, const void * buff, int len)
{int left len;const char* pbuf buff;int ret -1;while(left 0) {ret send(sockfd, pbuf, left, 0);if(ret 0) {perror(send);return -1;}left - ret;pbuf ret;}return len - left;
}int transferFile(int peerfd)
{//读取本地文件int fd open(FILENAME, O_RDONLY);ERROR_CHECK(fd, -1, open);//获取文件的长度struct stat st;memset(st, 0, sizeof(st));fstat(fd, st);char buff[100] {0};int filelength st.st_size; //获取文件的大小printf(filelength: %d\n, filelength);//进行发送操作//1. 发送文件名train_t t;memset(t, 0, sizeof(t));t.len strlen(FILENAME);strcpy(t.buf, FILENAME);sendn(peerfd, t, 4 t.len);//2. 再发送文件内容//2.1 发送文件的长度sendn(peerfd, filelength, sizeof(filelength));int ret 0;int total 0;//2.2 再发送文件内容while(total filelength) {memset(t, 0, sizeof(t));ret read(fd, t.buf, 1000); //每次从文件读取1000个字节的内容,放到一个小火车上if(ret 0) {t.len ret; //初始化小火车的车头长度//sendn函数确保 4 t.len 个字节的数据能正常发送ret sendn(peerfd, t, 4 t.len);if(ret 0) {printf( exit while not send.\n);break;//发生了错误,就退出while循环}total (ret - 4); //已发送内容不包括车头}}return 0;
}
服务端接受流程
1.先接受文件名内容
2.接受文件的内容
#include func.hint main()
{//创建客户端的套接字int clientfd socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, socket);struct sockaddr_in serveraddr;memset(serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family AF_INET;serveraddr.sin_port htons(8080);serveraddr.sin_addr.s_addr inet_addr(127.0.0.1);//连接服务器int ret connect(clientfd, (struct sockaddr*)serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, connect);printf(connect success.\n);//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length 0;ret recv(clientfd, length, sizeof(length), 0);printf(filename length: %d\n, length);//1.2 再接收文件名本身char buff[1000] {0};ret recv(clientfd, buff, length, 0);printf(1 recv ret: %d\n, ret);int fd open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, open);//2. 再接收文件的内容//2.1 先接收文件内容的长度ret recv(clientfd, length, sizeof(length), 0);printf(fileconent length: %d\n, length);int total 0;int len 0;//每一个分片的长度//2.2 再接收文件内容本身while(total length) {recv(clientfd, len, sizeof(len), 0);if(len ! 1000) {printf(slice len: %d\n, len);//printf(total: %d bytes.\n, total);}memset(buff, 0, sizeof(buff));//recv函数无法保证每一次接收都能获取len个字节的长度//因此出现了读取长度异常的情况ret recv(clientfd, buff, len, 0);// ret len//printf(slice %d bytes.\n, ret);if(ret 0) {total ret;write(fd, buff, ret);//写入本地文件}}close(fd);close(clientfd);return 0;
}使用md5算法计算哈希值验证文件的正确性
# client
$md5sum file2
# 计算md5码需要等待一段时间
8e9d11a16f03372c82c5134278a0bd7d file2
# server
$md5sum file2
8e9d11a16f03372c82c5134278a0bd7d file2
存在问题
一般情况下上述方法确实可以传输完整的文件但是存在一个大bugrecv函数无法保证每一次接收都能获取len个字节的长度因此出现了读取长度异常的情况。
比如内容只传输了一半后续的数据就直接被当成长度了 出现了长度的偏差导致传输出现问题下一次循环开始时本来希望读取的是长度信息但其实读取的是内容从而导致长度数据出现问题。
原因是TCP是一种流式协议它只能负责每个报文可靠有序地发送和接收但是并不能保证传输到网络缓冲区当中的就是完整的一个小火车。这样就有可能会到导致数据读取问题下面就举一个例子假设发送方需要传输两个小火车其中每个 车厢都是1000个字节那么自然火车头都是4个字节里面各自存储了1000 当然是二进制形式当 两个小火车发送到socket的时候由于TCP是流式协议所以小火车与小火车之间边界就不见了到了 接收方这边 recv可能会先收到4个字节确定第一个小火车的车厢长度再收到800字节此时继续再 recv就会从第一个火车车厢中继续取出4个字节那这4个字节显然就不是第二个小火车的车厢长度 了。 有以下解决方案
1.1使用MSG_WAITALL接收完整的长度数据
recv函数用于从套接字接收数据。它的第四个参数是一个标志用来控制接收操作的行为。
如果将第四个参数设置为0或者使用MSG_WAITALL标志recv函数会一直阻塞直到接收到指定长度的数据。如果接收到的数据长度小于请求的长度recv函数会一直阻塞直到接收完指定长度的数据或者发生错误。
#include func.hint main()
{//创建客户端的套接字int clientfd socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, socket);struct sockaddr_in serveraddr;memset(serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family AF_INET;serveraddr.sin_port htons(8080);serveraddr.sin_addr.s_addr inet_addr(127.0.0.1);//连接服务器int ret connect(clientfd, (struct sockaddr*)serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, connect);printf(connect success.\n);//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length 0;ret recv(clientfd, length, sizeof(length), 0);printf(filename length: %d\n, length);//1.2 再接收文件名本身char buff[1000] {0};ret recv(clientfd, buff, length, 0);printf(1 recv ret: %d\n, ret);int fd open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, open);//2. 再接收文件的内容//2.1 先接收文件内容的长度ret recv(clientfd, length, sizeof(length), 0);printf(fileconent length: %d\n, length);int total 0;int len 0;//每一个分片的长度//2.2 再接收文件内容本身while(total length) {recv(clientfd, len, sizeof(len), MSG_WAITALL);if(len ! 1000) {printf(slice len: %d\n, len);//printf(total: %d bytes.\n, total);}memset(buff, 0, sizeof(buff));//将recv函数的第四个参数设置为MSG_WAITALL之后//表示必须要接收len个字节的数据之后才会返回ret recv(clientfd, buff, len, MSG_WAITALL);// ret len//printf(slice %d bytes.\n, ret);if(ret 0) {total ret;write(fd, buff, ret);//写入本地文件}}close(fd);close(clientfd);return 0;
}1.2每次循环发送和接受指定长度的数据
服务端发来多少客户端就接受多少服务端封装一个发送指定大小数据的函数客户端封装一个接收指定大小数据的函数。
客户端代码
#include func.h//接收确定的字节数的数据
//sockfd:通信套接字buff接收的内容,len接收内容的长度
int recvn(int sockfd, void * buff, int len)
{int left len;char * pbuf buff;int ret -1;while(left 0) {ret recv(sockfd, pbuf, left, 0);if(ret 0) {break;} else if(ret 0) {perror(recv);return -1;}left - ret;pbuf ret;}return len - left;
}int main()
{//创建客户端的套接字int clientfd socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, socket);struct sockaddr_in serveraddr;memset(serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family AF_INET;serveraddr.sin_port htons(8080);serveraddr.sin_addr.s_addr inet_addr(127.0.0.1);//连接服务器int ret connect(clientfd, (struct sockaddr*)serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, connect);printf(connect success.\n);//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length 0;ret recvn(clientfd, length, sizeof(length));printf(filename length: %d\n, length);//1.2 再接收文件名本身char buff[1000] {0};ret recvn(clientfd, buff, length);printf(1 recv ret: %d\n, ret);int fd open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, open);//2. 再接收文件的内容//2.1 先接收文件内容的长度ret recvn(clientfd, length, sizeof(length));printf(fileconent length: %d\n, length);int total 0;int len 0;//每一个分片的长度//2.2 再接收文件内容本身while(total length) {ret recvn(clientfd, len, sizeof(len));if(len ! 1000) {printf(slice len: %d\n, len);//printf(total: %d bytes.\n, total);}memset(buff, 0, sizeof(buff));ret recvn(clientfd, buff, len);if(ret ! 1000) {//printf(slice %d bytes.\n, ret);}if(ret 0) {total ret;write(fd, buff, ret);//写入本地文件}}close(fd);close(clientfd);return 0;
}1.3客户端断开连接 --- SIGPIPE信号的处理
现象客户端断开连接时导致服务器中的某一个子进程挂掉了变成了僵尸进程导致父子进程通信的管道被关闭了。而父进程一直监听该管道因此epoll_wait不断返回才有了服务器疯狂打印的情况出现。
通常情况下如果程序向一个已经关闭写入的管道写数据操作系统会发送 SIGPIPE 信号给进程而默认的行为是终止该进程。但是有时候我们希望在这种情况下不让程序退出而是希望处理其他错误或者采取其他措施。这时候就可以通过 signal(SIGPIPE, SIG_IGN); 来忽略 SIGPIPE 信号让程序继续执行下去。
当客户端关闭时服务器先执行第一次send操作客户端会返回一个RST报文 当服务器的子进程再次发送第二次send操作时会接收到SIGPIPE信号导致子进程奔溃从而导致子进程与父进程通信的管道也会关掉。
解决该问题只需要让子进程忽略掉SIGPIPE信号即可。 1.4客户端打印文件传输的进度条
#include func.h//接收确定的字节数的数据
int recvn(int sockfd, void * buff, int len)
{int left len;char * pbuf buff;int ret -1;while(left 0) {ret recv(sockfd, pbuf, left, 0);if(ret 0) {break;} else if(ret 0) {perror(recv);return -1;}left - ret;pbuf ret;}return len - left;
}int main()
{//创建客户端的套接字int clientfd socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(clientfd, -1, socket);struct sockaddr_in serveraddr;memset(serveraddr, 0, sizeof(serveraddr));//指定使用的是IPv4的地址类型 AF_INETserveraddr.sin_family AF_INET;serveraddr.sin_port htons(8080);serveraddr.sin_addr.s_addr inet_addr(127.0.0.1);//serveraddr.sin_addr.s_addr inet_addr(192.168.30.129);//连接服务器int ret connect(clientfd, (struct sockaddr*)serveraddr, sizeof(serveraddr));ERROR_CHECK(ret, -1, connect);printf(connect success.\n);//进行文件的接收//1. 先接收文件的名字//1.1 先接收文件名的长度int length 0;ret recvn(clientfd, length, sizeof(length));printf(filename length: %d\n, length);//1.2 再接收文件名本身char buff[1000] {0};ret recvn(clientfd, buff, length);printf(1 recv ret: %d\n, ret);int fd open(buff, O_CREAT|O_RDWR, 0644);ERROR_CHECK(fd, -1, open);//2. 再接收文件的内容//2.1 先接收文件内容的长度ret recvn(clientfd, length, sizeof(length));printf(fileconent length: %d\n, length);int segment length / 100;//百分之一的长度int lastSize 0;#if 1int curSize 0;int len 0;//每一个分片的长度//2.2 再接收文件内容本身while(curSize length) {ret recvn(clientfd, len, sizeof(len));memset(buff, 0, sizeof(buff));ret recvn(clientfd, buff, len);if(ret 0) {curSize ret;write(fd, buff, ret);//写入本地文件if(curSize - lastSize segment) { //每百分之一打印一次//打印进度条printf(has complete %5.2f%%\r, (double)100 * curSize / length);fflush(stdout);lastSize curSize;//更新上一次打印百分比时的长度}}}printf(has complete 100.00%%\n);
#endifclose(fd);close(clientfd);return 0;
}