SOCKET网络编程快速上手(二)——细节问题(2)

2.TCP数据包接收问题

对初学者来说,很多都会认为:客户端与服务器最终的打印数据接收或者发送条数都该是一致的,1000条发送打印,1000条接收打印,长度都为1000。但是,事实上并不是这样,发送打印基本不会有什么问题(只是一般情况,如果发生调度或者其他情况,有可能导致差别,因此也要注意封装),接收打印却不是固定的,下面是测试代码:

测试客户端程序:

#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 }

测试服务器程序:

#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 }

客户端接收打印片段如下:

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函数(该函数不是系统调用),代码如下:

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函数

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发送和接收部分时,可以说必须要使用上述封装,否则等到造成数据不完整或者不一致后再去找问题,可能就麻烦了。这个是必不可少滴。

 
 

SOCKET网络编程细节问题(2)的更多相关文章

  1. SOCKET网络编程细节问题(4)

    SOCKET网络编程快速上手(二)——细节问题(4) 5.慢系统调用及EINTR 还记得前面readn和writen函数么?里面有个EINTR,现在就来谈谈这个,这个很重要. Linux世界有个叫信号 ...

  2. SOCKET网络编程细节问题3

    SOCKET网络编程快速上手(二)——细节问题(3) 3.SIGPIPE问题 人怕牺牲,我们写的程序也一样,人有死不瞑目,程序又何尝不是?程序跑着跑着,突然就崩掉了.好一点的牺牲前告诉你些打印,差点的 ...

  3. SOCKET网络编程细节问题1

    SOCKET网络编程快速上手(二)——细节问题(1) 三.细节问题一个也不能少 Socket编程说简单也简单,程序很容易就能跑起来,说麻烦还真是麻烦,程序动不动就出问题.记得刚开始写网络代码的时候,那 ...

  4. SOCKET网络编程5

    SOCKET网络编程快速上手(二)——细节问题(5)(完结篇) 6.Connect的使用方式 前面提到,connect发生EINTR错误时,是不能重新启动的.那怎么办呢,是关闭套接字还是直接退出进程呢 ...

  5. Py西游攻关之Socket网络编程

    新闻 管理   Py西游攻关之Socket网络编程   知识预览 计算机网络 回到顶部 网络通信要素: A:IP地址   (1) 用来标识网络上一台独立的主机 (2) IP地址 = 网络地址 + 主机 ...

  6. Python面向对象进阶和socket网络编程-day08

    写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...

  7. Python面向对象进阶和socket网络编程

    写在前面 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __init__(self ...

  8. Linux Socket 网络编程

    Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...

  9. Python Socket 网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

随机推荐

  1. Android - View Alpha值

    Android - View Alpha值 本文地址: http://blog.csdn.net/caroline_wendy Alpha值主要控制图像的透明度(0-1),0代表透明.1代表不透明. ...

  2. ar命令提取.a时刻,一个错误 is a fat file (use libtool(1) or lipo(1) and ar(1) on it)

    在减压.a当文件,据报一个类别似 xxx.a is a fat file (use libtool(1) or lipo(1) and ar(1) on it)的错误,经过查找资料,原来是由于该.a文 ...

  3. 再谈IE的浏览器模式和文档模式

    原文:再谈IE的浏览器模式和文档模式 以前在 “IE8兼容视图(IE7 mode)与独立IE7的区别”一文中曾经涉及过浏览器模式和文档模式,但二者的区别却不甚了了,现在有了新的认识,再补充一下. 1. ...

  4. 随记一个C的毫秒级群PING

    正好公司为了检测前台网络,力图收集有力证据与某CDN PK,所以随手写了一个群PING的程序. 写的内容比较简单,没有去特别追求线程效率,也没有去用LINUX 2.6+的殿堂级神器,以追求实现效率为主 ...

  5. EasyUI基础知识Draggable(拖累)

    学习前easyui基于解析器,装载机.对他们来说,入门阶段,我们只需要在一个简单的理解.第一阶段,不宜过深后,.接着,根据easyui订购的文件正在研究安排官方网站Draggable插入. Dragg ...

  6. PKU 1276 Cash Machine

    <span style="color:#000099;">/* Cash Machine Time Limit: 1000MS Memory Limit: 10000K ...

  7. 【翻译自mos文章】SYS_OP_C2C 导致的全表扫描(fts)/全索引扫描

    SYS_OP_C2C 导致的全表扫描(fts)/全索引扫描 參考原文: SYS_OP_C2C Causing Full Table/Index Scans (Doc ID 732666.1) 适用于: ...

  8. Hibernate实体映射配置(XML)简单三步完美配置

    我们在使用Hibernate框架的时候,非常纠结的地方就是实体和表之间的映射,今天借助汤老师的思路写了小教程,以后配置不用纠结了! 第一步:写注释 格式为:?属性,表达的是本对象与?的?关系. 例:“ ...

  9. SQL Server中存储过程比直接运行SQL语句慢的原因

    原文:SQL Server中存储过程比直接运行SQL语句慢的原因 在很多的资料中都描述说SQLSERVER的存储过程较普通的SQL语句有以下优点: 1.       存储过程只在创造时进行编译即可,以 ...

  10. Android分析应用程序的构建过程

    为了方便Android应用开发要求我们Androidproject编制和包装有了更深入的了解,例如,我们知道这是做什么的每一步,境和工具.输入和输出是什么.等等. 在前文<命令行下Android ...