signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);
**
signal(SIGCHLD, SIG_IGN)和signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。(Linux Only)
对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
signal(SIGPIPE, SIG_IGN);
TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道,
但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制,
一个端点无法获知对端的socket是调用了close还是shutdown.
对一个已经收到FIN包的socket调用read方法,
如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送).
但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以,
第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.
为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数:
signal(SIGPIPE, SIG_IGN);
这样, 第二次调用write方法时, 会返回-1, 同时errno置为SIGPIPE. 程序便能知道对端已经关闭.
**
RST报文
**
我们知道TCP建立连接的时候需要三次连接,TCP释放连接的时候需要四次挥手,在这个过程中,出现了很多特殊的标志报文段,例如SYN ACK FIN,在TCP协议中,除了上面说了那些标志报文段之外,还有其他的报文段,如PUSH标志报文段以及今天需要重点讲解的RST报文段。
RST:(Reset the connection)用于复位因某种原因引起出现的错误连接,也用来拒绝非法数据和请求。如果接收到RST位时候,通常发生了某些错误;
发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓冲区中的包,发送RST;接收端收到RST包后,也不必发送ACK包来确认。
“Connection reset”的原因是服务器关闭了Connection[调用了Socket.close()方法]。大家可能有疑问了:服务器关闭了Connection为什么会返回“RST”而不是返回“FIN”标志。原因在于Socket.close()方法的语义和TCP的“FIN”标志语义不一样:发送TCP的“FIN”标志表示我不再发送数据了,而Socket.close()表示我不在发送也不接受数据了。问题就出在“我不接受数据” 上,如果此时客户端还往服务器发送数据,服务器内核接收到数据,但是发现此时Socket已经close了,则会返回“RST”标志给客户端。当然,此时客户端就会提示:“Connection reset”。
产生RST报文的几种情景
目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;
客户端发起三次握手,发送完第一个SYN分节之后,收到来自服务器的RST分节(个人理解,分节就是报文段?)。
RST分节的内容:如果收到的是ACK报文,RST取ACK报文的ACK***为RST报文的SEQ;如果报文不是ACK报文,RST的SEQ为0且ACK字段为收到的报文SEQ+报文长度;
请求超时
一个客户端连接服务器,connect返回-1并且error=EINPROGRESS。 直接telnet发现网络连接没有问题。ping没有出现丢包。用抓包工具查看,客户端是在收到服务器发出的SYN之后就莫名其妙的发送了RST。
这是为什么呢?
原因就是请求超时了。
有89、27两台主机。主机89向主机27发送了一个SYN,表示希望连接8888端口,主机27回应了主机89一个SYN表示可以连接。但是主机27却很不友好,莫名其妙的发送了一个RST表示我不想连接你了。
后来经过排查发现,在主机27上的程序在建立了socket之后,用setsockopt的SO_RCVTIMEO选项设置了recv的超时时间为100ms。而我们看上面的抓包结果表示,从主机89发出SYN到接收SYN的时间多达110ms。(从15:01:27.799961到15:01:27.961886, 小数点之后的单位是微秒)。因此主机27上的程序认为接收超时,所以发送了RST拒绝进一步接收数据。
想取消一个已存在的连接
操作系统接收到的来自TCP连接中的每一个字节,我都会让应用程序接收到。如果应用程序不接收怎么办?发送RST。
打开一个socket然后连接一个服务器并发送5000个字节。刚才我们看服务器的代码,每次只接收4096个字节,那么就是说客户端发送的剩下的4个字节服务端的应用程序没有接收到,服务器端的socket就被关闭掉,这种情况下会发生什么状况呢,还是抓包看一看。
前三行就是TCP的3次握手,从第四行开始看,客户端的49660端口向服务器的9877端口发送了5000个字节的数据,然后服务器端发送了一个ACK进行了确认,紧接着服务器向客户端发送了一个RST断开了连接。
如果想提前关闭一个连接,就需要发送RST报文段。
客户端在服务端已经关闭掉socket之后,仍然在发送数据。
假设server和client 已经建立了连接,server调用了close(Socket.close()方法的语义和TCP的“FIN”标志语义不一样:发送TCP的“FIN”标志表示我不再发送数据了,而Socket.close()表示我不在发送也不接受数据了。) 发送FIN 段给client,此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。
当一个进程向某个已收到RST的套接字执行写操作时,(此时写操作返回EPIPE错误)内核向该进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止;
TCP接收到一个根本不存在的连接上的分节;
TCP接收到一个根本不存在的连接上的分节;服务器主机崩溃后重启:它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对于所有收到的来自客户的数据分节响应一个RST;
总结出现RST报文的场景:
1.connect一个不存在的端口;
2.向一个已经关掉的连接send数据;
3.向一个已经崩溃的对端发送数据(连接之前已经被建立);
4.close(sockfd)时,直接丢弃接收缓冲区未读取的数据,并给对方发一个RST。这个是由SO_LINGER选项来控制的;
5.a重启,收到b的保活探针,a发rst,通知b。代码片
tcp中RST报文理解小结:
一、出现RST包的情况
1、连接请求到达时,目的端口不存在情况
2、向一个已经关闭的连接发送数据
3、向一个已经崩溃的对端发送数据(连接之前已经被建立)
4、请求超时。 使用setsockopt的SO_RCVTIMEO选项设置recv的超时时间。接收数据超时时,会发送RST包
5、close(sockfd)时,直接丢弃接收缓冲区未读取的数据,并给对方发一个RST。这个是由SO_LINGER选项来控制的
6、TCP收到了一个根本不存在的连接上的分节
7、.处理半打开连接, 一方关闭了连接,另一方却没有收到结束报文(如网络故障),此时另一方还维持着原来的连接。而一方即使重启,也没有该连接的任何信息。这种状态 就叫做半打开连接。而此时另一方往处于半打开状态的连接写数据,则对方回应RST复位报文
二、收到RST包后的表现
1、TCP socket在任何状态下,只要收到RST包,即可进入CLOSED初始状态
2、值得注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接
三、程序中表现
1、阻塞模型下,内核无法主动通知应用层出错,只有应用层主动调用read()或者write()这样的IO系统调用时,内核才会利用出错来通知应用层对端RST
2、非阻塞模型下,select或者epoll会返回sockfd可读,应用层对其进行读取时,read()会报错RST
以上是通过网络和书本知识总结的RST情况,后续工作中遇见相同情况会补充上去
上一篇: python装饰器原理
下一篇: Freemarker 进行字符串截取