SOCKET网络编程细节问题(2)
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)的更多相关文章
- SOCKET网络编程细节问题(4)
SOCKET网络编程快速上手(二)——细节问题(4) 5.慢系统调用及EINTR 还记得前面readn和writen函数么?里面有个EINTR,现在就来谈谈这个,这个很重要. Linux世界有个叫信号 ...
- SOCKET网络编程细节问题3
SOCKET网络编程快速上手(二)——细节问题(3) 3.SIGPIPE问题 人怕牺牲,我们写的程序也一样,人有死不瞑目,程序又何尝不是?程序跑着跑着,突然就崩掉了.好一点的牺牲前告诉你些打印,差点的 ...
- SOCKET网络编程细节问题1
SOCKET网络编程快速上手(二)——细节问题(1) 三.细节问题一个也不能少 Socket编程说简单也简单,程序很容易就能跑起来,说麻烦还真是麻烦,程序动不动就出问题.记得刚开始写网络代码的时候,那 ...
- SOCKET网络编程5
SOCKET网络编程快速上手(二)——细节问题(5)(完结篇) 6.Connect的使用方式 前面提到,connect发生EINTR错误时,是不能重新启动的.那怎么办呢,是关闭套接字还是直接退出进程呢 ...
- Py西游攻关之Socket网络编程
新闻 管理 Py西游攻关之Socket网络编程 知识预览 计算机网络 回到顶部 网络通信要素: A:IP地址 (1) 用来标识网络上一台独立的主机 (2) IP地址 = 网络地址 + 主机 ...
- Python面向对象进阶和socket网络编程-day08
写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...
- Python面向对象进阶和socket网络编程
写在前面 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __init__(self ...
- Linux Socket 网络编程
Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...
- Python Socket 网络编程
Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...
随机推荐
- crawler_phantomjs_windows_linux下demo
1. phantomjs介绍 基于Javascript驱动的命令行webkit引擎,轻量级,安装简单,开发快速,渲染速度较快,无界面的webkit浏览器. phontomjs跟一般浏览器一样可以加载网 ...
- 通过私有协议Chrome浏览器页面打开本地程序
近期方有这样的要求:这两个系统,根据一组Chrome开展,根据一组IE开展,需要Chrome添加一个链接,然后进入IE该系统的开发.这,需要Chrome跳转到创建一个链接IE浏览器指定的页面.同时也实 ...
- 深入理解PHP中赋值与引用
原文:深入理解PHP中赋值与引用 先看下面的问题: <?php $a = 10;//将常量值赋给变量,会为a分配内存空间 $b = $a;//变量赋值给变量,是不是copy了一份副本,b也分配了 ...
- 利用webBrowser获取框架内Html页面内容
原文:利用webBrowser获取框架内Html页面内容 利用webBrowser获取页面比较简单,MSDN下有示例,在这里不必多说. 可是一些 HTML 文档由“框架”构成,或可以存放它们自己独特 ...
- bootstrap-wysiwyg 结合 base64 解码 .net bbs 图片操作类 (三) 图片裁剪
官方的例子 是 长方形的. 我这里 用于 正方形的头像 所以 做如下 修改 #preview-pane .preview-container { width: 73px; height: 73px ...
- C++中public,protected,private访问
对于公有继承方式: (1)父类的public成员成为子类的public成员,允许类以外的代码访问这些成员:(2)父类的private成员仍旧是父类的private成员,子类成员不可以访问这些成员:(3 ...
- MVC验证12-使用DataAnnotationsExtensions对整型、邮件、最小值、文件类型、Url地址等验证
原文:MVC验证12-使用DataAnnotationsExtensions对整型.邮件.最小值.文件类型.Url地址等验证 本文体验来自http://dataannotationsextension ...
- hibernate之使用Annotation注解搭建项目
之前开发都是使用xml配置来开发项目,开发起来特别繁琐 大家会发现通过注解大大简化了我们开发流程,使我们从繁琐的XML配置中解放出来. 第一步:新建一个javaweb项目.并将hibernate需要的 ...
- selenium2入门 用Yaml文件进行元素管理 (五)
比如界面有一个按钮,id号是test.如果进行对象化的话,就是test.click就可以了.不用每次都要去创建test对象.如果id号变了,我们也只需要改一下test的名称就行了. 使用Yaml需要用 ...
- VS2015集成新潮工具4
VS2015集成新潮工具(四) 本课程来源与微软connect视频教程,Modern Web Tooling in Visual Studio 2015 本课程主要讲下当下流行的前端工具 bowe ...