Linux——高性能服务器编程——select&poll&epoll
2017-11-21 15:10
661 查看
I/O复用:
多进程、 多线程、 进程池、 线程池每一个执行序列在同一时刻只能处理一个 socket(监
听、 链接)。 以线程池为例: 如果创建 N个线程, 同一时刻只能处理N的客户连接。I/O复
用: 在一个进程或者一个线程中, 同时监听多个 socket。 当有socket上有事件发生时,
程序
才会接受数据。
select:
int n = select(int nfds, fd_set *read, fd_set *write,fd_set *except,struct timeval *timeout);
nfds:监听的最大文件描述符值 + 1;
read write except:select监听的文件描述符上的可读,可写,异常事件。
struct fd_set
{
long int fd_sets[32];
}
struct timeval
{
long tv_sec; 秒数
long tv_usec; 微秒
}
返回值: 0:超时
-1 :出错
>0:就绪文件描述符的个数
poll:
int poll(struct pollfd *fds, int nfds, int timeout);
fds:数组,数组中记录所有监听的文件描述符和关注的事件类型
struct pollfd
{
int fd; /*文件描述符*/
short events; /*注册的事件*/
short revent; /*实际发生的事件,由内核填充 */
};
nfds:数组元素个数
timeout:超时时间,单位毫秒,-1表示永远阻塞,0表示立即返回
返回值: 0:超时
-1 :出错
>0:就绪文件描述符的个数
epoll:
#include<sys/epoll.h>
int epoll_create(int size);//创建一个内核事件表,返回内核事件表的标志符id
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:内核事件表的id,epoll_create的返回值
op:操作,EPOLL_CTL_ADD,EPOLL_CTL_MOD ,EPOLL_CTL_DEL
event:事件类型
struct epoll_event
{
int events;
epoll_data_t data;
};
struct epoll_data_t
{
void *ptr;
int fd;
int u32;
int u64;
};
int epoll_wait(int epfd, struct epoll_event *revent, int maxevents, int timeout);
revents是一个用于记录内核就绪事件的数组。
三组I/O复用的函数比较:
(1)事件集:
Select的参数类型fd_set没有将文描和事件绑定,所以需要三个这种类型的参数来分别传入和输出可读,可写和异常事件;poll则修改pollfd结构体中的revents事件,event事件保持不变,这两者时间复杂度都为O(n);但是epoll在内核中维护一个事件表,提供epoll_ctl来添加,删除,修改事件。Epoll_wait的events返回就绪的事件,则只返回就绪的文描个数
Poll, epollselest最大文件描述符个数受到限制,时间复杂度O(1)
(2)最大支持文件描述符数:
Poll和epoll分别用nfds和maxevents来指定最多监听多少个文描个数和事件,两个都能达到系统最大允许的65535,select允许的最大文描个数有限制,一般不可修改。
(3)工作模式
Select和poll工作在低效的LT模式,epoll工作在高效的ET模式,epoll支持EPOLLONESHOT事件。
(4)具体实现
Select和poll采用轮询的方式,每次调用都要扫描整个注册文件描述符的集合;epoll_wait采用回调的方式。
select.c
cli.c
poll.c
结果:
多进程、 多线程、 进程池、 线程池每一个执行序列在同一时刻只能处理一个 socket(监
听、 链接)。 以线程池为例: 如果创建 N个线程, 同一时刻只能处理N的客户连接。I/O复
用: 在一个进程或者一个线程中, 同时监听多个 socket。 当有socket上有事件发生时,
程序
才会接受数据。
select:
int n = select(int nfds, fd_set *read, fd_set *write,fd_set *except,struct timeval *timeout);
nfds:监听的最大文件描述符值 + 1;
read write except:select监听的文件描述符上的可读,可写,异常事件。
struct fd_set
{
long int fd_sets[32];
}
struct timeval
{
long tv_sec; 秒数
long tv_usec; 微秒
}
返回值: 0:超时
-1 :出错
>0:就绪文件描述符的个数
poll:
int poll(struct pollfd *fds, int nfds, int timeout);
fds:数组,数组中记录所有监听的文件描述符和关注的事件类型
struct pollfd
{
int fd; /*文件描述符*/
short events; /*注册的事件*/
short revent; /*实际发生的事件,由内核填充 */
};
nfds:数组元素个数
timeout:超时时间,单位毫秒,-1表示永远阻塞,0表示立即返回
返回值: 0:超时
-1 :出错
>0:就绪文件描述符的个数
epoll:
#include<sys/epoll.h>
int epoll_create(int size);//创建一个内核事件表,返回内核事件表的标志符id
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:内核事件表的id,epoll_create的返回值
op:操作,EPOLL_CTL_ADD,EPOLL_CTL_MOD ,EPOLL_CTL_DEL
event:事件类型
struct epoll_event
{
int events;
epoll_data_t data;
};
struct epoll_data_t
{
void *ptr;
int fd;
int u32;
int u64;
};
int epoll_wait(int epfd, struct epoll_event *revent, int maxevents, int timeout);
revents是一个用于记录内核就绪事件的数组。
三组I/O复用的函数比较:
(1)事件集:
Select的参数类型fd_set没有将文描和事件绑定,所以需要三个这种类型的参数来分别传入和输出可读,可写和异常事件;poll则修改pollfd结构体中的revents事件,event事件保持不变,这两者时间复杂度都为O(n);但是epoll在内核中维护一个事件表,提供epoll_ctl来添加,删除,修改事件。Epoll_wait的events返回就绪的事件,则只返回就绪的文描个数
Poll, epollselest最大文件描述符个数受到限制,时间复杂度O(1)
(2)最大支持文件描述符数:
Poll和epoll分别用nfds和maxevents来指定最多监听多少个文描个数和事件,两个都能达到系统最大允许的65535,select允许的最大文描个数有限制,一般不可修改。
(3)工作模式
Select和poll工作在低效的LT模式,epoll工作在高效的ET模式,epoll支持EPOLLONESHOT事件。
(4)具体实现
Select和poll采用轮询的方式,每次调用都要扫描整个注册文件描述符的集合;epoll_wait采用回调的方式。
select.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <assert.h> #include <sys/select.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> void main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in ser, cli; memset(&ser, 0, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(6000); ser.sin_addr.s_addr = inet_addr("192.168.1.120"); int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser)); assert(res != -1); listen(sockfd, 5); int nfds = sockfd + 1; fd_set read;// FD_ZERO(&read); int fds[128];//记录所有要监听的文描 //memset(fds, -1, 128); int i = 0; for(;i < 128;++i) { fds[i] = -1; } fds[0] = sockfd; int max = 0; while(1) { FD_ZERO(&read);//清空 int j = 0; for(; j < 128; j++) { if(fds[j] != -1) { if(max < fds[j]) max = fds[j]; FD_SET(fds[j], &read); } } int n = select(max+1, &read, NULL, NULL, NULL); //将fds数组中的文描设置到read,write,except中 if(n < 0) { printf("error\n"); exit(0); } if(n == 0) // 超时 { printf("time out\n"); continue; } int i = 0; //循环判断fd中每个文描是否是就绪事件 for(; i < 128; ++i) { if(fds[i] == -1) continue; if(FD_ISSET(fds[i], &read)) { if(fds[i] == sockfd) { int len = sizeof(cli); int c = accept(sockfd, (struct sockaddr*)&cli, &len); int i = 0; for(; i < 128; ++i) { if(fds[i] == -1) { fds[i] = c; break; } } } else { char buff[128] = {0}; int n = recv(fds[i], buff, 128, 0); if(n <= 0) { close(fds[i]); fds[i] = -1; continue; } printf("%s\n", buff); send(fds[i], "ok", 2, 0); } } } } }
cli.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <fcntl.h> void main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); assert(sockfd != -1); struct sockaddr_in ser, cli; memset(&ser, 0, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(6000);//端口号 ser.sin_addr.s_addr = inet_addr("192.168.1.120"); int res = connect(sockfd, (struct sockaddr*)&ser, sizeof(ser)); assert(res != -1); while(1) { printf("please input: "); fflush(stdout); char buff[128] = {0}; fgets(buff, 128, stdin); if(strncmp(buff, "end", 3) == 0) { close(sockfd); break; } send(sockfd, buff, strlen(buff) - 1, 0); memset(buff, 0, 128); recv(sockfd, buff, 127, 0); printf("%s\n", buff); } }结果:
poll.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <poll.h> #define MAX 128 void Init(struct pollfd *fds, int len) { int i = 0; for(; i < len; ++i) { fds[i].fd = -1; fds[i].events = 0; } } void AddFd(struct pollfd *fds, int len, int fd) { int i = 0; for(; i < len; ++i) { if(fds[i].fd == -1) { fds[i].fd = fd; fds[i].events = POLLIN; break; } } } void main() { int listenfd = socket(AF_INET, SOCK_STREAM, 0); assert(listenfd != -1); struct sockaddr_in ser, cli; memset(&ser, 0, sizeof(ser)); ser.sin_family = AF_INET; ser.sin_port = htons(6000); ser.sin_addr.s_addr = inet_addr("192.168.1.120"); int res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser)); assert(res != -1); listen(listenfd, 5); struct pollfd fds[MAX]; Init(fds, MAX); AddFd(fds, MAX, listenfd); while(1) { int n = poll(fds, MAX, -1); assert(n != -1); if(n == 0) { printf("time out\n"); continue; } int i = 0; for(; i < MAX; ++i) { if(fds[i].fd == -1) { continue; } if(fds[i].revents & POLLIN) { int fd = fds[i].fd; if(fd == listenfd) { int len = sizeof(cli); int c = accept(fd, (struct sockaddr *)&cli, &len); assert(c != -1); printf("one client link\n"); AddFd(fds, MAX, c); } else { char buff[128] = {0}; int n = recv(fd, buff, 127, 0); if(n <= 0) { printf("client unlink\n"); close(fd); fds[i].fd = -1; fds[i].events = 0; continue; } printf("%d : %s\n", fd, buff); send(fd, "OK", 2, 0); } } } } }
结果:
相关文章推荐
- Linux下select&poll&epoll的实现原理(一)
- 几种典型的服务器网络编程模型归纳( select poll epoll)
- 高性能网络服务器编程:为什么linux下epoll是最好,Netty要比NIO.2好?
- Linux网络编程 使用epoll实现一个高性能TCP Echo服务器
- 高性能网络服务器--I/O复用 select poll epoll_wait之间的区别
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
- 高性能网络服务器--I/O复用 select poll epoll_wait之间的区别
- Linux后台网络编程中select/poll/epoll的比较分析
- 【Linux网络编程】基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型
- Linux系统编程——I/O多路复用select、poll、epoll的区别使用
- 高性能网络服务器编程:为什么linux下epoll是最好,Netty要比NIO.2好?
- Linux网络编程之socket:epoll系列函数简介,与select,poll函数的区别
- Linux网络编程--select,poll和epoll的区别
- Linux系统编程——I/O多路复用select、poll、epoll的区别使用
- 高性能编程之IO复用之[select,poll,epoll]比较
- 唯快不破:高性能网络服务器--I/O复用 select poll epoll_wait之间的区别
- 高性能网络服务器编程:为什么linux下epoll是最好,Netty要比NIO.2好?
- linux 网络编程 I/O复用 select,poll ,epoll
- 几种典型的服务器网络编程模型归纳(select poll epoll)
- Linux系统编程——I/O多路复用select、poll、epoll的区别使用