本章我们主要讲述一下tcpip半关闭状态及可能产生的问题。
1. shutdown函数介绍
shutdown()函数调用可以关闭sockfd所关联的全双工socket连接的全部或部分。假如参数how为SHUT_RD
,则该socket将不会再接收数据;假如参数how为SHUT_WR
,则不被允许再发送数据;假如参数how为SHUT_RDWR
,则不被允许接收和发送数据。
返回值: 成功返回0;失败返回-1。
2. close()函数
close()一个socket的默认行为是把套接字标记为已关闭,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数。然而,TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP终止连接。
在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close()套接字时, 描述符引用计数就会相应的减1。当引用计数仍大于0时,这个close()系统调用就不会引发TCP的四次挥手断连过程。
3. close()与shutdown()的区别
close()与shutdown的区别主要表现在:
-
close()函数会关闭套接字ID,如果有其他进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写。并且有时候这是非常重要的,特别是对于多进程并发服务器来说。(即只是将socket fd的引用计数减1,只有当该socket fd的引用计数减至0时,TCP传输层才会发起4次挥手从而真正关闭联接)
-
shutdown()函数会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为0。那些试图读的进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号。同时可以利用shutdown的第二个参数选择断连方式。
更多关于close()与shutdown()的说明:
1) 只要TCP栈的读缓存里还有未读取(read)的数据,则调用close()时会直接向对端发送RST。
2) shutdown()与socket描述符没有关系,即使调用shutdown(fd,SHUT_RDWR)也不会关闭fd,最终还需close(fd)
3) 在已发送FIN包后write该socket描述符会引发EPIPE/SIGPIPE
4) 当有多个socket描述符指向同一socket对象时,调用close()时首先会递减该对象的引用计数,计数为0时才会发送FIN包结束TCP连接。
shutdown不同,只要以SHUT_WR/SHUT_RDWR方式调用即发送FIN包
5) SO_LINGER与close(), SO_LINGER选项开启但超时值为0时,调用close()直接发送RST(这样可以避免进入TIME_WAIT状态,但破坏了TCP协
议的正常工作方式),SO_LINGER对shutdown无影响。
6) TCP连接上出现RST与随后可能的TIME_WAIT状态没有直接关系,主动发FIN包方必然会进入TIME_WAIT状态,除非不发送FIN而直接发送RST结束连接。
ps: 产生RST的三个条件
1) 目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器;
2) TCP想取消一个已有的连接;
3) TCP接收到一个根本不存在的连接上的分节;
4. select/poll/epoll
5. 测试示例
5.1 server.c源代码
编译:
# gcc -o server server.c
5.2 client.c源代码
编译:
# gcc -o client client.c -lpthread
5.3 测试
5.3.1 测试向一个读端关闭的socket写数据
1) 开启tcpdump进行抓包
执行如下命令启动tcp抓包:
# tcpdump -i lo tcp and port 8010 -w out.pcap
2) 启动server
新开启一个窗口启动server:
# ./server
server默认会监听8010
端口.
3) 启动client
新开启一个窗口启动client,并向server端发送SHUT_RD
命令要求server执行shutdown(clientfd,SHUT_RD)
,然后再向server端发送若干条信息:
4)分析
将上述抓取到的out.pcap
包放到wireshark中分析。通过分析,我们发现向一个读端关闭的socket写数据,不会有任何问题。
在本例子中,server端
的读通道被关闭,然后client端继续向server端写数据,此时server端会正常的响应ACK,只是TCP协议栈会将接收到的数据直接丢弃。
5.3.2 补充
如果某一个socket句柄fd的写关闭:
shutdown(fd,SHUT_WR)
后续再向该句柄进行写入操作,则会产生SIGPIPE
信号。向一个读端关闭的socket进行写操作,则写入的数据都会被丢弃。如果进行tcp通信的两方,有一方检测到对端读写都已经关闭(例如:client检测到server已经关闭,但是client本身并未执行关闭操作),此时如果再向对端进行写操作同样会产生SIGPIPE
信号。
[参看]
-
Linux网络技术栈,看这篇就够了
-
Linux高性能网络编程十谈
-
linux网络编程Socket之RST详解