本文将介绍一个使用Linux的epoll API实现的简单网络服务器,展示如何高效地处理多个套接字连接。这个服务器模型利用了非阻塞IO和事件驱动的设计理念,以支持高并发的网络请求处理。
使用epoll实现高效的事件处理
Linux的epoll是一种高效的事件处理系统,它可以在数千甚至数万的并发连接中迅速响应IO事件,而不会引起性能瓶颈。与传统的select或poll模型相比,epoll更加适合于高负载环境,因为它不需要在每次调用时重新传递整个监听列表。
网络服务器的基本架构
在我们的示例中,服务器启动后首先创建一个套接字,绑定到指定的端口,并监听来自客户端的连接请求。一旦有新的连接请求,服务器就会接受连接并将新的客户端套接字注册到epoll实例以进行进一步的事件监听。
服务器对每个套接字连接都有对应的结构体conn
,该结构体存储了文件描述符、接收和发送缓冲区、缓冲区长度和回调函数指针。这样的设计使得每个连接都能独立处理,增加了服务器的处理能力。
事件处理机制
事件处理分为读和写两种主要类型:
- 读事件:当epoll通知某个套接字可读时,服务器将调用
recv_cb
回调函数,读取数据并根据数据内容处理请求。 - 写事件:当套接字准备好写入数据时,
send_cb
函数被调用,服务器将响应数据发送回客户端。
这种基于回调的处理机制使得服务器可以非常灵活地响应不同的事件,而不是在单一的大循环中处理所有逻辑。
#include "server.h"
#define CONN_SIZE 1048576
int recv_cb(int fd);
int accept_cb(int fd);
int send_cb(int fd);
int epfd = 0;
//创建一个连接结构体,其中包含文件描述符、接收和发送缓冲区、缓冲区长度和回调函数指针等。
struct conn conn_list[CONN_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);
}
}
//listen --> EPOLLIN -->ac_cb
int event_register(int fd, int event){
if (fd < 0) return -1;
conn_list[fd].fd = fd;
conn_list[fd].r_action.recv_callb = recv_cb;
conn_list[fd].send_callb = 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: 用于向 epoll 实例注册事件。接受文件描述符、事件类型和一个标志来决定是添加还是修改事件。
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;
}
//event_register: 将新的或现有的文件描述符注册到连接列表和 epoll 实例中,初始化连接的相关属性。
event_register(clientfd, EPOLLIN);
return 0;
}
int recv_cb(int fd){
int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
if (count == 0) { // disconnect
printf("client disconnect: %d\n", fd);
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);
set_event(fd, EPOLLOUT, 0);
return count;
}
int send_cb(int fd){
http_response(&conn_list[fd]);
//memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, BUFFER_LENGTH);
int count = send(fd, conn_list[fd].wbuffer, count, 0);
printf("SEND: %s\n", conn_list[fd].wbuffer);
set_event(fd, EPOLLIN, 0);
return count;
}
//init_server: 初始化服务器,创建套接字,绑定到指定端口,并开始监听连接请求。
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); // 0.0.0.0
servaddr.sin_port = htons(port); // 0-1023,
if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf("bind failed: %s\n", strerror(errno));
}
listen(sockfd, 10);
printf("listen finshed: %d\n", sockfd); // 3
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_callb =accept_cb;
set_event(sockfd, EPOLLIN, 1);
while(1){
int i = 0;
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, BUFFER_LENGTH, -1);
for (i = 0;i < nready;i ++) {
int connfd = events[i].data.fd;
if (events[i].events & EPOLLIN) {
conn_list[connfd].r_action.recv_callb(connfd);
}
if(events[i].events & EPOLLOUT){
conn_list[connfd].send_callb(connfd);
}
}
}
}
server.h:
#ifndef __SERVER_H__
#define __SERVER_H__
#define BUFFER_LENGTH 1024
typedef int (*rcallback)(int fd);
struct conn{
int fd;
char rbuffer[BUFFER_LENGTH];
int rlength;
char wbuffer[BUFFER_LENGTH];
int wlength;
rcallback send_callb;
union{
rcallback recv_callb;
rcallback accept_callb;
} r_action;
};
#endif