1. I/O多路复用基本思路
I/O多路复用就是让应用程序可以同时对多个I/O端口进行监控以判断其上的操作是否可以进行,达到时间复用的目的。由于I/O多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多。由于I/O多路复用都是在单一进程中进行的,所以不会出现多线程中的线程不安全的问题。
2. select模型
在man page中给出的select函数原型:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
函数准许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。man page中给出了一个较新的函数pselect,这个函数仅仅是多了一个sigmask参数。sigmask参数指定了在执行pselect函数时屏蔽的信号集合。
select()函数参数:
int nfds, //监控的文件描述符集里最大文件描述符加1
fd_set *readfds, //监控有读数据到达文件描述符集合
fd_set *writefds,//监控有写数据到达文件描述符集合
fd_set *exceptfds,//监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
struct timeval *timeout //定时阻塞监控时间,3种情况
/*
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
*/
另外的三个函数功能比较简单:
int FD_ISSET(int fd, fd_set *set); 测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); 把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); 把文件描述符集合里所有位清0
3. select模型回射服务器
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/select.h>
#define SERV_PORT 8000
#define MAXLINE 1024
int main()
{
//maxfd: 打开的最大文件描述符标号
//listenfd: 监听描述符
//confd: 链接描述符
//clientaddrlen 客户端地址长度
//sockfd: 暂存量
int maxfd, listenfd, confd, clientaddrlen, sockfd, n, i = 0;
struct sockaddr_in serveraddr, clientaddr; //服务器端地址,客户端地址
char str[INET_ADDRSTRLEN]; //存放IP地址,点分十进制表示
fd_set rset, allset; //select 使用
char buf[MAXLINE]; //传输数据
int nReady, client[FD_SETSIZE], maxi; //
//1. 创建一个socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//2. 绑定一个端口
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERV_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
//3. 设置监听
listen(listenfd, 20);
maxfd = listenfd;
maxi = 0;
for(i=0; i<FD_SETSIZE; ++i); //初始化clinet[]
client[i] = -1;
FD_ZERO(&allset); //select监控文件描述符集
FD_SET(listenfd, &allset);
for(;;)
{
rset = allset;
nReady = select(maxfd + 1, &rset, NULL, NULL, NULL);
if(nReady < 0) //select出错
{
perror("select err\n");
break;
}
if(FD_ISSET(listenfd, &rset)) //新链接的客户端
{
clientaddrlen = sizeof(clientaddr);
confd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
printf("ip: %s, port: %d \n",
inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)),
ntohs(clientaddr.sin_port));
for(i=0; i<FD_SETSIZE; ++i)
{
if(client[i] < 0)
{
client[i] = confd;
break;
}
}
if(i == FD_SETSIZE)
{
fputs("limited\n", stderr);
exit(1);
}
FD_SET(confd, &allset);
if(confd > maxfd) maxfd = confd;
if(i > maxi) maxi = i;
if(--nReady == 0) continue;
}
for(i=0; i<=maxi; ++i) //遍历看哪个客户端有数据就绪
{
if((sockfd = client[i]) < 0) continue;
if(FD_ISSET(sockfd, &rset))
{
if((n = read(sockfd, buf, MAXLINE)) == 0)
{
close(sockfd);
FD_CLR(sockfd, &allset);
client[i]=0;
}
else
write(sockfd, buf, n);
if(--nReady == 0)
break;
}
}
}
close(listenfd);
return 0;
}
测试用客户端代码,可使用上一篇博文中的客户端。