I/O复用——进程需要有预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(输入已经准备好被读取,或者描述符已能承担更多的输出),它就通知内核
更为先进的机制:让进程在一串事件上等待(如轮询设备)
select函数
该函数允许进程指示内核等待多个事情中的任何一个发生,并只在有一个或者多个事件发生或者经历一段指定的时间后才唤醒它。
#include <sys/select.h>
#include <sys/time.h>
//返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
timeout:它告知内核等待所指定描述符中的任何一个就绪话多长时间
timeval结构用于指定这段时间的秒数和微秒数
struct timeval{
long tv_sec;
long tv_usec;
}
这个参数有3种可能:
1)永远等待下去:仅在有一个描述符准备好I/O时才返回。为此,我们把该参数设置为空指针。
2)等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
3)根本不等待:检查描述符后立即返回,这称为轮询(polling)。为此,该参数必须指向timeval结构,而且其中的定时器必须为0(秒数和微秒数都要)。
前两种情形的等待通常会被进程在等待期间捕获的信号中断,并从信号处理函数返回。
timeout参数的const限定词表示它在函数返回时不会被select修改,如果需要知道函数返回时剩余的秒数,需要在函数调用前后分别获取系统时间,相减即为所需(任何健壮的程序都得考虑系统时间可能在这段时间内偶尔会被管理员或ntpd之类守护进程调整)。
中间三个参数readset,writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符(可以均设置为空,这样就成了一个比sleep精度更高的定时器),目前支持的异常条件有两个:
1)某个套接字的带外数据的到达。
2)某个已置分组模式的伪终端存在可从其主端读取的状态控制信息。
select使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述符。
void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
void FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
例子,定义fd_set变量,然后打开描述符1、4和5的对应位:
fd_set reset;
FD_ZERO(&reset);
FD_SET(1, &reset);
FD_SET(4, &reset);
FD_SET(5, &reset);
FD_SETSIZE常值是数据类型fd_ser中的描述符总数,其值通常是1024。
maxfdp1参数指定待测试描述符个数,它的值是待测试的最大描述符加1,描述符0,1,2,…,一直到maxfdp1 - 1均将被测试(描述符从0开始)。
两个常见编程错误:
I)忘了对最大描述符加1;
II)忘了描述符集是值-结果参数(会导致调用selcect时描述符集内我们认为是1 的位被置为0,因此每次调用select函数时都得再次把所有描述符集内关心的位均置1);
描述符就绪条件
(1)满足下列4个条件中的任何一个,一个套接字准备好读。
a)该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小(TCP与UDP默认值为1)。
b)该连接的读半部关闭(接收到了FIN的TCP连接)。对这样套接字的读操作将不阻塞并返回0(也就是返回EOF).
c)该套接字是一个监听套接字且已完成的连接数不为0。这样的套接字accept通常不会阻塞。
d)其上有一个套接字错误待处理。
(2)满足下列4个条件中的任何一个,一个套接字准备好写。
a)该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小(TCP与UDP默认值2048),且或者该套接字已连接或者该套接字不需要连接。
b)该连接的写半部关闭。对这样套接字的写操作将产生SIGPIPE信号。
c)使用非阻塞式connect的套接字已建立连接,或者connect已经以失败告终。
d)其上有一个套接字错误待处理
(3)如果一个套接字存在带外数据或者仍处于带外标记,那么它有异常条件待处理。
shutdown函数
#include <sys/socket.h>
//成功返回0,出错则为-1
int shutdown(int sockfd, int howto);
howto参数取值:
SHUT_RD 关闭连接的读这一半——套接字不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃,进程不能再对这样的套接字调用任何读函数,对一个TCP套接字调用shutdown函数后,由该套接字接收的来自对端的任何数据都会被确认,然后悄然丢弃
SHUT_WR 关闭连接的写这一半——对于TCP套接字这称为半关闭,当前留在套接字发送缓冲区中的数据将被发送掉,后跟TCP的正常终止序列,不管套接字的引用计数是否为0,这样的写半部关闭照样执行
SHUT_RDWR 连接的读半部和写半部都关闭
pselect函数
#include <sys/select.h>
#include <signal.h>
#include <time.h>
//返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_set *sigmask);
相对于select函数的两个变化
1)pselect使用timespec结构
struct timespec{
time_t tv_sec;/* seconds */
long tv_nsec;/* nanoseconds */
}
2)增加了第六个参数:一个指向信号掩码的指针。该参数允许程序先禁止递交某些信号,再测试由这些当前被禁止信号的信号处理函数设置的全局变量,然后调用pselect,告诉它重新设置信号掩码。
poll函数
#include <poll.h>
//返回:若有就绪的描述符则为其数目,若超时则为0,若出错则为-1
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
struct pollfd{
int fd; /* descripter to check */
short events; /* events of interest on fd */
short revents; /* events that occurred on fd */
}
timeout参数指定poll函数返回前等待多长时间,它是一个指定应等待毫秒数的正值。
高级I/O函数
套接字超时
1)调用alarm,它在指定超时期满时产生SIGALRM信号。
2)在select中阻塞I/O(select有内置的时间限制),以此代替直接阻塞在read或write调用上。
3)使用较新的SO_RCVTIMEO和SO_SNDTIMEO套接字选项。
标准函数read和write变体函数
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
struct iovec
{
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
}
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
struct msghdr
{
void *msg_name; /* protocol address */
socklen_t msg_namelen; /* size of protocol address */
struct iovec *msg_iov; /* scatter/gather array */
int msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data (cmsghdr struct) */
socklen_t msg_controllen; /* length of ancillary data */
int msg_flags; /* flags returned by recvmsg */
}