本节我们看一下怎样才能编写一个基于TCP稳定的客户端或者服务器程序,主要以试验抓包的方式观察数据包的变化,对网络中出现的多种情况进行分析,分析网络程序中常用的技术及它们出现的原因,在之后的编程中能早一点意识到这些潜在问题。实例代码如下: client.c 和server.c  因在试验过程中代码有所改动,本实例代码仅仅是参考。

#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#define PORT 6666
#define MAXSIZE 1024
void str_cli(FILE *, int);
int main(int argc, char *argv[])
{
if (argc != )
{
fprintf(stderr, "./client IP\n");
return ;
}
int fd = socket(AF_INET, SOCK_STREAM, );
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[], &serveraddr.sin_addr);
connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
//FILE * fp = fopen("./aa","r");
//str_cli(fp, fd);
sleep();
close(fd);
exit();
} void str_cli(FILE * fp, int fd)
{
char sendbuff[MAXSIZE], recvbuff[MAXSIZE];
bzero(sendbuff, MAXSIZE);
bzero(recvbuff,MAXSIZE);
while (fgets(sendbuff, MAXSIZE, fp) != NULL)
{
write(fd, sendbuff, strlen(sendbuff));
printf("hello\n");
/* if(read(fd, recvbuff, MAXSIZE) == 0)
{
if(errno == ECONNRESET)
{
fprintf(stderr, "reconnect\n");
}
fprintf(stderr, "server terminated!\n");
exit(1);
}*/
fputs(recvbuff, stdout);
bzero(sendbuff, MAXSIZE);
bzero(recvbuff,MAXSIZE);
}
}
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define PORT 6666
#define MAXSIZE 1024
void str_ser(int);
int main()
{
int fd = socket(AF_INET, SOCK_STREAM, );
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_aton("15.15.182.182",&serveraddr.sin_addr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT);
bind(fd,(const struct sockaddr *)&serveraddr, sizeof(serveraddr));
listen(fd, );
socklen_t len = sizeof(clientaddr);
for(;;)
{
// int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
/* if(fork() == 0)
{
close(fd);
str_ser(clientfd);
exit(0);
}
*/
// close(clientfd);
sleep();
}
return ;
} void str_ser(int clientfd)
{
char buff[MAXSIZE];
size_t n;
bzero(buff, MAXSIZE);
sleep();
while( (n = read(clientfd, buff, MAXSIZE)) > )
{ write(clientfd, buff, n);
bzero(buff, MAXSIZE);
}
if(n <= )
fprintf(stderr, "read error\n"); }

客户端遇到情形如下:

客户端连接一个没有在监听的主机:

这里必须阐述一个事实,TCP的三次握手在listen之后就可以完成,可以将accept注释掉进行测试,listen系统调用之后会建立两个队列 listen 和accept ,listen队列就是当在三次握手过程中服务器收到了客户端发送的SYN时,就会将客户端结构放入listen队列,然后向客户端回应一个SYN+ACK,  当收到客户端最后的ACK之后,就会将这个客户端相关结构放入accept队列等待accept系统调用将它从队列中取出。

