I/O 多路复用,也叫I/O多路转接。
I/O 多路复用使得程序能同时监听多个文件描述符 ,能够提高程序的性能,Linux 下实现 I/O 多路复用的
系统调用主要有 select、poll 和 epoll。
网络程序在下列情况下需要使用I/O复用技术
阻塞等待:
好处: 不占用cpu宝贵的时间片
缺点:同一时刻只能处理一个操作,效率低
解决方案
主旨思想:
1 构造一个关于文件描述符的列表,列表中放的是将要监听的文件描述符,将要监听的文件描述符添加到该列表中。
2 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或多个I/O操作时,该函数才返回。
a 这个函数是阻塞的
b 函数对文件描述符的检测的操作时由内核完成的
3 在返回是,他会告诉进程有多少(那些)描述符要进行I/O操作
// sizeof(fd_set) = 128 1024/* According to POSIX.1-2001, POSIX.1-2008 */#include /* According to earlier standards */#include #include #include int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);- 参数:- nfds : 委托内核检测的最大文件描述符的值 + 1- readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性- 一般检测读操作- 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区- 是一个传入传出参数- writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性- 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)- exceptfds : 检测发生异常的文件描述符的集合- timeout : 设置的超时时间struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};- NULL : 永久阻塞,直到检测到了文件描述符有变化- tv_sec = 0 tv_usec = 0, 不阻塞- tv_sec > 0 tv_usec > 0, 阻塞对应的时间- 返回值 :- -1 : 失败- >0(n) : 检测的集合中有n个文件描述符发生了变化 将参数文件描述符fd对应的标志位设置为0void FD_CLR(int fd, fd_set *set);// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1int FD_ISSET(int fd, fd_set *set);// 将参数文件描述符fd 对应的标志位,设置为1void FD_SET(int fd, fd_set *set);// fd_set一共有1024 bit, 全部初始化为0void FD_ZERO(fd_set *set);
fd_set结构体的定义如下:
#include<typesizes.h>
#define__FD_SETSIZE 1024
#include<sys/select.h>
#define FD_SETSIZE__FD_SETSIZE
typedef long int__fd_mask;
#undef__NFDBITS
#define__NFDBITS(8*(int)sizeof(__fd_mask))
typedef struct
{ #
ifdef__USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;
#include
#include
#include
#include
#include
#include int main() {// 创建socketint lfd = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;// 绑定bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));// 监听listen(lfd, 8);// 创建一个fd_set的集合,存放的是需要检测的文件描述符fd_set rdset, tmp;FD_ZERO(&rdset);FD_SET(lfd, &rdset);int maxfd = lfd;while(1) {tmp = rdset;// 调用select系统函数,让内核帮检测哪些文件描述符有数据int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);if(ret == -1) {perror("select");exit(-1);} else if(ret == 0) {continue;} else if(ret > 0) {// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变if(FD_ISSET(lfd, &tmp)) {// 表示有新的客户端连接进来了struct sockaddr_in cliaddr;int len = sizeof(cliaddr);int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);// 将新的文件描述符加入到集合中FD_SET(cfd, &rdset);// 更新最大的文件描述符maxfd = maxfd > cfd ? maxfd : cfd;}for(int i = lfd + 1; i <= maxfd; i++) {if(FD_ISSET(i, &tmp)) {// 说明这个文件描述符对应的客户端发来了数据char buf[1024] = {0};int len = read(i, buf, sizeof(buf));if(len == -1) {perror("read");exit(-1);} else if(len == 0) {printf("client closed...\n");close(i);FD_CLR(i, &rdset);} else if(len > 0) {printf("read buf = %s\n", buf);write(i, buf, strlen(buf) + 1);}}}}}close(lfd);return 0;
}
#include
#include
#include
#include
#include int main() {// 创建socketint fd = socket(PF_INET, SOCK_STREAM, 0);if(fd == -1) {perror("socket");return -1;}struct sockaddr_in seraddr;inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);seraddr.sin_family = AF_INET;seraddr.sin_port = htons(9999);// 连接服务器int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret == -1){perror("connect");return -1;}int num = 0;while(1) {char sendBuf[1024] = {0};sprintf(sendBuf, "send data %d", num++);write(fd, sendBuf, strlen(sendBuf) + 1);// 接收int len = read(fd, sendBuf, sizeof(sendBuf));if(len == -1) {perror("read");return -1;}else if(len > 0) {printf("read buf = %s\n", sendBuf);} else {printf("服务器已经断开连接...\n");break;}// sleep(1);usleep(1000);}close(fd);return 0;
}
poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll的原型如下:
#include<poll.h>
int poll(struct pollfd*fds,nfds_t nfds,int timeout);
参数说明
#include
struct pollfd {int fd; /* 委托内核检测的文件描述符 */short events; /* 委托内核检测文件描述符的什么事件 */short revents; /* 文件描述符实际发生的事件 */
};struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;int poll(struct pollfd *fds, nfds_t nfds, int timeout);- 参数:- fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合,传入传出参数。- nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1- timeout : 阻塞时长0 : 不阻塞-1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞>0 : 阻塞的时长- 返回值:-1 : 失败>0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
下一篇:Redis缓存优化