Linux网络中接收 "二进制" 流的那些事 --- 就recv的返回值和strlen库函数进行对话
1. 前言
很多朋友在做网络编程开发的时候可能都遇到这样的问题,在进行接收二进制流的数据的时候,使用strlen库函数来得到
二进制数据长度的时候并不准确。为什么呢??首先,使用strlen进行统计长度的为字符串,并非二进制流数据,因此在
获取二进制数据流的定长中并不适合。解决的问题必然使用网络接收函数的返回值来进行判断,如recv和recvfrom等。
2. 简单的网络服务器
Linux中简单的网络服务器做起来很简单,无非就是以下几个步骤
创建网络套接字(socket) --> 绑定本地套接字到网络中(bind) --> 设置最大监听数目(listen) --> 监听客户端接入(accept)
3. 具体的例子
(服务端)
1 #include <stdio.h> 2 #include <string.h> 3 #include <errno.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <arpa/inet.h> 7 #include <unistd.h> 8 9 #define WEB_PORT 8080 10 #define MAX_CLIENT 5 11 #define MAX_RECV 1024 12 13 int main(int argc,char *argv[]) 14 { 15 // 1. 创建网络套接字 16 int sock = socket(AF_INET,SOCK_STREAM,0); 17 if(0 > sock) 18 { 19 fprintf(stderr,"socket: %s\n",strerror(errno)); 20 return -1; 21 } 22 23 // 2. 设置端口立即释放,可以立即使用 24 int on = 1; 25 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); 26 27 // 3. 绑定本地套接字到网络中 28 struct sockaddr_in localAddr; 29 socklen_t localAddrLen = sizeof(localAddr); 30 31 localAddr.sin_family = AF_INET; 32 localAddr.sin_port = htons(WEB_PORT); 33 localAddr.sin_addr.s_addr = htonl(INADDR_ANY); 34 35 if(0 > bind(sock,(struct sockaddr *)&localAddr,localAddrLen)) 36 { 37 fprintf(stderr,"bind: %s\n",strerror(errno)); 38 return -1; 39 } 40 41 // 4. 设置最大监听数目 42 if(0 > listen(sock,MAX_CLIENT)) 43 { 44 fprintf(stderr,"bind: %s\n",strerror(errno)); 45 return -1; 46 } 47 48 // 5. 监听客户端接入 49 struct sockaddr_in peerAddr; 50 socklen_t peerAddrLen = sizeof(peerAddr); 51 char cRecvDataBuf[MAX_RECV] = {0}; 52 ssize_t sRecvRet = 0; 53 54 while(1) 55 { 56 int connfd = accept(sock,(struct sockaddr *)&peerAddr,&peerAddrLen); 57 if(0 > connfd) 58 { 59 fprintf(stderr,"accept: %s\n",strerror(errno)); 60 return -1; 61 } 62 63 memset(cRecvDataBuf,0,sizeof(cRecvDataBuf)); 64 sRecvRet = recv(connfd,cRecvDataBuf,sizeof(cRecvDataBuf),0); 65 if(0 > sRecvRet) 66 { 67 fprintf(stderr,"recv: %s\n",strerror(errno)); 68 return -1; 69 } 70 71 printf("\n**************************************\n"); 72 printf("sRecvRet = %ld\n",sRecvRet); 73 printf("strlen(cRecvDataBuf) = %lu\n",strlen(cRecvDataBuf)); 74 printf("**************************************\n"); 75 76 printf("\ncRecvDataBuf :\n%s\n\n",cRecvDataBuf); 77 78 close(connfd); 79 } 80 81 close(sock); 82 83 return 0; 84 }
(客户端)
#include <stdio.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #define WEB_PORT 8080 int main(int argc,char *argv[]) { // 1. 创建网络套接字 int sock = socket(AF_INET,SOCK_STREAM,0); if(0 > sock) { fprintf(stderr,"socket: %s\n",strerror(errno)); return -1; } // 2. 设置端口立即释放,可以立即使用 int on = 1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); // 3. 设置服务器的地址和链接发送二进制流数据 struct sockaddr_in serverAddr; socklen_t serverAddrLen = sizeof(serverAddr); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(WEB_PORT); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); if(0 == connect(sock,(struct sockaddr *)&serverAddr,serverAddrLen)) { FILE *pFile = fopen("./linux.bin.ub","rb"); if(NULL != pFile) { // 4. 获取二进制文件的数据大小 fseek(pFile,0,SEEK_END); long lFileSize = ftell(pFile); rewind(pFile); // 5. 读取数据并发送 char *pSendBuf = (char *)malloc(lFileSize+1); if(NULL == pSendBuf) { fprintf(stderr,"malloc: %s\n",strerror(errno)); return -1; } memset(pSendBuf,0,lFileSize+1); fread(pSendBuf,lFileSize,1,pFile); fclose(pFile); send(sock,pSendBuf,lFileSize,0); free(pSendBuf); close(sock); } } return 0; }
(二进制流数据 : 9.27 MB (9,728,804 字节) )
4. 比较结果:
首先本人在服务器端只是接受一次的数据,最大长度为1024字节,那么如何收到的二进制数据的程度实际上应该为1024字节(假设网路正常,只接收一次,由于文件的二进制流数据大小为9728804字节,所以收到的数据长度为1024字节),具体的结果如下:
5. 结论:
从结果图可以看出,如果使用strlen进行获取数据的话只有12字节,使用返回值来定长度的话,确实1024字节,这个返回值才是正确的。因
此,在网络编程中,建议大家在发送二进制文件流或者在接收二进制文件流的时候,切记不要使用strlen进行定长,否则容易出错。但是如果发送的是字符串流的数据的话,这倒不是什么问题,但是为了系统安全和数据的准确性,使用返回值比strlen更加有优
势。