若主机没有监听,客户端直接链接的话:

 CLIENT]# tcpdump -i eth0 -A -vvn tcp port  and host 192.168.179.128
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size bytes ::52.874157 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [S], cksum 0xe881 (incorrect -> 0xffde), seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<..@.@.i.........T0.
N..&......r............
H...........
::52.874425 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.128.ircu- > 192.168.179.129.: Flags [R.], cksum 0x0b16 (correct), seq , ack , win , length
E..(..@.@............
T0....N..'P.............

我的目的主机是192.168.179.128, 当来自192.168.179.129的客户端发起连接请求时,由于服务器相应端口并没有在监听,所以在收到客户端的SYN之后,紧接着就由内核发送了一个RST连接复位发送给客户端。那么客户端是否有办法知道这种事实?答案是肯定的 看一下manpage对connect的描述:

       ECONNREFUSED
No-one listening on the remote address.

当收到RST时, 客户端的connect调用会返回错误并将errno值为ECONNREFUSED,此时就可以判断处远程server并没有监听端口,此时客户端可以选择退出或者稍后重试等等。

客户端连接一个网络达不到的主机:

这里又可以分为两种情况:  1. 探测型的网络达不到ETIMEDOUT (如可能就是网络中的某个主机)   2. 已知型的网络达不到(如和客户端在同一局域网主机但是没有开机路由回馈ICMP) EHOSTUNREACH

这里随便找了一个ping长时间没有反馈的网络中的某个主机,用客户端程序连接它:

 CLIENT]# tcpdump -i eth0 -A -vvn tcp port  and host 122.155.44.22
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size bytes
::37.932632 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 122.155.44.22.ircu-: Flags [S], cksum 0x1b0a (incorrect -> 0x7163), seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<..@.@......z.,.Vn.
..........r..
.........
H.BG........
::38.934330 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 122.155.44.22.ircu-: Flags [S], cksum 0x1b0a (incorrect -> 0x6d79), seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<..@.@......z.,.Vn.
..........r..
.........
H.F1........
::40.941331 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 122.155.44.22.ircu-: Flags [S], cksum 0x1b0a (incorrect -> 0x65a2), seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<..@.@......z.,.Vn.
..........r..
.........
H.N.........
::44.949506 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 122.155.44.22.ircu-: Flags [S], cksum 0x1b0a (incorrect -> 0x55fa), seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<..@.@......z.,.Vn.
..........r..
.........
H.].........

可以看到客户端会不断尝试与服务器握手,间隔时间并不确定,  但是它并不会一直这样下去直到connet放弃  connect就会返回ETIMEDOUT。这种形式的错误路由器并不返回ICMP错误。

情况2: 这种情况会很快知道并由路由器返回ICMP错误 告知主机不可达。接下来不会再发送SYN 尝试连接。 connect返回errno == EHOSTUNREACH.

对由这两种情况下errno都可以捕捉到,但是timeout发生的时间稍微有些长,这里可以将connect设为非阻塞,利用select探测描述符是否可读可写,再getsockopt得到相应结果:

