第六章 I/O复用: select和poll函数 - ㊣来者犹可追
来源:百度文库 编辑:神马文学网 时间:2024/04/27 15:01:27
第六章 I/O复用: select和poll函数 6.1 Introduction
前面的例子了客户端要同时处理标准输入和TCP socket 当输入阻塞时不能立即处理socket 所以需要采用I/O复用技术
以下几个方面需要用到I/O复用技术:
1.一个客户同时处理多个描述符(如前所述)
2.客户同时处理多个socket
3.TCP服务器同时处理监听端口和连接端口
4.服务器同时处理TCP和UDP
5.服务器同时处理多个服务或多个协议
6.2 I/O Models
以UDP为例说明
阻塞式I/O模型
recvfrom()直到收到数据并复制给进程缓冲后返回 或者返程错误
非阻塞式I/O模型
当I/O操作请求不能立即完成时 不使进程进入休眠 而是立即返回错误信息
前4个recvfrom()因数据未到而直接返回了EWOULDBLOCK
应用程序在一个循环中反复这样调用 称为polling 这很占CPU时间
I/O复用模型
需要用到select()或poll()
如select()会在数据准备好之后返回而后调用recvfrom()
这个过程看似与阻塞式区别不大 但是在有多个描述符存在的情况下是很有利的!
信号驱动I/O模型
sigaction()建立一个信号处理函数 当即得到返回 直到数据到达后内核发送SIGIO信号给进程
随后recvfrom()
异步I/O模型
由POSIX定义 但一般系统都未将之实现 其与信号驱动I/O相比是在数据复制完成后再返回调用
前4个模型都可以成为是同步I/O
6.3 select Function
高知内核等待某一或某些事件发生 而后唤醒进程 或超时返回
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *excepset,
const struct timeval *timeout); 返回准备好的描述符数量 超时返0错误-1
struct timeval{
long tv_sec; 秒
long tv_usec; 毫秒
};
1.该参数用以指明等待的时间:
a.永远等待 设置NULL指针
b.不等待立即返回 2个元素都设为0
c.等待一定时间 具体设置之
2.并不十分精确 最大精度10ms 但强于sleep()的1s
3.因为有const修饰符 所以该值不会被修改 若要计算时间2次调用时间函数求差时
readset writeset excepset用以标示我们需要内核进行检测读写错误的描述符
1.excepset只有两种情况 一个是out-of-band 另一个书上没详细说
2.用以组整数数组来标识 每一位对应一个描述符 如果array[0]指示0~31个描述符 常用4个宏进行操作
void FD_ZERO(fd_set *fdset); 清除所有位
void FD_SET(int fd, fd_set *fdset); 设置位
void FD_CLR(int fd, fd_set *fdset); 关闭位
void FD_ISSET(int fd, fd_set *fdset); 查询该位是否设置
3.任何一个XXXset设为NULL表示对该状态不关心 全NULL可以代替sleep()使用
4.value-result参数 送时表明哪些描述符需要检测 返回时表明哪些描述符准备完毕
maxfdp1意思是最大描述符号加1
FD_SETSIZE定义fd_set描述符数量 但通常过大所以用这个参数减小系统负担
何时描述符准备完毕
书上列举很详细 比较容易想到 不记了
6.4 str_cli Function (Revisited)
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE];
FD_ZERO(&rset); 先要清空所有描述符
for(;;) {
FD_SET(fileno(fp), &rset); 设置描述符 fileno()将标准I/O指针转为对应描述符
FD_SET(sockfd, &rset);
maxfdp1=max(fileno(fp), sockfd)) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd, &rset) { socket可读
if(Readline(sockfd, recvline, MAXLINE) ==0)
err_quit(...);
Fputs(recvline, stdout);
}
if(FD_ISSET(fileno(fp), &rset) { 标准I/O可读
if(Fgets(sendline, MAXLINE, fp) == NULL)
return;
Writen(sockfd, sendline, strlen(sendline);
}
}
}
该改进程序处理如下情况:
1.对方TCP发数据 本方socket可读 read()返回大于0值
2.对方TCP发FIN 本方socket可读 read()返回0即EOF
3.对方TCP发RST 本方socket可读 read()返回-1并由errno指明错误
6.5 Batch Input and Buffering
时刻7和8时候在网络上的报文流向
在前一节改进的str_cli程序中 当输入数据都完成后系统得到EOF并一路返回到main() 而这时并没有完成程序的所有功能 没有接受到返回消息
所以在大批输入和缓冲的情况下 仍要改进程序 方法是半关闭TCP连接shutdown()
发送FIN告知对方 本方已没有数据要发送 但socket仍开放等待读取
6.6 shutdown Function
函数功能上一节已经描述了
int shutdown(int sockfd, int howto); OK返0错误-1
howto有三个选择(括号里表示定义的数值):
SHUT_RD(0) : 连接的读功能关闭-socket不会收到任何数据 当前接受缓冲数据亦被丢弃 读函数无效 再接受到的数据会被ACK然后直接丢弃
SHUT_WR(1) : 连接的写功能关闭-当前发送缓冲数据会被发送 然后进入半关闭流程 写函数无效
SHUT_RDWR(2): 如同RD WR同时使用
6.7 str_cli Function (Revisited Again)
void str_cli(FILE *fp, int sockfd)
{
** int maxfdp1, stdineof;
fd_set rset;
** char buf[MAXLINE];
** int n;
stdioof = 0;
FD_ZERO(&rset);
for(;;) {
if(stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1=max(fileno(fp), sockfd)) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd, &rset) {
if(
if(stdioeof == 1) 仅当输入EOF关闭后才可能退出程序
return;
else
err_quit(...);
}
Writen(fileno(stdout), buf, n);
}
if(FD_ISSET(fileno(fp), &rset) {
if((n=Read(fileno(fp), buf, MAXLINE) ==0) {
stdioeof=1;
Shutdown(sockfd, SHUT_WR); 半关闭
FD_CLR(fileno(fp), &ret); 输入设备关闭描述符
continue;
}
Writen(sockfd, buf, n);
}
}
stdineof的作用 仅当输入确认完成后置1 随后才可能退出socket
注意程序中只用到read() write() 因为select()可以代替readn writen的作用
6.8 TCP Echo Server (Revisited)
第五章中的服务器程序对每一个客户开启一个进程 这一节的程序仅使用一个进程 用select()处理不同的客户
举一个实例:
Figure 6.15
1.服务器仅有一个监听socket时候的TCP数据结构
当服务器在前台运行 rset中描述符0 1 2分别针对标准输入 输出 错误设备 所以第一个可分配给监听socket的描述符号是3
另外还有一个数值型client[] 标明分配给每个客户的连接socket描述符 初始化为-1
2.新连接accept()后返回描述符4 后一个连接返回5
rset相应位置置1 client[]置描述符号
3.第一个连接关闭 rset相应位置置0 client[]置-1
注意! maxfd值没有改变
int main(int argc, char **argv)
{
** int i, maxi, maxfd, listenfd, connfd, sockfd;
** int nready, client[FD_SETSIZE];
ssize_t n;
** fd_set rset, allset;
char buf[MAXLINE];
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd=Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s.addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
** maxfd=listenfd; 记录最大的描述名号
maxi =-1; 记录client[]用到的最大数目
for(i=0; i< FD_SETSIZE; i++)
client=-1; 初始-1
FD_ZERO(&allset);
FD_SET(listenfd, &allset); 仅关注listenfd操作
for(;;) {
** rset=allset;
nready=Select(maxfd+1, &rset, NULL, NULL, NULL);
if(FD_ISSET(listenfd, &rset) { 有新连接接收
clilen=sizeof(cliaddr);
connfd=Accept(listenfd, (SA*)&cliaddr, &clilen);
for(i=0; iif(client<0) {
client=connfd;
break;
}
if(i==FD_SETSIZE) 连接过多退出
err_quit(...)
FD_SET(connfd, &allset); 新增关注的描述符
if(connfd>maxfd) 是否增加一个新单元
maxfd=connfd;
if(i>maxi)
maxi=i;
if(--nready<=0) 当没有可读描述符了
continue;
}
for(i=0; i<=maxi; i++) { 检查所以客户的数据请求
if((sockfd=client < 0)
continue;
if(FD_ISSET(sockfd, &rset) {
if((n=Read(sockfd,buf, MAXLINE)) == 0) {
Close(sockfd);
FD_CLR(sockfd, &allset);
client=-1;
} else
Writen(sockfd, buf, n);
if(--nready<=0) 当没有可读描述符了
break;
}
}
}
}
这个版本还存在的问题是DOS拒绝服务
某客户发送一个字节后进入休眠 服务器在下次进入Read()会被阻塞 (不是很明白!!!)
所以结论是 在处理多客户时 服务器绝不能调用只于一个客户相关的函数 否则被被阻塞而不能提供其他客户服务
6.9 pselect Function
以后会有例子 见到再说
6.10 poll Funtion
poll()功能同select() 但能更好处理流设备
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); 返回值同select()
1.struct pollfd {
int fd; 需要检查的描述符名
short events; 检测的情况
short revents; 检测后返回的情况
}
Figure 6.23
2.poll()定义了三个类别: 普通 优先带 高优先 这些词汇来自于基于流的系统实现 以后有更详细解释
3.nfds记录了pollfd{}里的元素数
4.timeout的设置
INFTIM 永远等待
0 立即返回
>0 一段时间 以ms为单位(没这么精确而以)
5.不需要处理某个描述符的时候 只要将fd置负数events就会被忽略而revents会被填0
6.11 TCP Echo Server (Revisited Again)
poll()的一个实例 不需要waitpid()了
int main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd=Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s.addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
client[0].fd=listenfd; 第一个元素赋给listenfd
client[0].events=POLLRDNORM;
for(i=1; iclient[i].fd=-1;
maxi=0;
for(;;) {
nready=Poll(client, maxi+1, INFTIM);
if(client[0].revents & POLLRDNORM) { 新连接
clilen=sizeof(cliaddr);
connfd=Accept(listenfd, (SA*)&cliaddr, &clilen);
for(i=1; iif(client[i].fd < 0) {
client[i].fd=connfd;
break;
}
if(i == OPEN_MAX) 连接过多
err_quit(...)
client[i].events=POLLRDNORM; 等待poll()检测
if(i>maxi)
maxi=i;
if(--nready<=0)
continue;
}
for(i=1; i<=maxi; i++) { 检测所有客户
if((sockfd=client[i].fd) < 0)
continue;
if(client[i].revents & (POLLRDNORM | POLLERR) {
if((n=read(sockfd, buf, MAXLINE)) < 0) {
if(errno==ECONNRESET) { 连接RST
Close(sockfd);
client[i].fd=-1;
}else
err_sys(...) 读错误
}else if(n==0) { 客户关闭连接
Close(sockfd);
client[i].fd=-1;
}else
Writen(sockfd, buf, n);
if(--nready <= 0)
break;
}
}
}
}
前面的例子了客户端要同时处理标准输入和TCP socket 当输入阻塞时不能立即处理socket 所以需要采用I/O复用技术
以下几个方面需要用到I/O复用技术:
1.一个客户同时处理多个描述符(如前所述)
2.客户同时处理多个socket
3.TCP服务器同时处理监听端口和连接端口
4.服务器同时处理TCP和UDP
5.服务器同时处理多个服务或多个协议
6.2 I/O Models
以UDP为例说明
阻塞式I/O模型
recvfrom()直到收到数据并复制给进程缓冲后返回 或者返程错误
非阻塞式I/O模型
当I/O操作请求不能立即完成时 不使进程进入休眠 而是立即返回错误信息
前4个recvfrom()因数据未到而直接返回了EWOULDBLOCK
应用程序在一个循环中反复这样调用 称为polling 这很占CPU时间
I/O复用模型
需要用到select()或poll()
如select()会在数据准备好之后返回而后调用recvfrom()
这个过程看似与阻塞式区别不大 但是在有多个描述符存在的情况下是很有利的!
信号驱动I/O模型
sigaction()建立一个信号处理函数 当即得到返回 直到数据到达后内核发送SIGIO信号给进程
随后recvfrom()
异步I/O模型
由POSIX定义 但一般系统都未将之实现 其与信号驱动I/O相比是在数据复制完成后再返回调用
前4个模型都可以成为是同步I/O
6.3 select Function
高知内核等待某一或某些事件发生 而后唤醒进程 或超时返回
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *excepset,
const struct timeval *timeout); 返回准备好的描述符数量 超时返0错误-1
struct timeval{
long tv_sec; 秒
long tv_usec; 毫秒
};
1.该参数用以指明等待的时间:
a.永远等待 设置NULL指针
b.不等待立即返回 2个元素都设为0
c.等待一定时间 具体设置之
2.并不十分精确 最大精度10ms 但强于sleep()的1s
3.因为有const修饰符 所以该值不会被修改 若要计算时间2次调用时间函数求差时
readset writeset excepset用以标示我们需要内核进行检测读写错误的描述符
1.excepset只有两种情况 一个是out-of-band 另一个书上没详细说
2.用以组整数数组来标识 每一位对应一个描述符 如果array[0]指示0~31个描述符 常用4个宏进行操作
void FD_ZERO(fd_set *fdset); 清除所有位
void FD_SET(int fd, fd_set *fdset); 设置位
void FD_CLR(int fd, fd_set *fdset); 关闭位
void FD_ISSET(int fd, fd_set *fdset); 查询该位是否设置
3.任何一个XXXset设为NULL表示对该状态不关心 全NULL可以代替sleep()使用
4.value-result参数 送时表明哪些描述符需要检测 返回时表明哪些描述符准备完毕
maxfdp1意思是最大描述符号加1
FD_SETSIZE定义fd_set描述符数量 但通常过大所以用这个参数减小系统负担
何时描述符准备完毕
书上列举很详细 比较容易想到 不记了
6.4 str_cli Function (Revisited)
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE];
FD_ZERO(&rset); 先要清空所有描述符
for(;;) {
FD_SET(fileno(fp), &rset); 设置描述符 fileno()将标准I/O指针转为对应描述符
FD_SET(sockfd, &rset);
maxfdp1=max(fileno(fp), sockfd)) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd, &rset) { socket可读
if(Readline(sockfd, recvline, MAXLINE) ==0)
err_quit(...);
Fputs(recvline, stdout);
}
if(FD_ISSET(fileno(fp), &rset) { 标准I/O可读
if(Fgets(sendline, MAXLINE, fp) == NULL)
return;
Writen(sockfd, sendline, strlen(sendline);
}
}
}
该改进程序处理如下情况:
1.对方TCP发数据 本方socket可读 read()返回大于0值
2.对方TCP发FIN 本方socket可读 read()返回0即EOF
3.对方TCP发RST 本方socket可读 read()返回-1并由errno指明错误
6.5 Batch Input and Buffering
时刻7和8时候在网络上的报文流向
在前一节改进的str_cli程序中 当输入数据都完成后系统得到EOF并一路返回到main() 而这时并没有完成程序的所有功能 没有接受到返回消息
所以在大批输入和缓冲的情况下 仍要改进程序 方法是半关闭TCP连接shutdown()
发送FIN告知对方 本方已没有数据要发送 但socket仍开放等待读取
6.6 shutdown Function
函数功能上一节已经描述了
int shutdown(int sockfd, int howto); OK返0错误-1
howto有三个选择(括号里表示定义的数值):
SHUT_RD(0) : 连接的读功能关闭-socket不会收到任何数据 当前接受缓冲数据亦被丢弃 读函数无效 再接受到的数据会被ACK然后直接丢弃
SHUT_WR(1) : 连接的写功能关闭-当前发送缓冲数据会被发送 然后进入半关闭流程 写函数无效
SHUT_RDWR(2): 如同RD WR同时使用
6.7 str_cli Function (Revisited Again)
void str_cli(FILE *fp, int sockfd)
{
** int maxfdp1, stdineof;
fd_set rset;
** char buf[MAXLINE];
** int n;
stdioof = 0;
FD_ZERO(&rset);
for(;;) {
if(stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1=max(fileno(fp), sockfd)) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd, &rset) {
if(
if(stdioeof == 1) 仅当输入EOF关闭后才可能退出程序
return;
else
err_quit(...);
}
Writen(fileno(stdout), buf, n);
}
if(FD_ISSET(fileno(fp), &rset) {
if((n=Read(fileno(fp), buf, MAXLINE) ==0) {
stdioeof=1;
Shutdown(sockfd, SHUT_WR); 半关闭
FD_CLR(fileno(fp), &ret); 输入设备关闭描述符
continue;
}
Writen(sockfd, buf, n);
}
}
stdineof的作用 仅当输入确认完成后置1 随后才可能退出socket
注意程序中只用到read() write() 因为select()可以代替readn writen的作用
6.8 TCP Echo Server (Revisited)
第五章中的服务器程序对每一个客户开启一个进程 这一节的程序仅使用一个进程 用select()处理不同的客户
举一个实例:
Figure 6.15
1.服务器仅有一个监听socket时候的TCP数据结构
当服务器在前台运行 rset中描述符0 1 2分别针对标准输入 输出 错误设备 所以第一个可分配给监听socket的描述符号是3
另外还有一个数值型client[] 标明分配给每个客户的连接socket描述符 初始化为-1
2.新连接accept()后返回描述符4 后一个连接返回5
rset相应位置置1 client[]置描述符号
3.第一个连接关闭 rset相应位置置0 client[]置-1
注意! maxfd值没有改变
int main(int argc, char **argv)
{
** int i, maxi, maxfd, listenfd, connfd, sockfd;
** int nready, client[FD_SETSIZE];
ssize_t n;
** fd_set rset, allset;
char buf[MAXLINE];
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd=Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s.addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
** maxfd=listenfd; 记录最大的描述名号
maxi =-1; 记录client[]用到的最大数目
for(i=0; i< FD_SETSIZE; i++)
client=-1; 初始-1
FD_ZERO(&allset);
FD_SET(listenfd, &allset); 仅关注listenfd操作
for(;;) {
** rset=allset;
nready=Select(maxfd+1, &rset, NULL, NULL, NULL);
if(FD_ISSET(listenfd, &rset) { 有新连接接收
clilen=sizeof(cliaddr);
connfd=Accept(listenfd, (SA*)&cliaddr, &clilen);
for(i=0; i
client=connfd;
break;
}
if(i==FD_SETSIZE) 连接过多退出
err_quit(...)
FD_SET(connfd, &allset); 新增关注的描述符
if(connfd>maxfd) 是否增加一个新单元
maxfd=connfd;
if(i>maxi)
maxi=i;
if(--nready<=0) 当没有可读描述符了
continue;
}
for(i=0; i<=maxi; i++) { 检查所以客户的数据请求
if((sockfd=client < 0)
continue;
if(FD_ISSET(sockfd, &rset) {
if((n=Read(sockfd,buf, MAXLINE)) == 0) {
Close(sockfd);
FD_CLR(sockfd, &allset);
client=-1;
} else
Writen(sockfd, buf, n);
if(--nready<=0) 当没有可读描述符了
break;
}
}
}
}
这个版本还存在的问题是DOS拒绝服务
某客户发送一个字节后进入休眠 服务器在下次进入Read()会被阻塞 (不是很明白!!!)
所以结论是 在处理多客户时 服务器绝不能调用只于一个客户相关的函数 否则被被阻塞而不能提供其他客户服务
6.9 pselect Function
以后会有例子 见到再说
6.10 poll Funtion
poll()功能同select() 但能更好处理流设备
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); 返回值同select()
1.struct pollfd {
int fd; 需要检查的描述符名
short events; 检测的情况
short revents; 检测后返回的情况
}
Figure 6.23
2.poll()定义了三个类别: 普通 优先带 高优先 这些词汇来自于基于流的系统实现 以后有更详细解释
3.nfds记录了pollfd{}里的元素数
4.timeout的设置
INFTIM 永远等待
0 立即返回
>0 一段时间 以ms为单位(没这么精确而以)
5.不需要处理某个描述符的时候 只要将fd置负数events就会被忽略而revents会被填0
6.11 TCP Echo Server (Revisited Again)
poll()的一个实例 不需要waitpid()了
int main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd=Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr);
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s.addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
client[0].fd=listenfd; 第一个元素赋给listenfd
client[0].events=POLLRDNORM;
for(i=1; i
maxi=0;
for(;;) {
nready=Poll(client, maxi+1, INFTIM);
if(client[0].revents & POLLRDNORM) { 新连接
clilen=sizeof(cliaddr);
connfd=Accept(listenfd, (SA*)&cliaddr, &clilen);
for(i=1; i
client[i].fd=connfd;
break;
}
if(i == OPEN_MAX) 连接过多
err_quit(...)
client[i].events=POLLRDNORM; 等待poll()检测
if(i>maxi)
maxi=i;
if(--nready<=0)
continue;
}
for(i=1; i<=maxi; i++) { 检测所有客户
if((sockfd=client[i].fd) < 0)
continue;
if(client[i].revents & (POLLRDNORM | POLLERR) {
if((n=read(sockfd, buf, MAXLINE)) < 0) {
if(errno==ECONNRESET) { 连接RST
Close(sockfd);
client[i].fd=-1;
}else
err_sys(...) 读错误
}else if(n==0) { 客户关闭连接
Close(sockfd);
client[i].fd=-1;
}else
Writen(sockfd, buf, n);
if(--nready <= 0)
break;
}
}
}
}
第六章 I/O复用: select和poll函数 - ㊣来者犹可追
第六章 I/O复用: select和poll函数
select, poll和epoll的区别 - linux系统调用函数 - Embedded and Linux
select, poll和epoll的区别
select poll使用
select函数
socket之select函数
Linux中select函数的使用
I/O端口与I/O内存”
文件I/O
主机I/O总线
Javascript操作Select和Option
俄罗斯:权力中的黑社会 第六章(I)
俄罗斯:权力中的黑社会 第六章(I)
安装Gforge - 往者不可鉴来者犹可追 - CSDNBlog
Improving (network) I/O performance
趣解六种Socket I/O模型
Notes on Asynchronous I/O
winsock中的I/O模型
winsock中的I/O模型
I/O接口的作用
系统总线、I/O总线区别
同步I/O(阻塞I/O),异步I/O(非阻塞) - Linux/Unix - xxdbup...
设置 DB2 和 AIX 与条带技术匹配以提高 I/O 性能