分类: LINUX
2013-05-24 17:54:39
一、close
close函数原型如下:
int close(int sockfd);
参数sockfd为套接字描述符,成功返回0,失败返回-1。
错误码errno:EBADF表示一个非有效描述符;EINTER表示被信号中断;EIO表示一个IO错误。
该函数的功能是关闭套接字描述符引用计数,当计数大于0时什么都不干,当计数等于0时触发TCP/IP的四次挥手,即主动关闭方发送FIN。
比如:在多进程服务器中,父子进程共享套接字描述符,其引用计数为父进程数+子进程数的和,当父进程或其中某个子进程调用close函数时,描述符计数减一,当所有进程都调用close函数后,引用计数减到0,触发TCP/IP四次挥手过程。描述符计数c = 父进程数+子进程数,当c==0,TCP/IP触发四次挥手过程,每一个父进程或子进程调用一次close,描述符计数减1.
如果在调用close以前,TCP协议栈的发送队列中有已排队等候发送的数据,则协议栈尝试将数据发送出去,发送完毕后根据套接字描述引用计数来决定是否关闭连接。
如果在调用close以后,且套接字描述符引用计数c==0,则在其上调用write或者read函数则会产生错误码为9即EBADF的错误。
需要注意的是:调用close函数时,TCP协议栈对发送队列中已排队等候发送数据的处理流程受SO_LINGER选项的影响,该选项关联的数据结构如下:
struct linger{
int l_onoff; //0=off,nonzero=on
int l_linger; //linger time
};
l_onoff是选项开关,如果是0则关闭选项并且忽略参数l_linger,如果是非零则打开选项,默认是0;l_linger是延迟关闭连接时间,只有l_onoff打开时即为非零时,该参数的值才有效。这几个参数与close时TCP协议栈对待发数据的处理流程如下表:
表1 待发数据处理流程
l_onoff |
l_linger |
close行为 |
发送队列 |
TCP协议栈 |
零 |
忽略 |
立即返回 |
保持直至发送完成 |
接管套接字并保证将数据发送至对端 |
非零 |
零 |
立即返回 |
立即放弃 |
直接发送RST,自身立即复位,不用经过2MSL状态,对端收到复位错误码 |
非零 |
非零 |
阻塞直到l_linger时间超时或数据发送完成(套接字必须设置为阻塞) |
在超时时间段内保持尝试发送,若超时则立即放弃 |
超时则同第二种情况,若发送完成则皆大欢喜 |
二、 shutdown
shutdown函数原型如下:
int shutdown(int sockfd, int howto);
参数sockfd为套接字描述符,howto取值如下:
(1) SHUT_RD:值为0,关闭连接的读功能。
(2) SHUT_WR:值为1,关闭连接的写功能。
(3) SHUT_RDWR:值为2,先关闭连接的读功能,再关闭连接的写功能。
成功返回0,错误返回-1。
错误码errno:EBADF表示一个非有效描述符;ENOTCONN表示在该描述符上未连接;ENOTSOCK表示是一个文件描述符而非套接字描述符。
shutdown提供了将四次挥手的过程拆分开的可能,需要注意的是,如果调用shutdown时填参数SHUT_WR或者SHUT_RDWR,然后调用write函数则会引发EPIPE/SIGPIPE,因为shutdown以这两个为参数时会发送FIN。
三、区别
1、是否触发四次挥手
close:只是减少套接字描述符的计数,如果计数为0,则触发四次挥手。
shutdown:拆分四次挥手过程,在设置howto参数为SHUT_WR或者SHUT_RDWR时,会立即发送FIN。
2、多进程共享描述符
close:只要描述符计数不为0,没有调用过该函数的进程仍然可以正常收发数据。
shutdown:无论描述符计数是多少,只要任一进程调用该函数都会破坏所有进程的连接,任一进程在该描述符上读取数据都会收到EOF结束符,写数据时会收到SIGPIPE信号。
四、使用场景
服务器上要建立很多子进程,不能用shutdown()只能用close(),客户端可以用shutdown(),也可以用close()。服务器端,每侦听到一个连接就会创建一个子进程,因此是多个子进程的,shutdown会关闭服务器侦听工作,也会关闭其他正在通信的客户端。客户端可以使用shutdown(),因为客户端和服务器端一般只有一条连接,可以使用shutdown()。当客户端与服务器端有多条连接且是在同一个进程中,最好用close(),以免影响到其他连接通信。