SetNONBlock();
tval.tv_sec = timeout;
tval.tv_usec = ;
fd_set wfd;
FD_ZERO(&wfd);
FD_SET(sockfd, &wfd);
int resconn = connect(sockfd, (const sockaddr *)&seraddr, sizeof(seraddr));
if(resconn == )
{
write(, "Connection success.\n",);
return true;
}
if (resconn == -)
{
if(errno == EINPROGRESS)
{
int nready = select(sockfd+, NULL, &wfd, NULL, &tval);
if(nready == -)
{
close(sockfd);
HandleError("Select");
}else if(nready == )
{
write(, "Connect Timeout!\n", );
close(sockfd);
exit();
}else
{
int err;
socklen_t len = sizeof(err);
if(FD_ISSET(sockfd, &wfd))
{
if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &len) == -)
{
close(sockfd);
HandleError("Getsockopt");
}
if(err == )
{
write(, "Connect success.\n", );
return true;
}else{
close(sockfd);
errno = err;
HandleError("Connect");
}

客户端已经连接到服务器没有发送数据 这时网络突然断了(或server主机断电):

CLIENT]# tcpdump -i eth0 -A -vvn tcp port  and host 192.168.179.128
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size bytes ::28.613040 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [S], cksum 0xe881 (incorrect -> 0x74b8), seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<c.@.@...........T4.
U.gk......r............
H...........
::28.613408 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.128.ircu- > 192.168.179.129.: Flags [S.], cksum 0x87af (correct), seq , ack , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
E..<..@.@.Ri.........
T4.. .U.gl..............
....H.......

这里可以看到 client并不知道网络中出现异常,因为它并没有发送数据,server断开网络或者断电并没有跟客户端打招呼,这里TCP协议中的保活计时器就会发现这种情况, 套接字选项中的keeplive。但是这个时间间隔很长不能及时发现,虽然这个值可以改动,一般这种情况就需要心跳检测了,这就是心跳检测出现的原因。心跳就是对于长连接而言,客户端与服务器不断的进行少量的数据交互来保证彼此的存在。(如果这时开始发送数据那么就会不断重传 下述情形)

客户端已经连接到服务器并在发送东西,这时网络断了或者server主机突然断电:

这里测试就是一个简单的Echo模式:客户端发送字符串然后服务器接收后在发送回来 如下抓包结果:

::14.139432 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.128.ircu- > 192.168.179.129.: Flags [P.], cksum 0xd461 (correct), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>.@.@.~..........       //服务器向客户端发送数据
T@..+........r.a.....
.(l.H...sdfsdfsdf ::14.139509 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [.], cksum 0xe879 (incorrect -> 0xeba2), seq , ack , win , options [nop,nop,TS val ecr ], length
E....@.@.d.........T@.       //客户端确认
......+......y.....
H....(l.
::16.140328 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xcc12), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.          //客户端再发送   这时服务器已经断开了网络
......+............
H....(l.sdfsdfsdf ::16.342204 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xcb49), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.                                              //看它的序号     
......+............
H..p.(l.sdfsdfsdf ::16.542401 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xca80), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.                                          //序号不变    接下来都是不变的
......+............
H...(l.sdfsdfsdf ::16.945409 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xc8ed), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.
......+............
H....(l.sdfsdfsdf ::17.751262 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xc5c7), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.
......+............
H....(l.sdfsdfsdf ::19.365418 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xbf79), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.
......+............
H..@.(l.sdfsdfsdf
::22.589323 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xb2e1), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>..@.@.d.........T@.
......+............
H....(l.sdfsdfsdf

这里分析结果可以看到,client后续一直再重传了,因为当server之间断开网络时,client再次发送数据 此时已经收不到server的ACK确认信息并且以后任何信息都接收不到了,client就反复的重传大约会坚持8-15min。当网络恢复正常时 又可以重新发送(TCP协议的实现)。 那么client怎么才能知道这件事呢?记得套接字选项里有sendtimeout 和recvtimeoutout 但是sendtimeout并不是指收到ACK的超时设定。那么一直收不到ACK造成重传的这个问题到底要怎么办呢?我这里试验方式如下:1. 将文件描述符改为非阻塞行不行?   2.  加上套接字选项send 超时行不行?  3. 这种情况一直send会造成EPIPE信号产生吗?  4. 什么时候才会由这个EPIPE的信号产生?

#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#define PORT 6666
#define MAXSIZE 1024
void str_cli(FILE *, int);
int main(int argc, char *argv[])
{
if (argc != )
{
fprintf(stderr, "./client IP\n");
return ;
}
int fd = socket(AF_INET, SOCK_STREAM, );
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[], &serveraddr.sin_addr);
struct timeval tv;
tv.tv_sec = ;
tv.tv_usec = ;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
FILE * fp = fopen("./aa","r");
str_cli(fp, fd);
sleep();
close(fd);
exit();
} void str_cli(FILE * fp, int fd)
{
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, O_NONBLOCK | flags);
char sendbuff[MAXSIZE], recvbuff[MAXSIZE];
bzero(sendbuff, MAXSIZE);
bzero(recvbuff,MAXSIZE);
fgets(sendbuff, MAXSIZE, fp);
while ( )
{
if(-==send(fd, sendbuff, strlen(sendbuff), ))
{
fprintf(stderr, "%s\n", strerror(errno));
}
fprintf(stderr, "p>>>>>\n");
if(read(fd, recvbuff, MAXSIZE) == )
{
if(errno == ECONNRESET)
{
fprintf(stderr, "reconnect\n");
}
fprintf(stderr, "server terminated!\n");
exit();
}
fputs(recvbuff, stdout);
// bzero(sendbuff, MAXSIZE);
bzero(recvbuff,MAXSIZE);
sleep();
}
}
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#define PORT 6666
#define MAXSIZE 1024
void str_ser(int);
int main()
{
int fd = socket(AF_INET, SOCK_STREAM, );
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_aton("15.15.182.182",&serveraddr.sin_addr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT);
bind(fd,(const struct sockaddr *)&serveraddr, sizeof(serveraddr));
listen(fd, );
socklen_t len = sizeof(clientaddr);
for(;;)
{
int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
str_ser(clientfd);
close(clientfd);
sleep();
}
return ;
} void str_ser(int clientfd)
{
char buff[MAXSIZE];
size_t n;
bzero(buff, MAXSIZE);
int num = ;
//sleep(2000);
while( (n = read(clientfd, buff, MAXSIZE)) > )
{
num++;
if (num == )
{
assert(num > );
}
write(clientfd, buff, n);
bzero(buff, MAXSIZE);
}
if(n <= )
fprintf(stderr, "read error\n"); }

1.   将文件描述符设为非阻塞.

  在断开网络后,仍然可以send,一段时间内并不会返回-1,也不会造成epipe信号产生。直到tcp已经放弃重传后,send 返回-1,errno=EHOSTUNREACH 也可能是ETIMEDOUT。如果这里使用IO复用的话,返回可读时间read也会返回-1,errno相同。

2. 加上套接字选项sendtimeout

  这种情况并不会发生send 超时的异常,这里套接字选项中的超时只是应对延迟发送的,并不能保证send在超时时间内收到来自对方的确认ACK。

3. 断开网络一直send不会产生EPIPE异常

  那什么时候才会产生这个异常信号呢?  这里在发送过程中,将server进程杀掉注意这里目标主机还能达到只是server进程死了,让client并不处理server发来的FIN 或者RST复位 client仍然再次发送数据,这时client就会返回EPIPE异常。默认情况下进程会终止,服务器常常会发生这种情况可将此信号忽略。

试验结果就是即使改为非阻塞,send也并不会马上返回错误,仍然会继续往内核缓冲区写,因为没有收到对方的确认这时会重新发送一段时间,后续如果网络恢复那么就会继续正常发送。这个重传时间为8-15min。超过这个时间那么就会产生路由发送ICMP错误 告诉client已经路由不到目的主机了  client send就会返回-1   errno = EHOSTUNREACH网络不可达的错误(也可能没有收到ICMP,返回ETIMEDOUT)。(那么还有一种情况就是重传还没有超时间,但是内核的缓冲区被我们写满了,那么send也会返回-1, errno=ENOBUFS,manpage中有这个说法)。

对于这种情况无论是客户端还是服务端都会发生,对于服务端影响还是很大的,例如如果客户端断网服务器没有发现就一直保留相关client的数据结构并不断尝试重传。这种情形在真实的网络中常有发生,对于服务器对客户端发送数据时一直重传的话那么效率必定会下降。其中心跳可以让我们发现这个问题,例如我们规定心跳包发送2min内没有收到反馈,那么就认为是对方断网了,不必一直等待重传 可将它直接close 再删除相关数据结构。那么这里还有个细节就是close时 发送FIN可能也不会得到对方确认啊!? 抓包截这看: client与服务器断开网络之后 我在client又send多次数据之后close连接。

::49.231527 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0xa82e), seq :, ack , win , options [nop,nop,TS val ecr ], length
E..>z`@.@..............
...Z..............      此处为重传 看这里还是那一个包
.?@.*.sdfsdfsdf ::07.799865 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [FP.], cksum 0xe955 (incorrect -> 0x6d95), seq :, ack , win , options [nop,nop,TS val ecr ], length
E...za@.@.............   //这里close 连接 刚才数据还在一直send 所以会有很多的数据一次被发送出去
...d........U.....
.?{..*.sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf ::15.151565 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [FP.], cksum 0xe95f (incorrect -> 0x3931), seq :, ack , win , options [nop,nop,TS val ecr ], length
E...zb@.@..(........... //重传
...Z........_.....
.?...*.sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf

再看close时网络状态转换:

这里可以看到client发送一个FIN后会立即进入FIN-WAIT-1状态,当收到ACK之后就会进入FIN-WAIT-2状态。由抓包结果FIN发送之后并没有等到确认,那么还是会重传,再看网络状态:

 CLIENT]$ netstat -an | grep
tcp 192.168.179.129: 192.168.179.128: FIN_WAIT1
unix [ ] STREAM CONNECTED
CLIENT]$ netstat -an | grep
tcp 192.168.179.129: 192.168.179.128: FIN_WAIT1
unix [ ] STREAM CONNECTED
CLIENT]$ netstat -an | grep
tcp 192.168.179.129: 192.168.179.128: FIN_WAIT1
unix [ ] STREAM CONNECTED

这里的FIN_WAIT1会维持一段时间,大约10秒左右就没有了,最后并不会进入TIME_WAIT状态,估计内核会把所有数据和状态清空。即使close掉还是造成重传,但是应用层数据已经清空了,剩下的只是内核在处理,但是如果你在close之后恰好网络又恢复了正常,最后的FIN和push都被收到的话,这种情况对端也会正常接收数据并发送FIN。

客户端已连接服务器但并没有在发送数据 server进程崩溃 server主机正常关机(LINUX):

    192.168.179.129. > 192.168.179.128.ircu-: Flags [.], cksum 0xe879 (incorrect -> 0xcb3b), seq , ack , win , options [nop,nop,TS val  ecr ], length
E....@.@.J.........Td.
.C.'/........y.....
H....B..
::15.546958 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.128.ircu- > 192.168.179.129.: Flags [F.], cksum 0x796d (correct), seq , ack , win , options [nop,nop,TS val ecr ], length
E....@.@..`.........
Td/....C.'...rym.....
.B.<H...
::15.547715 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.129. > 192.168.179.128.ircu-: Flags [.], cksum 0xe879 (incorrect -> 0x26ba), seq , ack , win , options [nop,nop,TS val ecr ], length
E....@.@.J.........Td.
.C.'/........y.....
H...B.<

