服务器开发中的错误处理与连接关闭
- RST包和IO函数
- 收到RST包的情况
RST包即复位包,一般在连接发生错误的时候由传输层发送的一个包,R ST用来异常的关闭连接。发送RST包关闭连接时,不必等待缓冲区的数据都发出去(普通的close会先将发送缓冲区中的数据发送完毕后再发送fin包),直接就丢弃缓冲区的包发送RST包,而接收端收到RST后,也不必发送ACK确认。常见的收到RST包的情况有以下几种:
- 端口未打开,如果服务器端口未打开而客户端来连接。S->C
- 客户端请求连接超时。C->S
- 提前关闭,当内核读缓冲区中还有数据就关闭连接时,关闭连接的一方会向另一方发送RST
- 在一个已经关闭的socket上收到数据,例如如果服务器端在已经关闭掉socket之后,仍然在发送数据。这时服务器会产生RST
https://russelltao.iteye.com/blog/1405349
-
- 收到RST包后的处理
应用程序使用的API必须提供产生异常关闭而不是正常关闭的手段,即API函数返回相应的错误。那么API函数应该怎样去处理这个错误呢?它们又该返回怎么样的错误呢?
- read函数收到RST包后,会返回-1,并设置errno为ECONNRESET。
- 如果对端连接已经关闭,那么在调用write函数时会正常返回,因为write只是将数据复制到发送缓冲区中,所以会正常返回,当发送方将数据发送给对方时,对端会响应一个RST,这时如果再写则write会返回-1,并设置errno为EPIPE错误,同时内核会向该进程发送一个SIGPIPE信号。
- 即如果进程去读写一个已经收到RST的套接字都会发送错误。
- epoll的事件触发类型
2.1 shutdown函数触发
在muduo库中通过shutdown去主动关闭当前的连接写操作,需要注意一个思维误区,当shutdown服务器端的写后,只是服务器端不可以再发送数据了(不能在应用层发送数据,即关闭了写缓冲区,但是ack还是可以继续发的),但是需要注意的是服务器端还是可以继续接收数据的,如果客户端继续发送数据,那么服务器端还是可以继续读取的此时触发的是EPOLLIN,但是如果客户端调用close关闭了连接,那么客户端会发送FIN包,则此时的服务器端会触发EPOLLIN+EPOLLDHUP+EPOLLHUP。(如果此时不将该fd从epoll中移除其将一直触发这三个事件)。
我对TCP连接的理解如下:
当客户端通过三次握手和服务器建立连接后,两端之间建立两条连接即C->S和S->C,,,其中C->S负责客户端向服务器发送数据和服务器向客户端发送ack,而S->C负责服务器向客户端发送数据和客户端向服务器发送ack。
当服务器调用shutdown关闭写时,S->C的连接将会发生关闭。此时服务器不会再向客户端写数据,但是C->S的连接仍然存在,此时客户端还可以向服务器写数据。
当服务器调用write关闭连接时,S->C的连接会关闭,但是注意虽然此时的C->S还没有关闭但是如果C->S写数据的话,服务器端会向客户端发送RST。
2.2 epoll与RST触发
如果对端发送了RST给本端,本端的epoll对RST给出的反应是触发EPOLLIN+EPOLLHUP+EPOLLERR
https://blog.csdn.net/halfclear/article/details/78061771?utm_source=blogxgwz8
三muduo库中的连接关闭和错误处理方法
-
- muduo库中的连接关闭
muduo库中的连接关闭分为主动关闭和被动关闭以及错误关闭三类(不管怎么关闭都要保证最后将此连接移除),,主动关闭只有shutdown写这一种方法,当用户主动关闭了连接后,服务器将关闭写操作一方,客户端还可以照常向服务器进行写操作,如果客户端一直不调用read来处理shutdown发来的FIN包,那么将一直存在此半连接状态。但是如果一旦read被调用将返回0,从而客户端主动close连接,此时客户端向服务器发送FIN包,服务器的epoll将对此事件触发EPOLLIN+EPOLLHUP事件,服务器的handleRead函数处理此事件返回0,从而将连接移除,最后将此连接彻底移除,
第二种连接的关闭方法是被动关闭,客户端主动发起连接关闭操作,然后epoll触发EPOLLIN事件,调用handleRead函数read返回0,从而关闭移除连接。
第三种是如果连接发生错误导致的连接的关闭。对于epoll而言什么样的事件类型叫错误事件??即EPOLLERR+EPOLLHUP。当epoll触发了这两个中的其中一个都属于连接发生错误(shutdown写后出现的情况不算)。当epoll上被触发了这两种事件后,服务器该采取怎样的操作呢??我在看muduo库源码的时候,我一直很好奇,为什么服务器对于错误的情况都只是调用一个handleError函数将错误信息打印到日志中,但是并没有去关闭和移除此出错的连接。在经过我的测试后,我发现其实EPOLLERR和EPOLLHUP都是和EPOLLIN同时出现的,也就是说这两个错误的类型并不会单独出现。所以由于它们和EPOLLIN一起出现那么在handleRead函数中的read操作会返回0(注意,read如果直接读取RST包会返回-1,但是如果经过epoll接收到RST后触发的EPOLL那么read将返回0),当read返回0后服务器将会调用handleClose函数,从而将连接进行关闭和移除。同时由于handleEvent函数中的if不是采用的if-else模式(为什么不是,我的另外一篇文章中会将)所以handleError操作也会被触发,从而也会将连接出错的相关信息输出到日志当中。(其实EPOLLHUP和EPOLLERR有时候并不是成对出现的,但是它们只要出现一个就意味着连接有错误产生,所以muduo将这两个事件类型放在了一起进行考虑)。另外为了防止出现只有EPOLLHUP而没有EPOLLIN的情况出现(EPOLLERR出现的时候EPOLLHUP也一定会出现所以只考虑EPOLLHUP即可),muduo库还增加了一个if语句。用于处理这种特殊情况,此时muduo直接调用handleClose函数关闭移除连接。
例如如果客户端连接已经关闭,而服务器端仍然用send或者EPOLLOUT向对端发送数据,那么对端将发送RST包,从触发epoll上的EPOLLIN+EPOLLERR+EPOLLHUP,从而开始关闭移除连接和错误处理。
-
- muduo库中的错误处理
对于muduo库而言,有哪些地方需要进行错误处理呢,,常见的就是四种错误,一个是accept错误,这种错误直接写到日志里面就行,不用采取别的措施,一个是read错误,同样对于read返回的错误也是直接写到日志中,对于write的错误需要具体处理(另外一篇文章会详述),对于连接上的错误则需要关闭和移除连接,同时写到日志中。如果只是普通的连接错误那么只用将错误信息写到日志中即可。