2.TCP数据包接收问题
对初学者来说,很多都会认为:客户端与服务器最终的打印数据接收或者发送条数都该是一致的,1000条发送打印,1000条接收打印,长度都为1000。但是,事实上并不是这样,发送打印基本不会有什么问题(只是一般情况,如果发生调度或者其他情况,有可能导致差别,因此也要注意封装),接收打印却不是固定的,下面是测试代码:
测试客户端程序:
![](http://img.e-com-net.com/image/product/68fbd65e7a7842d78f7ff5544f4ff423.gif)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORT 1234
#define MAXDATASIZE 1000
int main(int argc, char *argv[])
{
int sockfd, num;
char buf[MAXDATASIZE + 1] = {0};
struct sockaddr_in server;
int iCount = 0;
if (argc != 2)
{
printf("Usage:%s <IP Address>\n", argv[0]);
exit(1);
}
if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("socket()error\n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
printf("connect()error\n");
exit(1);
}
while (1)
{
memset(buf, 0, sizeof(buf));
if ((num = recv(sockfd, buf, MAXDATASIZE,0)) == -1)
{
printf("recv() error\n");
exit(1);
}
buf[num - 1]='\0';
printf("%dth Recv Length: %d\n", iCount++, num);
}
close(sockfd);
return 0;
}
TCP客户端
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <string.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <netdb.h> 9 10 #define PORT 1234 11 #define MAXDATASIZE 1000 12 13 int main(int argc, char *argv[]) 14 { 15 int sockfd, num; 16 char buf[MAXDATASIZE + 1] = {0}; 17 struct sockaddr_in server; 18 int iCount = 0; 19 20 if (argc != 2) 21 { 22 printf("Usage:%s <IP Address>\n", argv[0]); 23 exit(1); 24 } 25 26 if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1) 27 { 28 printf("socket()error\n"); 29 exit(1); 30 } 31 bzero(&server, sizeof(server)); 32 server.sin_family = AF_INET; 33 server.sin_port = htons(PORT); 34 server.sin_addr.s_addr = inet_addr(argv[1]); 35 if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1) 36 { 37 printf("connect()error\n"); 38 exit(1); 39 } 40 41 while (1) 42 { 43 memset(buf, 0, sizeof(buf)); 44 if ((num = recv(sockfd, buf, MAXDATASIZE,0)) == -1) 45 { 46 printf("recv() error\n"); 47 exit(1); 48 } 49 buf[num - 1]='\0'; 50 printf("%dth Recv Length: %d\n", iCount++, num); 51 } 52 53 close(sockfd); 54 55 return 0; 56 }
测试服务器程序:
![](http://img.e-com-net.com/image/product/68fbd65e7a7842d78f7ff5544f4ff423.gif)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char szbuf[MAXDATASIZE] = {0};
int iCount = 0;
int iLength = 0;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
perror("Bind()error.");
exit(1);
}
if (listen(listenfd, BACKLOG) == -1)
{
perror("listen()error\n");
exit(1);
}
addrlen = sizeof(client);
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)
{
perror("accept()error\n");
exit(1);
}
printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));
memset(szbuf, 'a', sizeof(szbuf));
while (iCount < 1000)
{
iLength = send(connectfd, szbuf, sizeof(szbuf), 0);
printf("%dth Server Send Length %d\n", iCount++, iLength);
}
printf("send over!\n");
sleep(10);
close(connectfd);
close(listenfd);
return 0;
}
TCP服务器程序
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include <signal.h> 10 11 #define PORT 1234 12 #define BACKLOG 5 13 #define MAXDATASIZE 1000 14 15 int main() 16 { 17 int listenfd, connectfd; 18 struct sockaddr_in server; 19 struct sockaddr_in client; 20 socklen_t addrlen; 21 char szbuf[MAXDATASIZE] = {0}; 22 int iCount = 0; 23 int iLength = 0; 24 25 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 26 { 27 perror("Creating socket failed."); 28 exit(1); 29 } 30 31 int opt = SO_REUSEADDR; 32 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 33 34 bzero(&server, sizeof(server)); 35 server.sin_family = AF_INET; 36 server.sin_port = htons(PORT); 37 server.sin_addr.s_addr = htonl(INADDR_ANY); 38 if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) 39 { 40 perror("Bind()error."); 41 exit(1); 42 } 43 if (listen(listenfd, BACKLOG) == -1) 44 { 45 perror("listen()error\n"); 46 exit(1); 47 } 48 49 addrlen = sizeof(client); 50 if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) 51 { 52 perror("accept()error\n"); 53 exit(1); 54 } 55 printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port)); 56 57 memset(szbuf, 'a', sizeof(szbuf)); 58 while (iCount < 1000) 59 { 60 iLength = send(connectfd, szbuf, sizeof(szbuf), 0); 61 printf("%dth Server Send Length %d\n", iCount++, iLength); 62 } 63 64 printf("send over!\n"); 65 sleep(10); 66 67 close(connectfd); 68 close(listenfd); 69 70 return 0; 71 }
客户端接收打印片段如下:
![](http://img.e-com-net.com/image/product/68fbd65e7a7842d78f7ff5544f4ff423.gif)
936th Recv Length: 1000
937th Recv Length: 1000
938th Recv Length: 1000
939th Recv Length: 1000
940th Recv Length: 1000
941th Recv Length: 1000
942th Recv Length: 384
943th Recv Length: 616
944th Recv Length: 1000
945th Recv Length: 1000
946th Recv Length: 1000
947th Recv Length: 1000
948th Recv Length: 1000
949th Recv Length: 1000
950th Recv Length: 1000
951th Recv Length: 1000
952th Recv Length: 1000
953th Recv Length: 1000
954th Recv Length: 1000
955th Recv Length: 1000
956th Recv Length: 1000
957th Recv Length: 1000
958th Recv Length: 1000
959th Recv Length: 1000
960th Recv Length: 1000
961th Recv Length: 1000
962th Recv Length: 384
963th Recv Length: 616
964th Recv Length: 1000
965th Recv Length: 1000
966th Recv Length: 1000
967th Recv Length: 1000
968th Recv Length: 1000
969th Recv Length: 1000
970th Recv Length: 1000
971th Recv Length: 1000
972th Recv Length: 1000
973th Recv Length: 1000
974th Recv Length: 1000
975th Recv Length: 1000
976th Recv Length: 1000
977th Recv Length: 1000
978th Recv Length: 1000
979th Recv Length: 1000
980th Recv Length: 1000
981th Recv Length: 1000
982th Recv Length: 384
983th Recv Length: 616
984th Recv Length: 1000
985th Recv Length: 1000
986th Recv Length: 1000
987th Recv Length: 1000
988th Recv Length: 1000
989th Recv Length: 1000
990th Recv Length: 1000
991th Recv Length: 1000
992th Recv Length: 1000
993th Recv Length: 1000
994th Recv Length: 1000
995th Recv Length: 1000
996th Recv Length: 1000
997th Recv Length: 1000
998th Recv Length: 1000
999th Recv Length: 1000
1000th Recv Length: 1000
1001th Recv Length: 1000
1002th Recv Length: 384
1003th Recv Length: 616
1004th Recv Length: 1000
1005th Recv Length: 1000
1006th Recv Length: 1000
1007th Recv Length: 1000
1008th Recv Length: 1000
1009th Recv Length: 1000
1010th Recv Length: 1000
1011th Recv Length: 1000
1012th Recv Length: 1000
1013th Recv Length: 1000
1014th Recv Length: 1000
1015th Recv Length: 1000
1016th Recv Length: 1000
1017th Recv Length: 1000
1018th Recv Length: 1000
1019th Recv Length: 1000
1020th Recv Length: 1000
1021th Recv Length: 1000
1022th Recv Length: 384
1023th Recv Length: 616
1024th Recv Length: 1000
1025th Recv Length: 1000
1026th Recv Length: 1000
1027th Recv Length: 1000
1028th Recv Length: 1000
1029th Recv Length: 1000
1030th Recv Length: 1000
1031th Recv Length: 1000
1032th Recv Length: 1000
1033th Recv Length: 1000
1034th Recv Length: 1000
1035th Recv Length: 1000
1036th Recv Length: 1000
1037th Recv Length: 1000
1038th Recv Length: 1000
1039th Recv Length: 1000
1040th Recv Length: 1000
1041th Recv Length: 1000
1042th Recv Length: 384
1043th Recv Length: 616
1044th Recv Length: 1000
1045th Recv Length: 1000
1046th Recv Length: 1000
1047th Recv Length: 1000
1048th Recv Length: 1000
1049th Recv Length: 1000
1050th Recv Length: 1000
客户端接收打印片段
1 936th Recv Length: 1000 2 937th Recv Length: 1000 3 938th Recv Length: 1000 4 939th Recv Length: 1000 5 940th Recv Length: 1000 6 941th Recv Length: 1000 7 942th Recv Length: 384 8 943th Recv Length: 616 9 944th Recv Length: 1000 10 945th Recv Length: 1000 11 946th Recv Length: 1000 12 947th Recv Length: 1000 13 948th Recv Length: 1000 14 949th Recv Length: 1000 15 950th Recv Length: 1000 16 951th Recv Length: 1000 17 952th Recv Length: 1000 18 953th Recv Length: 1000 19 954th Recv Length: 1000 20 955th Recv Length: 1000 21 956th Recv Length: 1000 22 957th Recv Length: 1000 23 958th Recv Length: 1000 24 959th Recv Length: 1000 25 960th Recv Length: 1000 26 961th Recv Length: 1000 27 962th Recv Length: 384 28 963th Recv Length: 616 29 964th Recv Length: 1000 30 965th Recv Length: 1000 31 966th Recv Length: 1000 32 967th Recv Length: 1000 33 968th Recv Length: 1000 34 969th Recv Length: 1000 35 970th Recv Length: 1000 36 971th Recv Length: 1000 37 972th Recv Length: 1000 38 973th Recv Length: 1000 39 974th Recv Length: 1000 40 975th Recv Length: 1000 41 976th Recv Length: 1000 42 977th Recv Length: 1000 43 978th Recv Length: 1000 44 979th Recv Length: 1000 45 980th Recv Length: 1000 46 981th Recv Length: 1000 47 982th Recv Length: 384 48 983th Recv Length: 616 49 984th Recv Length: 1000 50 985th Recv Length: 1000 51 986th Recv Length: 1000 52 987th Recv Length: 1000 53 988th Recv Length: 1000 54 989th Recv Length: 1000 55 990th Recv Length: 1000 56 991th Recv Length: 1000 57 992th Recv Length: 1000 58 993th Recv Length: 1000 59 994th Recv Length: 1000 60 995th Recv Length: 1000 61 996th Recv Length: 1000 62 997th Recv Length: 1000 63 998th Recv Length: 1000 64 999th Recv Length: 1000 65 1000th Recv Length: 1000 66 1001th Recv Length: 1000 67 1002th Recv Length: 384 68 1003th Recv Length: 616 69 1004th Recv Length: 1000 70 1005th Recv Length: 1000 71 1006th Recv Length: 1000 72 1007th Recv Length: 1000 73 1008th Recv Length: 1000 74 1009th Recv Length: 1000 75 1010th Recv Length: 1000 76 1011th Recv Length: 1000 77 1012th Recv Length: 1000 78 1013th Recv Length: 1000 79 1014th Recv Length: 1000 80 1015th Recv Length: 1000 81 1016th Recv Length: 1000 82 1017th Recv Length: 1000 83 1018th Recv Length: 1000 84 1019th Recv Length: 1000 85 1020th Recv Length: 1000 86 1021th Recv Length: 1000 87 1022th Recv Length: 384 88 1023th Recv Length: 616 89 1024th Recv Length: 1000 90 1025th Recv Length: 1000 91 1026th Recv Length: 1000 92 1027th Recv Length: 1000 93 1028th Recv Length: 1000 94 1029th Recv Length: 1000 95 1030th Recv Length: 1000 96 1031th Recv Length: 1000 97 1032th Recv Length: 1000 98 1033th Recv Length: 1000 99 1034th Recv Length: 1000 100 1035th Recv Length: 1000 101 1036th Recv Length: 1000 102 1037th Recv Length: 1000 103 1038th Recv Length: 1000 104 1039th Recv Length: 1000 105 1040th Recv Length: 1000 106 1041th Recv Length: 1000 107 1042th Recv Length: 384 108 1043th Recv Length: 616 109 1044th Recv Length: 1000 110 1045th Recv Length: 1000 111 1046th Recv Length: 1000 112 1047th Recv Length: 1000 113 1048th Recv Length: 1000 114 1049th Recv Length: 1000 115 1050th Recv Length: 1000
服务器发送打印片段整理时发现丢失了,大家可以自己试试,没有问题。
不难发现,服务器发送正常,客户端在接收时却和我们想的很不一样,但发送和接收的总数据量是一致的,就是说数据没有丢失。如果编程者认为TCP情况下发送和接收的数据长度都一致的,那就极有可能在代码中体现出这一思想,最终出现问题。
其实,这就是所谓的“粘包”现象,Stevens很明确地已经指出了这一点,他说,“UDP是长度固定的、无连接的不可靠报文传输;TCP是有序、可靠、双向的面向连接字节流”。他没说TCP是长度固定的,有没有?当然我更倾向于这样的理解,UDP是面向报文的,报文在传输时是不能被分割的(只是从应用层来看);TCP是面向字节流的,接收多少数据完全取决于发送和接收的速度了,有多少数据recv就返回多少,数据长度并不和send保持一致,也没这个必要。
那么这个问题怎么解决呢?其实,我们只要将recv封装一层就可以了,那就是我们熟悉的readn函数(该函数不是系统调用),代码如下:
![](http://img.e-com-net.com/image/product/68fbd65e7a7842d78f7ff5544f4ff423.gif)
int readn(int connfd, void *vptr, int n)
{
int nleft;
int nread;
char *ptr;
struct timeval select_timeout;
fd_set rset;
ptr = vptr;
nleft = n;
while (nleft > 0)
{
FD_ZERO(&rset);
FD_SET(connfd, &rset);
select_timeout.tv_sec = 5;
select_timeout.tv_usec = 0;
if (select(connfd+1, &rset, NULL, NULL, &select_timeout) <= 0)
{
return -1;
}
if ((nread = recv(connfd, ptr, nleft, 0)) < 0)
{
if(errno == EINTR)
{
nread = 0;
}
else
{
return -1;
}
}
else if (nread == 0)
{
break;
}
nleft -= nread;
ptr += nread;
}
return(n - nleft);
}
readn
1 int readn(int connfd, void *vptr, int n) 2 { 3 int nleft; 4 int nread; 5 char *ptr; 6 struct timeval select_timeout; 7 fd_set rset; 8 9 ptr = vptr; 10 nleft = n; 11 12 while (nleft > 0) 13 { 14 FD_ZERO(&rset); 15 FD_SET(connfd, &rset); 16 select_timeout.tv_sec = 5; 17 select_timeout.tv_usec = 0; 18 if (select(connfd+1, &rset, NULL, NULL, &select_timeout) <= 0) 19 { 20 return -1; 21 } 22 if ((nread = recv(connfd, ptr, nleft, 0)) < 0) 23 { 24 if(errno == EINTR) 25 { 26 nread = 0; 27 } 28 else 29 { 30 return -1; 31 } 32 } 33 else if (nread == 0) 34 { 35 break; 36 } 37 nleft -= nread; 38 ptr += nread; 39 } 40 return(n - nleft); 41 }
相应的也有writen函数
![](http://img.e-com-net.com/image/product/68fbd65e7a7842d78f7ff5544f4ff423.gif)
int writen(int connfd, void *vptr, size_t n)
{
int nleft, nwritten;
char *ptr;
ptr = vptr;
nleft = n;
while(nleft>0)
{
if((nwritten = send(connfd, ptr, nleft, 0)) == ERROR)
{
if(errnoGet() == EINTR)
{
//PRT_ERR(("EINTR\n"));
nwritten = 0;
}
else
{
//PRT_ERR(("Send() error, 0x%x\n", errnoGet()));
return ERROR;
}
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
writen
1 int writen(int connfd, void *vptr, size_t n) 2 { 3 int nleft, nwritten; 4 char *ptr; 5 6 ptr = vptr; 7 nleft = n; 8 9 while(nleft>0) 10 { 11 if((nwritten = send(connfd, ptr, nleft, 0)) == ERROR) 12 { 13 if(errnoGet() == EINTR) 14 { 15 //PRT_ERR(("EINTR\n")); 16 nwritten = 0; 17 } 18 else 19 { 20 //PRT_ERR(("Send() error, 0x%x\n", errnoGet())); 21 return ERROR; 22 } 23 } 24 nleft -= nwritten; 25 ptr += nwritten; 26 } 27 28 return(n); 29 }
函数中为什么对EINTR进行处理后面再说,也是必不可少的。
在处理TCP发送和接收部分时,可以说必须要使用上述封装,否则等到造成数据不完整或者不一致后再去找问题,可能就麻烦了。这个是必不可少滴。