由抓包结果看即使进程死了但是内核会回收资源告知已关闭。客户端或者服务器都可以当正常处理。

client正在发送数据   server进程崩溃server正常关机:

192.168.179.129. > 192.168.179.128.ircu-: Flags [P.], cksum 0xe883 (incorrect -> 0x2c64), seq :, ack , win , options [nop,nop,TS val  ecr ], length
E..>..@.@.=.........TL.
...*r..C...........
H.....7sdfsdfsdf ::25.423049 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.128.ircu- > 192.168.179.129.: Flags [.], cksum 0x3c92 (correct), seq , ack , win , options [nop,nop,TS val ecr ], length
E...@.@............
TLr..C......r<......
..1H...
::25.744845 IP (tos 0x0, ttl , id , offset , flags [DF], proto TCP (), length )
192.168.179.128.ircu- > 192.168.179.129.: Flags [F.], cksum 0x3b3f (correct), seq , ack , win , options [nop,nop,TS val ecr ], length
E...@.@............
TLr..C......r;?.....
...H...

这时也会收到断开信息,但是如果接收端对这个断开忽略掉 仍坚持send的话 那么就会造成EPIPE信号  这个信号默认情况下是终止进程,对于服务器来讲当然不希望这样,一个客户端异常关闭了服务器仍然坚持写的话就造成了服务器进程退出,在服务器程序中往往重写这个信号处理函数。服务器端也可能检测到这个文件描述符异常在将其关闭。

