如何编写一个稳定的网络程序(TCP)
本节我们看一下怎样才能编写一个基于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)的更多相关文章
- 编写一个简单的C++程序
编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...
- 用C语言编写一个简单的词法分析程序
问题描述: 用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表.如果产生词法错误,则显示错误信息.位置,并试图从错误中恢复.简单的恢复方法 ...
- IM服务器:编写一个健壮的服务器程序需要考虑哪些问题
如果是编写一个服务器demo,比较简单,只要会socket编程就能实现一个简单C/S程序,但如果是实现一个健壮可靠的服务器则需要考虑很多问题.下面我们看看需要考虑哪些问题. 一.维持心跳 为何要维持心 ...
- Java入门篇(一)——如何编写一个简单的Java程序
最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...
- 编写一个 Chrome 浏览器扩展程序
浏览器扩展允许我们编写程序来实现对浏览器元素(书签.导航等)以及对网页元素的交互, 甚至从 web 服务器获取数据,以 Chrome 浏览器扩展为例,扩展文件包括: 一个manifest文件(主文件, ...
- 使用PyQt5编写一个简单的GUI程序(pyside 有 pyside-uic 把ui文件转成py文件,pyside-rcc 把qrc文件转成 py文件导入就行了)
我做Python窗口界面编程时,经常使用PyQt进行设计.这里简单叙述一下使用PyQt5制作一个简单的图形界面的流程 PyQt的简介以及开发环境的搭建在此不多赘述. 1. 打开Qt Des ...
- 使用BSD socket编写Windows版的网络程序
我们知道BSD Socket是标准的套接字规范,那么怎么在windows使用他们呢? 我们首先要引用<winsock2.h>和ws2_32.lib 然后,执行WSAStartup #ifd ...
- 使用PHP-GTK编写一个windows桌面应用程序
PHP-GTK的下载地址:http://gtk.php.net/download.php?language=en-US, 猿哥选择了最新版本(beta版),可能有人会问我们为啥不选最新的stable版 ...
- 用python编写一个合格的ftp程序,思路是怎样的?
经验1.一般在比较正规的类中的构造函数.都会有一个verify_args函数,用于验证传入参数.尤其是对于系统传参.2.并且系统传参,其实后面大概都是一个函数名 例如:python server. ...
随机推荐
- (function($){...})(jQuery)和$(document).ready(function(){}) 的区别
(function($){...})(jQuery) 实际上是执行()(para)匿名函数,只不过是传递了jQuery对象. 立即执行函数:相当于先申明一个函数,声明完后直接调用: 用于存放开发 ...
- java7大排序算法
1.冒泡排序 package lizicong; import java.util.Scanner; public class BubbleSort { /* * 属于交换排序:稳定 * 排序原理:相 ...
- 【前端GUI】——网站设计的重要知识点总结&思维导图(一)
前言:网页美术设计具有四大特点,分别为交互性.整合性.多维性以及动态性.完整的网页设计既需要试听元素,也需要版式设计,以求有效的传达信息.在设计的时候,设计者要学会利用框架,也要学会打破框架. 一.优 ...
- 利用PowerShell 得到 进程总共占用的内存
$task = tasklist /nh /fo csv $total = 0 for($i=0; $i -lt $task.count; $i++) { $one = $task[ $i ].Spl ...
- javaCountDownLatch闭锁
package com.java.concurrent; import java.util.concurrent.CountDownLatch; /** * CountDownLatch: 闭锁,在完 ...
- CSS浮动(Float)
定义 浮动会使元素向左或向右移动,其周围的元素也会重新排列: 浮动直到它的外边缘碰到包含框或者另一个浮动框才停止: 浮动之后的元素将围绕它,浮动之前的元素不变: 由于浮动框不在文档的普通流中,所以文档 ...
- robotframework自动化系列:随机下拉框
robotframework自动化系列:随机下拉框 随着项目自动化不断推进,在下拉框定位的时候出现些问题,每次下拉框选择都是相同的下拉选项,如果想每次选择的选项不一样,该如何实现呢,查找了很多资料,没 ...
- 使用原生JavaScript的Canvas实现拖拽式图形绘制,支持画笔、线条、箭头、三角形、矩形、平行四边形、梯形以及多边形和圆形,不依赖任何库和插件,有演示demo
前言 需要用到图形绘制,没有找到完整的图形绘制实现,所以自己实现了一个 - - 一.实现的功能 1.基于oop思想构建,支持坐标点.线条(由坐标点组成,包含方向).多边形(由多个坐标点组成).圆形(包 ...
- WebSocket小插件
一.WebSocket小介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信 ...
- c#控件攻略宝典之ListBox控件
ListBox控件的使用: 1)控件属性 Items SelectedItems SelectioModes 2)数据绑定 DataSoure DisplayMember ValueMenber 3) ...