1. I/O多路复用(I/O多路转接)
I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的 系统调用主要有 select、poll 和 epoll。
IO多路复用图解
阻塞 BIO模型


非阻塞,忙轮询
accept() // 不断的检测客户的连接 - 不阻塞
read() // 不断的循环 - 不阻塞


IO多路转接技术 - select/poll - epoll


2. select
主旨思想:
1 .首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
2. 调用系统函数(select), 监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I\O操作时, 该函数才会返回
a. 这个函数是阻塞的
b. 函数对文件描述符的检测的操作是由内核完成
3. 在返回时, 它会告诉进程有多少(哪些)描述符要进行I/O操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); - 参数: - nfds: 委托内核的检测最大文件描述符的值 + 1 - readfds: 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性 - 一班检测读操作 - 对应的是对方发送来的数据, 因为读被的接收数据, 检测的就是读缓冲区 - 是一个传入传出参数 - writefds: 要检测的文件描述符的写的集合, 委托内核检测哪些文件描述符的写的属性 - 委托内核检测写缓存区是不是还可以写数据 (不满就可以写) - exceptfds: 检测发生异常的文件描述符的集合 - timeout: 要设置的超时时间 struct timeout { long tv_sec; long tv_usec; } - 值为 NULL: 永久阻塞, 直到检测到了文件描述符的变化 - tv_sec = 0, tv_usec: = 0 : 表示不阻塞 - tv_sec > 0, tv_usec: > 0 : 阻塞对应的时间 - 返回值: - -1: 失败 - >0 (n): 检测的集合中n个文件描述符发生了变化
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);
|


server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/time.h> #include <sys/types.h>
int main() {
int lfd = socket(AF_INET,SOCK_STREAM ,0);
if(lfd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = 0; saddr.sin_port = htons(5200); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) { perror("bind"); exit(-1); }
ret = listen(lfd, 8); if(ret == -1) { perror("listen"); exit(-1); }
fd_set rdset, tmp; FD_ZERO(&rdset); FD_SET(lfd, &rdset); int maxfd = lfd;
while (1) {
tmp = rdset;
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; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); char cliIP[16]; int cliport = ntohs(cliaddr.sin_port); inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP)); printf("client IP : %s, prok %d\n", cliIP, cliport); if(cfd == -1) { perror("accept"); exit(-1); }
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]; memset(buf, 0, sizeof(buf)); int len = read(i, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed ... ...\n"); FD_CLR(i, &rdset); close(i); } else if(len > 0) { printf("read buf = %s\n", buf); write(i, buf, sizeof(buf)); } } }
}
}
close(lfd);
return 0; }
|
client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h>
int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(5200); inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr); int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret == -1) { perror("connect"); exit(-1); }
char recvBuf[1024] = {0}; char data[1024]; while (1) { memset(data, 0, sizeof(data)); fgets(data, sizeof(data), stdin); write(fd, data, strlen(data)); int readLen = read(fd, recvBuf, sizeof(recvBuf)); if(readLen == -1) { perror("read"); exit(-1); } else if(readLen > 0) { printf("recv server data : %s\n", recvBuf); } else if(readLen == 0) { printf("server closed ... ...\n"); break; } }
close(fd);
return 0; }
|
3.poll
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <poll.h> 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个文件描述符发生变化
|