总结:

  宗上的几种情况都是实际网络中容易发生的,其实无论是客户端和服务器都应该注意这些细节问题,当编写服务器或者客户端时考虑这些情形才会让程序更加健壮。尤其对于服务器开发而言,这些知识都十分重要。

如何编写一个稳定的网络程序(TCP)的更多相关文章

  1. 编写一个简单的C++程序

    编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...

  2. 用C语言编写一个简单的词法分析程序

    问题描述: 用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表.如果产生词法错误,则显示错误信息.位置,并试图从错误中恢复.简单的恢复方法 ...

  3. IM服务器:编写一个健壮的服务器程序需要考虑哪些问题

    如果是编写一个服务器demo,比较简单,只要会socket编程就能实现一个简单C/S程序,但如果是实现一个健壮可靠的服务器则需要考虑很多问题.下面我们看看需要考虑哪些问题. 一.维持心跳 为何要维持心 ...

  4. Java入门篇(一)——如何编写一个简单的Java程序

    最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...

  5. 编写一个 Chrome 浏览器扩展程序

    浏览器扩展允许我们编写程序来实现对浏览器元素(书签.导航等)以及对网页元素的交互, 甚至从 web 服务器获取数据,以 Chrome 浏览器扩展为例,扩展文件包括: 一个manifest文件(主文件, ...

  6. 使用PyQt5编写一个简单的GUI程序(pyside 有 pyside-uic 把ui文件转成py文件,pyside-rcc 把qrc文件转成 py文件导入就行了)

    我做Python窗口界面编程时,经常使用PyQt进行设计.这里简单叙述一下使用PyQt5制作一个简单的图形界面的流程 PyQt的简介以及开发环境的搭建在此不多赘述. 1.       打开Qt Des ...

  7. 使用BSD socket编写Windows版的网络程序

    我们知道BSD Socket是标准的套接字规范,那么怎么在windows使用他们呢? 我们首先要引用<winsock2.h>和ws2_32.lib 然后,执行WSAStartup #ifd ...

  8. 使用PHP-GTK编写一个windows桌面应用程序

    PHP-GTK的下载地址:http://gtk.php.net/download.php?language=en-US, 猿哥选择了最新版本(beta版),可能有人会问我们为啥不选最新的stable版 ...

  9. 用python编写一个合格的ftp程序,思路是怎样的?

      经验1.一般在比较正规的类中的构造函数.都会有一个verify_args函数,用于验证传入参数.尤其是对于系统传参.2.并且系统传参,其实后面大概都是一个函数名 例如:python server. ...

随机推荐

  1. RabbitMQ-Windows单机集群搭建

    1.先安装Erlang http://www.erlang.org/downloads,安装完成后,设置环境变量: 变量名:ERLANG_HOME 变量值:D:\Program Files\erl9. ...

  2. Page visibility 页面可见性

    一直以来,判断页面是不是当前可见标签,浏览器有没有缩小都是比较麻烦的.   通过页面可见性API可以获得相关信息document.hidden  判断页面当前是不是可见的document.visibi ...

  3. Python文件夹备份

    Python文件夹备份 import os,shutil def file_copy(path1,path2): f2 = [filename1 for filename1 in os.listdir ...

  4. Socket 的理解及实例

    Socket 的理解及实例Socket 的理解TCP/IP要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Intern ...

  5. Android开发之漫漫长途 Ⅳ——Activity的显示之ViewRootImpl初探

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. 发放福利:原价135元/年的阿里云CDN流量包(500GB)免费送

    不少朋友看过本站推荐的两篇文章:1. <阿里云全民云计算活动:云服务器ECS二折起>2. <阿里云双11优惠活动-爆款云服务器> 大部分人都说不错,很快下单购买了服务器,后续使 ...

  7. js判断对象为空 JSON.stringify(obj)

    JSON.stringify(obj) : 用于从一个对象解析出字符串 var c = {}; if(JSON.stringify(obj) == "{}"){ console.l ...

  8. pwnable.kr login之write up

    main函数如下: auth函数如下: 程序的流程如下: 输入Authenticate值,并base64解码,将解码的值代入md5_auth函数中 mad5_auth()生成其MD5值并与f87cd6 ...

  9. Spring Security 4 新增特性

    1.概述 a) 特性 以下是Spring Security 4.0的新特性 Web Socket 支持 测试支持 整合Spring Data CSRF令牌参数解析 更安全的默认设置 role权限不再必 ...

  10. 采用Vue2.0开发的分页js组件

    2017-11-17 19:14:23 基于jQuery的分页插件相信大家伙已经都用过很多了,今天分享一下基于Vue2.0的分页插件pagination.js 由于项目需求,要求使用 Vue2.0 开 ...