11 理解tcp的有序释放操作
tcp连接的三个阶段:
- 连接建立阶段
- 数据传输阶段
- 连接拆除阶段
服务器和客户端可以通过关闭各自半边分连接来通知对方,它已完成应答了.效果上看,它们发送的就是一条EOF.
shutdown调用
int shutdown( int s, int how );
how = 0 : 关闭连接的接收端(也适用于UDP)
how = 1 : 关闭连接的发送端(后继所有试图对套接字进行写操作都会除错.将发送缓冲区中所有的数据都发送出去之后,tcp会向其对等实体发送一个FIN,通知它没数据了)
how = 2 : 两端都关闭(通常不设置此参数,但是某些winsock实现中,如果不先设置2调用shutdown,closesocket无法正常工作.
- how = 0, linux会刷新输入队列,以丢弃应用程序还未读取的所以数据,如果有新数据到达,tcp会进行ack,然后悄悄将其丢弃,因为应用程序再也无法接收数据了,windows中,如果数据在队列中,或者有新数据到达,winsock都会重置连接,因此winsock下使用shutdown(s, 0 );是不安全的.
- 在winsock中,将how设置为2调用shutdown遇到的问题与设置为0遇到额问题相同----连接可能会重置
- 调用shutdown并没有释放套接字及其资源(how被设置为0或2时可能缓冲区释放掉),同时会使的套接字的所有持有者都生效.而调用close或closesocket,不影响套接字的其他持有者
- how = 1调用shutdown,不管其他进程是否打开了这个套接字,都可以保证对等实体会收到一个EOF.而调用close或closesocket无法保证这一点,因为在套接字的引用计数减少到0之前,它都不会将FIN发送给对等实体
- 当应用程序的对等实体通过close,shutdown或terminate关闭了连接的写入侧时,select会返回一个对套接字的读事件,如果应用程序没有检查EOF状态,它可能要处理一个长度为0的段.这是因为对等实体已经关闭了连接的写入侧,而读事件实际是tcp向应用程序发送了一个来自对等实体的EOF.
注意点:
- 调用shutdown导致tcp_usrclosed的调用,如果shutdown已经调用过了,连接就处于FIN-WAIT-2状态,tcp_usrclosed会调用soisdisconnected,这个调用会完全关闭套接字,recvfrom返回0,代表EOF。
- fget在使用CTRL-D的时候,在流上做了标记,因此程序再次走到fget的时候,就不会调用read,fget就直接返回了NULL了
- 调用FD_CLR把stdin(描述符)排除在select的检查之外
- 在抓包看不出问题的时候,使用strace或ktrace查看系统调用