在传统的阻塞io模型中,服务器通常使用一个循环来不断监听端口是否有新的套接字连接。当有新的连接请求到来时,服务器会接受连接,并创建一个线程来处理该连接的后续读写操作。这种模型的问题在于,如果当前的请求没有处理完,后续的请求将被阻塞,直到前一个请求处理完成。这导致了服务器的吞吐量低下,无法处理高并发连接。
为了解决阻塞io模型的问题,人们想到了使用多线程模型,即一请求一线程。这种模型在一定程度上提高了服务器的吞吐量,因为不同的请求在不同的线程中处理,互不影响。然而,随着连接数的增加,系统中需要创建的线程数也会急剧增加,这导致了系统资源的巨大消耗。此外,线程的创建和销毁也需要一定的代价,进一步降低了系统的性能。
为了克服这些限制,io多路复用技术应运而生。它允许单个线程或进程同时监控多个io流(如网络套接字、文件描述符等),并在某个io流上有事件发生时进行通知和处理。这种方式可以显著减少系统资源的消耗,提高服务器的并发处理能力。
Reactor模型是一种设计模式,其核心思想就是将io多路复用和事件派发相结合,从而减少系统中活跃线程的数量。Reactor模式通常建立在io复用的基础上(例如epoll),这种机制可以让一个线程同时监控大量的socket连接,只有当某个socket上有数据可读/可写时才会被唤醒去处理,从而避免了传统的每个连接一个线程的开销。下表是io、事件、回调函数的关系。
io | 事件 | 回调函数 |
listenfd | EPOLLIN | accept_cb |
clientfd | EPOLLIN | recv_cb |
clientfd | EPOLLOUT | send_cb |
struct conn{
int fd;
char rbuffer[BUFFER_LENGTH];
int rlength;
char wbuffer[BUFFER_LENGTH];
int wlength;
RCALLBACK send_callback;
union{
RCALLBACK recv_callback;
RCALLBACK accept_callback;
}r_action;
};
//包含fd,读存储和写存储,三个回调函数
struct conn conn_list[CONNECTION_SIZE] ={0};
int set_event(int fd, int event, int flag){//flag非零时添加事件,为零时修改事件
if(flag){
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd,EPOLL_CTL_ADD, fd, &ev);
}else{
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd,EPOLL_CTL_MOD, fd, &ev);
}
}
//accept_cb
int even_register(int fd, int event){
conn_list[fd].fd = fd;
conn_list[fd].r_action.recv_callback = recv_cb;
conn_list[fd].send_callback = send_cb;
memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);
conn_list[fd].rlength = 0;
memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);
conn_list[fd].wlength = 0;
set_event(fd, event, 1);
}
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
printf("accept finshed: %d\n", clientfd);
if(clientfd < 0){
printf("accept errno: %d --> %s\n", errno, strerror(errno));
return -1;
}
even_register(clientfd, EPOLLIN);
return 0;
}
//recv_cb
int recv_cb(int fd){
int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
if(count == 0){
printf("client disconnect: %d\n", fd);
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}else if(count < 0){
printf("count: %d, errno: %d, %s\n",count, errno, strerror(errno));
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}
conn_list[fd].rlength = count;
printf("RECV: %s\n", conn_list[fd].rbuffer);
conn_list[fd].wlength = conn_list[fd].rlength;
memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
set_event(fd, EPOLLOUT, 0);
return count;
}
//send_cb
int send_cb(int fd){
int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
set_event(fd, EPOLLIN, 0);
return count;
}
int main(){
unsigned short port = 2000;
int sockfd = init_server(port);
epfd = epoll_create(1);
conn_list[sockfd].fd = sockfd;
conn_list[sockfd].r_action.recv_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);
while(1){
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for(i = 0;i < nready; i++){
int connfd = events[i].data.fd;
if(events[i].events & EPOLLIN){
conn_list[connfd].r_action.recv_callback(connfd);
}
if(events[i].events & EPOLLOUT){
conn_list[connfd].send_callback(connfd);
}
}
}
}
三、完整程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_LENGTH 1024
#define CONNECTION_SIZE 1024
typedef int (*RCALLBACK)(int fd);
int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);
int epfd = 0;
struct conn{
int fd;
char rbuffer[BUFFER_LENGTH];
int rlength;
char wbuffer[BUFFER_LENGTH];
int wlength;
RCALLBACK send_callback;
union{
RCALLBACK recv_callback;
RCALLBACK accept_callback;
}r_action;
};
struct conn conn_list[CONNECTION_SIZE] ={0};
int set_event(int fd, int event, int flag){
if(flag){
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd,EPOLL_CTL_ADD, fd, &ev);
}else{
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd,EPOLL_CTL_MOD, fd, &ev);
}
}
int even_register(int fd, int event){
conn_list[fd].fd = fd;
conn_list[fd].r_action.recv_callback = recv_cb;
conn_list[fd].send_callback = send_cb;
memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);
conn_list[fd].rlength = 0;
memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);
conn_list[fd].wlength = 0;
set_event(fd, event, 1);
}
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
printf("accept finshed: %d\n", clientfd);
if(clientfd < 0){
printf("accept errno: %d --> %s\n", errno, strerror(errno));
return -1;
}
even_register(clientfd, EPOLLIN);
return 0;
}
int recv_cb(int fd){
int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
if(count == 0){
printf("client disconnect: %d\n", fd);
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}else if(count < 0){
printf("count: %d, errno: %d, %s\n",count, errno, strerror(errno));
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}
conn_list[fd].rlength = count;
printf("RECV: %s\n", conn_list[fd].rbuffer);
#if 1
conn_list[fd].wlength = conn_list[fd].rlength;
memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
#endif
set_event(fd, EPOLLOUT, 0);
return count;
}
int send_cb(int fd){
int count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
set_event(fd, EPOLLIN, 0);
return count;
}
int init_server(unsigned short port){
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){
printf("bind faild: %s\n", strerror(errno));
}
listen(sockfd, 10);
printf("listen finshed: %d\n", sockfd);
return sockfd;
}
int main(){
unsigned short port = 2000;
int sockfd = init_server(port);
epfd = epoll_create(1);
conn_list[sockfd].fd = sockfd;
conn_list[sockfd].r_action.recv_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);
while(1){
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for(i = 0;i < nready; i++){
int connfd = events[i].data.fd;
if(events[i].events & EPOLLIN){
conn_list[connfd].r_action.recv_callback(connfd);
}
if(events[i].events & EPOLLOUT){
conn_list[connfd].send_callback(connfd);
}
}
}
}