1.多路复用基础知识
(1)阻塞和非阻塞,同步和异步
阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作在没有接收完数据或者没有得到结果之前不 会返回,需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完 成。
同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成 后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册 的回调函数。
(2)IO多路复用
IO多路复用:IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数 可以避免同步非阻塞IO模型中轮询等待的问题,此外poll、epoll都是这种模型。在该种模式下,用户首先将需要进行IO操作的 socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起 read请求,读取数据并继续执行。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加 监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理 多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处 理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
2.实现函数
(1)select多路复用
select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时 间后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行相应的处理。
(2)poll多路复用
poll()的机制与 select() 类 似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件 描述符数量的限制(但是数量过大后性能也是会下降)。
(3)epoll多路复用
select和poll多路复用的缺点
单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫 描文件描述符,文件描述符数量越多,性能越差。
内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销。
select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件。
select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调 用还是会将这些文件描述符通知进程。
epoll的实现
int epoll_create(int size);//创建epoll实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev)//修改epoll的兴趣列表
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);//事件等待
3.实现代码
服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <pthread.h>
#include <getopt.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/resource.h>
#define MAX_EVENTS 512
#define MSG_STR "Hello, you have connected to the server successfully!\n"
void set_socket_rlimit();
void set_socket_rlimit()
{
struct rlimit limit = {0};
getrlimit(RLIMIT_NOFILE, &limit);
limit.rlim_cur = limit.rlim_max;
setrlimit(RLIMIT_NOFILE, &limit);
printf("set socket open fd max count to %d\n", limit.rlim_max);
}
int main(int argc, char **argv)
{
int listen_fd = -1, connect_fd = -1;
int listen_port = 8889;
int daemon_run = 0;
int i, j;
char buf[1024];
struct sockaddr_in server_addr;
int rv = 0;
int on = 1;
int epoll_fd;
struct epoll_event event;
struct epoll_event event_array[MAX_EVENTS];
int events;
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("socket() creates a TCP socket unsuccessfully : %s", strerror(errno));
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(listen_port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
{
printf("bind() binds the TCP socket unsuccessfully : %s, strerror(errno)");
return -2;
}
if(listen(listen_fd, 64) < 0)
{
printf("listen() listens the port unsuccessfully : %s, strerror(errno)");
return -3;
}
set_socket_rlimit();
if ( daemon_run )
{
daemon(0, 0);
}
if ((epoll_fd = epoll_create(MAX_EVENTS)) < 0)
{
printf("epoll_create() creates epoll_fd unsuccessfully");
return -4;
}
event.events = EPOLLIN|EPOLLET;
event.data.fd = listen_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0)
{
printf("epoll add listen socket unsuccessfully : %s\n", strerror(errno));
return -5;
}
while(1)
{
events = epoll_wait(epoll_fd, event_array, MAX_EVENTS, -1);
if (events < 0)
{
printf("epoll failure : %s\n", strerror(errno));
break;
}
else if (events == 0)
{
printf("epoll get timeout\n");
continue;
}
for(i = 0; i < events; i++)
{
if((event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP))
{
printf("event_array[i] get error on fd[%d] : %s\n", event_array[i].data.fd, strerror(errno));
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
}
if(event_array[i].data.fd == listen_fd)
{
if((connect_fd = accept(listen_fd, (struct sockaddr *) NULL, NULL)) < 0)
{
printf("accept new client unsuccessfully : %s\n", strerror(errno));
continue;
}
event.data.fd = connect_fd;
event.events = EPOLLIN|EPOLLET;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, connect_fd, &event) < 0)
{
printf("epoll add client socket unsuccessfully : %s\n", strerror(errno));
close(event_array[i].data.fd);
continue;
}
printf("epoll add new client socket[%d] successfully!\n\n", connect_fd);
}
else
{
memset(buf, 0, sizeof(buf));
if((rv = read(event_array[i].data.fd, buf, sizeof(buf))) <=0)
{
printf("socket[%d] read unsuccessfully!\n", event_array[i].data.fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
continue;
}
else
{
printf("socket[%d] read %d bytes data\n%s\n", event_array[i].data.fd, rv, buf);
if( write(event_array[i].data.fd, MSG_STR, strlen(MSG_STR)) < 0 )
{
printf("socket[%d] write failure: %s\n", event_array[i].data.fd, strerror(errno));
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
close(event_array[i].data.fd);
}
}
}
}
}
close(listen_fd);
return 0;
}