【Linux C/C++开发】epoll模式的开源库及原生socket实现
前言
epoll模式涉及到系统底层的I/O多路复用机制,可以处理高并发的场景。本文使用开源的libuv库以及原生的scoket来分享epoll的运作机制,方便更加深入的理解网络编程。
libuv库实现epoll
这是一个C库,之所以先分享libuv,是因为它更像QT的信号-槽机制(适合对网络编程编程不熟,但有希望深入理解epoll功能的QT开发读者阅读),并且做了一定的封装,比原生socket容易理解。
下载源码
gitecode的libuv库
https://gitcode.com/gh_mirrors/li/libuv?source_module=search_result_repo
源码编译
cd libuv
sh autogen.sh # 生成必要的脚本文件
./configure # 配置你的系统环境,可以选择性地添加一些参数,例如--prefix=/usr/local来指定安装目录
make # 编译库
sudo make install # 安装库到系统目录(安装之后,Linux在/usr/local/lib/可看到libuv.so 和 libuv.a)
gcc编译的时候需要加上-luv引用即可。
功能讲解
以下按照功能的引用顺序提供一个表格,方便直观的了解工作流程。
| libuv函数 | 函数分类 | 功能描述 | 用途说明 |
|---|---|---|---|
|
| 事件循环 | 获取默认的事件循环实例 | 创建并返回libuv的默认事件循环,用于管理所有异步I/O操作 |
|
| TCP通信 | 初始化TCP句柄 | 创建服务器端socket并初始化为非阻塞模式,准备进行网络通信 |
|
| TCP通信 | 绑定TCP服务器到指定地址和端口 | 将TCP socket与特定的网络接口和端口号关联 |
|
| TCP通信 | 开始监听连接请求 | 设置TCP socket为监听状态,并指定最大连接队列长度 |
|
| TCP通信 | 接受客户端连接 | 从连接队列中取出已建立的连接,创建客户端socket |
|
| 数据读写 | 开始异步读取数据 | 注册读取事件到事件循环,当有数据可读时触发回调 |
|
| 数据读写 | 异步发送数据 | 将数据写入socket,非阻塞方式,通过回调通知完成状态 |
|
| 资源管理 | 关闭句柄 | 安全关闭连接或定时器等资源,并在关闭后调用回调函数 |
|
| TCP通信 | 获取对端地址信息 | 获取已连接客户端的IP地址和端口信息 |
|
| 定时器 | 初始化定时器句柄 | 创建定时器,用于在指定时间间隔执行回调函数 |
|
| 定时器 | 启动定时器 | 设置定时器的首次延迟、重复间隔和回调函数 |
|
| 事件循环 | 运行事件循环 | 启动I/O多路复用,阻塞等待事件发生并处理回调 |
可以看到,libuv还是保留了原始scoket服务器端的典型流程:
socket->bind->listen->accept->read->write->close
libuv库通过以下几步机制,封装引用了epoll及I/O多路复用机制:
(1)在开头引用uv_default_loop(),在结尾引用uv_run(),提供了封装的epoll事件循环;
(2)侦听客户端时,使用uv_listen()进入监听状态但不会阻塞主线程,事件循环通过uv_run持续运行并处理各种I/O事件,当新的客户端连接请求到达时,libuv会在事件循环中触发预先注册的回调函数on_new_connection;
(3)uv_accept并非传统的阻塞式accept,而是处理已经被操作系统接受并放入完成队列的连接,整个过程都是在事件驱动的异步框架下完成的;
(4)使用uv_read_start()注册读取参照到事件循环中,监听可读事件,通过调用回调函数on_read()方式实现了异步读取数据的方法;
(5)使用uv_close()进行资源清理,使用回调函数on_client_closed释放需要释放的资源(比如业务功能涉及的内存资源)。
以上的uv_listen()、uv_read_start()、uv_close()都由回调函数进行响应,这个机制与QT的信号和槽机制非常像,加上事件循环的机制,就更加像了。
服务端源码
//uv_epollserver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>#define PORT 8080 //服务器监听端口号
#define MAX_CLIENTS 10000 //最大支持的客户端连接数
#define BUFFER_SIZE 1024 //数据缓冲区大小(字节)typedef struct {uv_tcp_t handle; //libuv TCP句柄,用于管理TCP连接struct sockaddr_in addr; //客户端网络地址信息char buffer[BUFFER_SIZE]; //数据接收缓冲区int client_id; //客户端唯一标识符time_t connect_time; //客户端连接时间戳
} client_t;uv_loop_t *loop; //libuv事件循环实例
client_t *clients[MAX_CLIENTS]; //客户端指针数组
int client_count = 0; //当前连接客户端计数//分配客户端ID
int allocate_client_id() {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i] == NULL) { //查找空闲槽位return i; //返回可用的客户端ID}}return -1; //无可用ID,返回错误码
}//释放客户端资源
void free_client(client_t *client) {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i] == client) { //定位客户端在数组中的位置clients[i] = NULL; //清空数组对应位置client_count--; //减少客户端计数break;}}free(client); //释放客户端结构体内存
}//数据发送回调
void on_write_end(uv_write_t *req, int status) {if (status) { //检查发送是否出错fprintf(stderr, "Write error: %s\n", uv_strerror(status));}free(req); //释放写请求结构体内存
}//读取客户端数据
void on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {client_t *client = (client_t*)stream;//转换类型//char client_ip[INET_ADDRSTRLEN];const char *ip_str = "Unknown";if (client != NULL) {uv_ip4_name(&client->addr, client_ip, sizeof(client_ip));ip_str = client_ip;}if (nread > 0) {//成功读取到数据//处理接收到的数据buf->base[nread] = '\0';//添加字符串终止符printf("Received from client: %s\n", buf->base);//回显数据给客户端uv_write_t *req = (uv_write_t*)malloc(sizeof(uv_write_t));//分配写请求内存uv_buf_t wrbuf = uv_buf_init(buf->base, nread);//初始化写缓冲区uv_write(req, stream, &wrbuf, 1, on_write_end);//异步发送数据} else if (nread < 0) {//读取发生错误if (nread != UV_EOF) {//非正常断开连接fprintf(stderr, "Read error: %s\n", uv_strerror(nread));}else {//客户端正常断开连接printf("[%s] Client disconnected\n", ip_str);}uv_close((uv_handle_t*)stream, NULL);//关闭连接句柄}free(buf->base);//释放读取缓冲区内存
}//分配读取缓冲区
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {buf->base = (char*)malloc(suggested_size);//动态分配内存buf->len = suggested_size;//设置缓冲区长度
}//客户端关闭回调
void on_client_closed(uv_handle_t *handle) {client_t *client = (client_t*)handle;free_client(client);//调用资源释放函数
}//新连接处理
void on_new_connection(uv_stream_t *server, int status) {if (status < 0) {//检查连接状态是否异常fprintf(stderr, "New connection error: %s\n", uv_strerror(status));return;}//分配客户端结构体内存client_t *client = (client_t*)malloc(sizeof(client_t));int client_id = allocate_client_id();//获取客户端IDif (client_id == -1) {//为-1时,表示已经达到最大连接数fprintf(stderr, "Too many clients\n");free(client);//释放已分配的内存return;}clients[client_id] = client;//将客户端指针存入数组client_count++;//增加客户端计数//初始化TCP句柄-创建客户端socketuv_tcp_init(loop, &client->handle);//非阻塞方式建立客户端连接if (uv_accept(server, (uv_stream_t*)&client->handle) == 0) {//获取客户端地址int addr_len = sizeof(client->addr);uv_tcp_getpeername(&client->handle, (struct sockaddr*)&client->addr, &addr_len);client->connect_time = time(NULL);//记录连接建立时间client->client_id = client_id;//记录客户端IDchar client_ip[INET_ADDRSTRLEN];uv_ip4_name(&client->addr, client_ip, sizeof(client_ip));printf("New client connected: ID=%d, IP=%s, Total clients: %d\n",client_id, client_ip, client_count);//开始异步读取数据--多路复用注册uv_read_start((uv_stream_t*)&client->handle, alloc_buffer, on_read);} else {//接受连接失败//资源清理,使用回调函数on_client_closeduv_close((uv_handle_t*)&client->handle, on_client_closed);}
}//定时器回调 - 定期统计
void on_timer(uv_timer_t *handle) {printf("Server status - Connected clients: %d\n", client_count);printf("Memory usage: %ld KB\n", (long)(client_count * sizeof(client_t) / 1024));printf("========================\n");
}int main() {loop = uv_default_loop();//获取默认事件循环,在系统底层关联上I/O多路复用器//创建TCP服务器uv_tcp_t server;//初始化TCP句柄-创建服务器端socketuv_tcp_init(loop, &server);//绑定服务器地址和端口struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", PORT, &addr);uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);//绑定到指定地址//listen创建连接请求队列,队列长度为SOMAXCONN(通常128),同时将服务器socket注册到I/O多路复用器(epoll/IOCP等)//当客户端发起连接时,已完成三次握手的连接会放入这个队列,然后由I/O多路复用器激活调用on_new_connectionint r = uv_listen((uv_stream_t*)&server, SOMAXCONN, on_new_connection);if (r) {//监听失败处理fprintf(stderr, "Listen error: %s\n", uv_strerror(r));return 1;}printf("Server listening on port %d\n", PORT);printf("Maximum clients: %d\n", MAX_CLIENTS);//初始化客户端数组memset(clients, 0, sizeof(clients));//启动统计定时器--显示全局信息uv_timer_t timer;uv_timer_init(loop, &timer);uv_timer_start(&timer, on_timer, 0, 5000);//启动事件循环,调用epoll_wait等函数阻塞等待return uv_run(loop, UV_RUN_DEFAULT);
}
编译方法:gcc uv_epollserver.c -oserver
客户端源码
这是配套测试的,不详细讲解了
//uv_epollclient.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>#define PORT 8080 //服务器监听端口号
#define MAX_CLIENTS 10000 //最大支持的客户端连接数
#define BUFFER_SIZE 1024 //数据缓冲区大小(字节)typedef struct {uv_tcp_t handle; //libuv TCP句柄,用于管理TCP连接struct sockaddr_in addr; //客户端网络地址信息char buffer[BUFFER_SIZE]; //数据接收缓冲区int client_id; //客户端唯一标识符time_t connect_time; //客户端连接时间戳
} client_t;uv_loop_t *loop; //libuv事件循环实例
client_t *clients[MAX_CLIENTS]; //客户端指针数组
int client_count = 0; //当前连接客户端计数//分配客户端ID
int allocate_client_id() {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i] == NULL) { //查找空闲槽位return i; //返回可用的客户端ID}}return -1; //无可用ID,返回错误码
}//释放客户端资源
void free_client(client_t *client) {for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i] == client) { //定位客户端在数组中的位置clients[i] = NULL; //清空数组对应位置client_count--; //减少客户端计数break;}}free(client); //释放客户端结构体内存
}//数据发送回调
void on_write_end(uv_write_t *req, int status) {if (status) { //检查发送是否出错fprintf(stderr, "Write error: %s\n", uv_strerror(status));}free(req); //释放写请求结构体内存
}//读取客户端数据
void on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {client_t *client = (client_t*)stream;//转换类型//char client_ip[INET_ADDRSTRLEN];const char *ip_str = "Unknown";if (client != NULL) {uv_ip4_name(&client->addr, client_ip, sizeof(client_ip));ip_str = client_ip;}if (nread > 0) {//成功读取到数据//处理接收到的数据buf->base[nread] = '\0';//添加字符串终止符printf("Received from client: %s\n", buf->base);//回显数据给客户端uv_write_t *req = (uv_write_t*)malloc(sizeof(uv_write_t));//分配写请求内存uv_buf_t wrbuf = uv_buf_init(buf->base, nread);//初始化写缓冲区uv_write(req, stream, &wrbuf, 1, on_write_end);//异步发送数据} else if (nread < 0) {//读取发生错误if (nread != UV_EOF) {//非正常断开连接fprintf(stderr, "Read error: %s\n", uv_strerror(nread));}else {//客户端正常断开连接printf("[%s] Client disconnected\n", ip_str);}uv_close((uv_handle_t*)stream, NULL);//关闭连接句柄}free(buf->base);//释放读取缓冲区内存
}//分配读取缓冲区
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {buf->base = (char*)malloc(suggested_size);//动态分配内存buf->len = suggested_size;//设置缓冲区长度
}//客户端关闭回调
void on_client_closed(uv_handle_t *handle) {client_t *client = (client_t*)handle;free_client(client);//调用资源释放函数
}//新连接处理
void on_new_connection(uv_stream_t *server, int status) {if (status < 0) {//检查连接状态是否异常fprintf(stderr, "New connection error: %s\n", uv_strerror(status));return;}//分配客户端结构体内存client_t *client = (client_t*)malloc(sizeof(client_t));int client_id = allocate_client_id();//获取客户端IDif (client_id == -1) {//为-1时,表示已经达到最大连接数fprintf(stderr, "Too many clients\n");free(client);//释放已分配的内存return;}clients[client_id] = client;//将客户端指针存入数组client_count++;//增加客户端计数//初始化TCP句柄-创建客户端socketuv_tcp_init(loop, &client->handle);//非阻塞方式建立客户端连接if (uv_accept(server, (uv_stream_t*)&client->handle) == 0) {//获取客户端地址int addr_len = sizeof(client->addr);uv_tcp_getpeername(&client->handle, (struct sockaddr*)&client->addr, &addr_len);client->connect_time = time(NULL);//记录连接建立时间client->client_id = client_id;//记录客户端IDchar client_ip[INET_ADDRSTRLEN];uv_ip4_name(&client->addr, client_ip, sizeof(client_ip));printf("New client connected: ID=%d, IP=%s, Total clients: %d\n",client_id, client_ip, client_count);//开始异步读取数据--多路复用注册uv_read_start((uv_stream_t*)&client->handle, alloc_buffer, on_read);} else {//接受连接失败//资源清理,使用回调函数on_client_closeduv_close((uv_handle_t*)&client->handle, on_client_closed);}
}//定时器回调 - 定期统计
void on_timer(uv_timer_t *handle) {printf("Server status - Connected clients: %d\n", client_count);printf("Memory usage: %ld KB\n", (long)(client_count * sizeof(client_t) / 1024));printf("========================\n");
}int main() {loop = uv_default_loop();//获取默认事件循环,在系统底层关联上I/O多路复用器//创建TCP服务器uv_tcp_t server;//初始化TCP句柄-创建服务器端socketuv_tcp_init(loop, &server);//绑定服务器地址和端口struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", PORT, &addr);uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);//绑定到指定地址//listen创建连接请求队列,队列长度为SOMAXCONN(通常128),同时将服务器socket注册到I/O多路复用器(epoll/IOCP等)//当客户端发起连接时,已完成三次握手的连接会放入这个队列,然后由I/O多路复用器激活调用on_new_connectionint r = uv_listen((uv_stream_t*)&server, SOMAXCONN, on_new_connection);if (r) {//监听失败处理fprintf(stderr, "Listen error: %s\n", uv_strerror(r));return 1;}printf("Server listening on port %d\n", PORT);printf("Maximum clients: %d\n", MAX_CLIENTS);//初始化客户端数组memset(clients, 0, sizeof(clients));//启动统计定时器--显示全局信息uv_timer_t timer;uv_timer_init(loop, &timer);uv_timer_start(&timer, on_timer, 0, 5000);//启动事件循环,调用epoll_wait等函数阻塞等待return uv_run(loop, UV_RUN_DEFAULT);
}
原生socket实现epoll
不需要额外下载开发包。
功能讲解
以下按照功能的引用顺序提供一个表格,方便直观的了解工作流程。
| 函数 | 函数分类 | 功能描述 | 用途说明 | 对应libuv函数 |
|---|---|---|---|---|
|
| 套接字创建 | 创建套接字描述符 | 创建TCP通信端点,指定协议族和类型 |
|
|
| 地址绑定 | 绑定套接字地址 | 将服务器socket与特定IP和端口关联 |
|
|
| 连接监听 | 监听连接请求 | 设置socket为监听状态,指定连接队列长度 |
|
|
| 模式设置 | 设置属性 | 将服务器端ocket设置为非阻塞模式 | libuv自动处理非阻塞 |
|
| 多路复用 | 创建epoll实例 | 创建事件多路分离器,用于监控多个文件描述符 |
|
|
| 多路复用 | 控制epoll监控列表 | 添加、修改或移除被监控的文件描述符 | libuv事件循环内部管理 |
|
| 多路复用 | 等待事件发生 | 阻塞等待被监控的文件描述符上事件发生 |
|
|
| 连接管理 | 接受客户端连接 | 从连接队列中取出已建立的连接,创建客户端socket |
|
|
| 模式设置 | 设置属性 | 将客户端socket设置为非阻塞模式 | libuv自动处理非阻塞 |
|
| 数据读写 | 从套接字读取数据 | 从客户端socket接收数据 |
|
|
| 数据读写 | 向套接字写入数据 | 向客户端socket发送数据 |
|
|
| 资源管理 | 关闭文件描述符 | 释放socket资源,终止连接 |
|
从以上流程中可看到,在原始的socket->bind->listen->accept->read->write->close流程中,在listen()侦听之后,加入了fcntl()->epoll_create1()->epoll_ctl()->epoll_wait()流程,此流程是引入非阻塞的服务端接收连接机制,以及将服务端文件描述符添加到epoll实例中进行监控,这样当socket缓存区中有数据时,会触发epoll_wait()通知事件。
另外,在accept连接上客户端之后,需要将客户端socket设置为非阻塞模式,才能达到异步的效果。
服务端源码
以下代码中有详细的注释
//src_scoketepoll.c
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>#define MAX_EVENTS 1024
#define BUFFER_SIZE 4096 //数据缓冲区大小(字节)
#define SERVER_PORT 8080 //服务器监听端口号#define PRINTF_ERR_MSG(format, ...) fprintf(stderr, "[ERROR]<%s:%d>:" format "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define SCREEN_PRINTF(format,args...) printf("%s-%s-%d:" format "\n",__FILE__,__FUNCTION__,__LINE__,##args)// 设置文件描述符为非阻塞模式
static int set_nonblocking(int fd) {int flags = fcntl(fd, F_GETFL, 0);if (flags == -1) return -1;return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}int main() {int server_fd;// 创建服务器socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {perror("socket creation failed");exit(EXIT_FAILURE);}// 设置socket选项,允许端口重用int opt = 1;setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 绑定服务器地址struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = INADDR_ANY;//所有可用的网络接口//server_addr.sin_addr.s_addr = inet_addr("192.168.1.123");//固定IP//绑定服务端地址if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 开始监听,listen创建连接请求队列,队列长度为SOMAXCONN(通常128)//当客户端发起连接时,已完成三次握手的连接会放入这个队列if (listen(server_fd, SOMAXCONN) == -1) {perror("listen failed");close(server_fd);exit(EXIT_FAILURE);}// 设置服务器socket为非阻塞if (set_nonblocking(server_fd) == -1) {perror("set_nonblocking server_fd failed");close(server_fd);exit(EXIT_FAILURE);}SCREEN_PRINTF("Epoll server started on port %d\n", SERVER_PORT);struct epoll_event event, events[MAX_EVENTS];// 创建epoll实例int epoll_fd= epoll_create1(0);//老方法是int epoll_fd = epoll_create(1)if (epoll_fd == -1) {perror("epoll_create1 failed");close(server_fd);exit(EXIT_FAILURE);}//EPOLLIN默认是水平方式读事件,只要socket缓存区中有数据,就会一直触发epoll_wait通知(下文while循环中)//EPOLLIN | EPOLLET边缘触发读事件,socket缓存区由空->非空时,只触发一次epoll_wait通知(下文while循环中)event.events = EPOLLIN | EPOLLET; //边缘触发的可读事件event.data.fd = server_fd;//epoll_ctl 将服务端文件描述符添加到epoll实例中进行监控//EPOLL_CTL_ADD - 添加新的文件描述符到监控列表//EPOLL_CTL_MOD - 修改已监控文件描述符的事件设置//EPOLL_CTL_DEL - 从监控列表中移除文件描述符if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {perror("epoll_ctl add server_fd failed");close(server_fd);close(epoll_fd);exit(EXIT_FAILURE);}// 主事件循环while (1) {//如果成功,nfds接收返回的事件个数,把就绪的事件放在events数组中int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_wait failed");break;}//事件处理for (int i = 0; i < nfds; i++) {// 处理新连接if (events[i].data.fd == server_fd) {//处理服务端描述符事件SCREEN_PRINTF("接收到epoll_wait推送的服务端链接事件\n");while (1) {struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);//从已完成连接的队列里面,获取一个客户端信息,生成一个新的文件描述符,这是与客户端通信的文件描述符int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);if (client_fd == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {break; // 已处理所有待处理连接} else {perror("accept failed");break;}}// 设置客户端socket为非阻塞if (set_nonblocking(client_fd) == -1) {perror("set_nonblocking failed");close(client_fd);continue;}SCREEN_PRINTF("[%s:%d] 客户端连接成功 client_fd=%d \n",\inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port), client_fd);// 添加客户端socket到epoll监控//EPOLLIN:当客户端发送数据到服务器时触发//EPOLLET:只在socket缓冲区从空变为非空时通知一次//EPOLLRDHUP:当客户端断开连接时触发event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;event.data.fd = client_fd;//保存客户端文件描述符if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {perror("epoll_ctl add client_fd failed");close(client_fd);}}}// 处理客户端数据else {//SCREEN_PRINTF("接收到epoll_wait推送的客户端交互事件\n");int client_fd = events[i].data.fd;// 检查连接是否关闭//EPOLLRDHUP:当客户端断开连接时触发//EPOLLHUP:当客户端强制终止连接时if (events[i].events & (EPOLLRDHUP | EPOLLHUP)) {printf("Client disconnected (fd: %d)\n", client_fd);close(client_fd);continue;}// 处理可读事件if (events[i].events & EPOLLIN) {char buffer[BUFFER_SIZE];ssize_t bytes_read;int total_bytes = 0;// 读取客户端数据--读取socket缓存区中的数据while (1) {//可以用bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0),多了一个参数bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);if (bytes_read > 0) {buffer[bytes_read] = '\0';total_bytes += bytes_read;SCREEN_PRINTF("Received from client %d: %s", client_fd, buffer);// 回显数据给客户端--测试发送是否成功if (write(client_fd, buffer, bytes_read) != bytes_read) {perror("write failed");break;}}if (bytes_read == 0) {SCREEN_PRINTF("Client disconnected (fd: %d)", client_fd);//epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);// 从epoll监控中移除client_fd//当调用 close(fd) 时,内核会自动将fd文件描述符从所有epoll实例中移除,以上代码不需要显示EPOLL_CTL_DEL,只是展示有这个动作close(client_fd);}else {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 没有更多数据可读,这是正常情况if (total_bytes > 0) {printf("Finished reading from client %d, total: %d bytes\n",client_fd, total_bytes);} else {perror("read failed");close(client_fd);}break;}}}}}}}close(server_fd);close(epoll_fd);return 0;
}
使用gcc src_socketepoll.c -oserver编译即可。
客户端可以使用libuv中的客户端源码来测试。
篇尾
以上的epoll服务端可以处理万级以上的高并发需求场景,本篇也是进程间通信(IPC)-socket内容的补充。
