《unix网络编程》(11)tcp服务器的几种常见状况分析的“服务器进程终止”提到客户阻塞于fgets所以没办法收到服务器发的fin,只有当客户再次输入文本并发送给服务器后才会从套接字中读取,这时才知道服务器的状态。但这可能已经过了很长时间。这样的进程就需要预先告知内核的能力,使得内核一旦发现进程指定的一个或多个i/o条件就绪(即,输入已经准备好读取,或描述符能够承接更多输出),它就通知进程。这种能力就是i/o复用(i/o mutiplexing)。该能力由select和poll支持。
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历了一段指定的时间后才唤醒它。
select函数及参数
#include
#include
int select(int maxfdp1, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//返回:若有就绪描述符则返回就绪描述符的数目,若超时返回0,若出错返回-1
参数:
(1) timeout:告知内核等待指定描述符中任何一个就绪花费的最长时间,其timeval结构用于指定秒数和微妙数。
//timeval结构:
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
使用select,设置其3个set为空指针, nfds为0, 一个非空的timeout来达到较为
精确的sleep(单位为微秒),普通的sleep单位为
秒。
timeout取值:
(1)timeout== null,仅当一个描述符准备好i/o时才返回。
(2)timeout->tv_sec == 0 && tvptr->tv_usec == 0,立即返回,称为
轮询(
五种i/o模型)。
(3) timeout->tv_sec != 0 || tvptr->tv_usec !=0, 等待特定时间长度,超时返回0;
在这段时间内如果有描述符准备好就返回。
(2)中间的三个参数:指定要让内核测试读、写、异常的描述符,若对某一个不感兴趣可置为null。
这三个参数都是socket,调用函数时,用于指定所关心的描述符的值;函数返回时,结果将指示哪些描述符已经就绪。
select使用描述符集,通常是一个整数数组,其中每个整数的一位对应一个描述符。而poll是用可变长度的结构数组,每个结构代表一个描述符。
通过fd_set的数据类型和四个宏实现:
fd_clr(int fd, fd_set *set); //关闭fd_set中的fd位
fd_isset(int fd, fd_set *set); //测试该位是否打开,如果为1则该位对应描述符就绪
fd_set(int fd, fd_set *set); //打开该fd位
fd_zero(fd_set *set); //清空所有位
如下打开描述符1、4位:
fd_set rset;
fd_zero(&rset); //清空所有,每次调用select都要清空为0
fd_set(1,&rset); //调用select将我们关心的位置为1.
fd_set(4,&rset);
(3)maxfdp1参数指定待测试的描述符的个数,其值为最大待测试描述符加1。例如上例打开1、4描述符,那么这里maxfdp1值为5。
描述符就绪条件
当某套接字发生错误,将由select标记为既可读又可写。
终止网络连接通常使用close函数。
不过close有两个限制,但可通过shutdown函数避免。
(1)close把描述符引用计数减1,仅在该计数为0时才关闭套接字。shutdown可以不管引用计数就激发tcp的正常连接终止。
(2)close终止读和写两个方向的数据传输。有时候需要告诉对端我们已经发完数据,即使对端仍有数据发送。
#include
int shutdown(int sockfd, int howto);
//成功返回0,出错返回-1
shutdown允许我们处理批量数据,典型情况:
该函数行为依赖于howto参数:
(1)shut_rd
关闭连接读这一半——套接字不再有数据接收,而且套接字接收缓冲区现有数据丢弃。不再对这样的套接字调用读函数。来自对端的数据都被确认并丢弃。
(2)shut_wr
关闭连接写这一半,这称为半关闭。当前留在套接字缓冲区内的数据将被发送掉,后跟tcp的正常连接终止序列。
(3)shut_rdwr
连接的读和写都关闭。