poll_server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/time.h> #include <sys/types.h> #include <poll.h>
int main() {
int lfd = socket(AF_INET,SOCK_STREAM ,0); printf(" --- %d ---\n", lfd);
if(lfd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = 0; saddr.sin_port = htons(5200); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) { perror("bind"); exit(-1); }
ret = listen(lfd, 8); if(ret == -1) { perror("listen"); exit(-1); }
const int MAX_N = 1024; struct pollfd fds[MAX_N]; for(int i = 0; i < MAX_N; ++i) { fds[i].fd = -1; fds[i].events = POLLIN; } fds[0].fd = lfd; int nfds = 0;
while (1) { ret = poll(fds, nfds + 1, -1); if(ret == -1) { perror("poll"); exit(-1); } else if(ret == 0) { continue; } else if(ret > 0) { if(fds[0].revents & POLLIN) { struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); char cliIP[16]; int cliport = ntohs(cliaddr.sin_port); inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP)); printf("client IP : %s, prok : %d fd : %d\n", cliIP, cliport, cfd); if(cfd == -1) { perror("accept"); exit(-1); }
for(int i = 1; i < MAX_N; i++) { if(fds[i].fd != -1) continue; fds[i].fd = cfd; fds[i].events = POLLIN; break; } nfds = nfds > cfd ? nfds : cfd; }
for(int i = 1; i <= nfds; i++) { if(fds[i].revents & POLLIN) { char buf[1024]; memset(buf, 0, sizeof(buf)); int len = read(fds[i].fd, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed ... ...\n"); fds[i].fd = -1; close(fds[i].fd); } else if(len > 0) { printf("read buf = %s\n", buf); write(fds[i].fd, buf, sizeof(buf)); } } }
}
}
close(lfd);
return 0; }
|
client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h>
int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(5200); inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr); int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret == -1) { perror("connect"); exit(-1); }
char recvBuf[1024] = {0}; char data[1024]; while (1) { memset(data, 0, sizeof(data)); fgets(data, sizeof(data), stdin); write(fd, data, strlen(data)); int readLen = read(fd, recvBuf, sizeof(recvBuf)); if(readLen == -1) { perror("read"); exit(-1); } else if(readLen > 0) { printf("recv server data : %s\n", recvBuf); } else if(readLen == 0) { printf("server closed ... ...\n"); break; } }
close(fd);
return 0; }
|
4. epoll
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <sys/epoll.h>
int epoll_create(int size); - 参数: size : (以前是使用哈希实现的需要用到size)目前没有意义了。随便写一个数,必须大于0 - 返回值: -1 : 失败 > 0 : 文件描述符,操作epoll实例的 typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
struct epoll_event { uint32_t events; epoll_data_t data; };
常见的Epoll检测事件: - EPOLLIN - EPOLLOUT - EPOLLERR ... ...
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); - 参数: - epfd : epoll实例对应的文件描述符 - op : 要进行什么操作 EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除 - fd : 要检测的文件描述符 - event : 检测文件描述符什么事情
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); - 参数: - epfd : epoll实例对应的文件描述符 - events : 传出参数,保存了发送 了变化的文件描述符的信息 - maxevents : 第二个参数结构体数组的大小 - timeout : 阻塞时间 - 0 : 不阻塞 - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - > 0 : 阻塞的时长(毫秒) - 返回值: - 成功,返回发送变化的文件描述符的个数 > 0 - 失败 -1
|
epoll_server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <sys/epoll.h>
int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(5200); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) { perror("bind"); exit(-1); }
ret = listen(lfd, 5);
if(ret == -1) { perror("listen"); exit(-1); }
int epfd = epoll_create(100);
struct epoll_event epev; epev.events = EPOLLIN; epev.data.fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
const int MAX_N = 1e3; struct epoll_event epevs[MAX_N];
while(1) {
ret = epoll_wait(epfd, epevs, MAX_N, -1); if(ret == -1) { perror("epoll_wait"); exit(-1); }
printf("ret = %d \n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *) &cliaddr, &len); epev.events = EPOLLIN; epev.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
char cliIP[16]; int cliport = ntohs(cliaddr.sin_port); inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP)); printf("client IP : %s, prok %d\n", cliIP, cliport);
} else { char buf[1024]; memset(buf, 0, sizeof(buf)); int len = read(curfd, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed ... ...\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL); close(curfd); } else if(len > 0) { printf("read buf = %s\n", buf); write(curfd, buf, sizeof(buf)); } } }
}
close(lfd); close(epfd);
return 0; }
|
client.c 点我向上
Epoll 的工作模式:
假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
b.用户只读了一部分数据,epoll会通知
c.缓冲区的数据读完了,不通知
LT(level - triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket。在这 种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操 作。如果你不作任何操作,内核还是会继续通知你的。
假设委托内核检测读事件 -> 检测fd的读缓冲区
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
b.用户只读了一部分数据,epoll不通知
c.缓冲区的数据读完了,不通知
ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述 符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪, 并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述 符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成 未就绪),内核不会发送更多的通知(only once)。 ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写 操作把处理多个文件描述符的任务饿死。
1 2 3 4 5 6 7 8 9
| struct epoll_event { uint32_t events; epoll_data_t data; }; 常见的Epoll检测事件: - EPOLLIN - EPOLLOUT - EPOLLERR - EPOLLET
|
epoll_LT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <sys/epoll.h>
int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(5200); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) { perror("bind"); exit(-1); }
ret = listen(lfd, 5);
if(ret == -1) { perror("listen"); exit(-1); }
int epfd = epoll_create(100);
struct epoll_event epev; epev.events = EPOLLIN; epev.data.fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
const int MAX_N = 1e3; struct epoll_event epevs[MAX_N];
while(1) {
ret = epoll_wait(epfd, epevs, MAX_N, -1); if(ret == -1) { perror("epoll_wait"); exit(-1); }
printf("ret = %d \n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *) &cliaddr, &len); epev.events = EPOLLIN; epev.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); char cliIP[16]; int cliport = ntohs(cliaddr.sin_port); inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP)); printf("client IP : %s, prok %d\n", cliIP, cliport); memset(cliIP, 0, sizeof(cliIP));
} else { char buf[5] = {0}; int len = read(curfd, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed ... ...\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL); close(curfd); } else if(len > 0) { printf("read buf = %s\n", buf); write(curfd, buf, sizeof(buf)); } } }
}
close(lfd); close(epfd);
return 0; }
|
epoll_ET
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h>
int main() {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1) { perror("socket"); exit(-1); }
struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(5200); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) { perror("bind"); exit(-1); }
ret = listen(lfd, 5);
if(ret == -1) { perror("listen"); exit(-1); }
int epfd = epoll_create(100);
struct epoll_event epev; epev.events = EPOLLIN; epev.data.fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
const int MAX_N = 1e3; struct epoll_event epevs[MAX_N];
while(1) {
ret = epoll_wait(epfd, epevs, MAX_N, -1); if(ret == -1) { perror("epoll_wait"); exit(-1); } printf("ret = %d \n", ret);
for(int i = 0; i < ret; i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *) &cliaddr, &len);
int flag = fcntl(cfd, F_GETFL); flag |= O_NONBLOCK; fcntl(cfd, F_SETFL, flag);
epev.events = EPOLLIN | EPOLLET; epev.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); char cliIP[16]; int cliport = ntohs(cliaddr.sin_port); inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP)); printf("client IP : %s, prok %d\n", cliIP, cliport); memset(cliIP, 0, sizeof(cliIP));
} else {
int len = 0; char buf[5] = {0}; while((len = read(curfd, buf, sizeof(buf))) > 0) { write(STDOUT_FILENO, buf, len); write(curfd, buf, len); }
if(len == 0) { printf("client closed ... ...\n"); } else if(len == -1) { if(errno == EAGAIN) { printf("data over ... ...\n"); } else { perror("read"); exit(-1); } }
} }
}
close(lfd); close(epfd);
return 0; }
|