当前位置: 首页 > news >正文

一个基于 epoll 实现的多路复用 TCP 服务器程序,相比 select 和 poll 具有更高的效率

/*5 - 使用epoll实现多路复用 */
#include <stdio.h>          // 标准输入输出函数库
#include <stdlib.h>         // 标准库函数,包含exit等
#include <string.h>         // 字符串处理函数
#include <unistd.h>         // Unix标准函数,包含read, write等
#include <sys/socket.h>     // 套接字相关函数
#include <sys/types.h>      // 基本系统数据类型
#include <sys/epoll.h>      // epoll相关函数和结构体
#include <fcntl.h>          // 文件控制函数
#include <sys/stat.h>       // 文件状态相关定义
#include <netinet/in.h>     // 互联网地址族
#include <arpa/inet.h>      // 提供IP地址转换函数
#include <signal.h>         // 信号处理函数
#include <linux/input.h>    // Linux输入设备相关定义(未实际使用)#define FD_CNT 1000         // 定义最大监控的文件描述符数量int main(int argc,char *argv[])
{if(argc!=2){                    // 检查命令行参数数量是否正确printf("Usage:%s port\n",argv[0]);  // 输出程序使用方法:程序名 端口号exit(0);                    // 正常退出}//1.创建socketint sockfd = socket(AF_INET,SOCK_STREAM,0);  // 创建TCP套接字,AF_INET表示IPv4,SOCK_STREAM表示TCPif(sockfd==-1){                  // 检查socket创建是否成功perror("socket");            // 输出错误信息exit(-1);                    // 异常退出}//允许地址复用int optval = 1;                  // 选项值,1表示启用// 设置套接字选项,允许地址复用,避免端口占用问题setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));//2.绑定ip和端口号(自己)struct sockaddr_in addr;         // 定义IPv4地址结构体addr.sin_family = AF_INET;       // 设置协议族为IPv4addr.sin_port = htons(atoi(argv[1]));  // 将命令行参数的端口号转换为网络字节序addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的网络接口// 绑定套接字到指定地址和端口int res = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));if(res==-1){                     // 检查绑定是否成功perror("bind");              // 输出错误信息exit(-1);                    // 异常退出}//3.监听res = listen(sockfd,10);         // 开始监听连接,最大等待队列长度为10if(res==-1){                     // 检查监听是否成功perror("listen");            // 输出错误信息exit(-1);                    // 异常退出}//准备epoll事件数组,用于存储epoll_wait返回的活动事件struct epoll_event evt_arr[FD_CNT] = {};  // 事件数组初始化int maxfd,i;                     // maxfd未实际使用,i用于循环char msg[1024] = {};             // 用于存储接收和发送的消息//1.创建epoll专用描述符// 参数为最大监控数量(>=1,Linux 2.6.8后忽略该参数)int epfd = epoll_create(1000);if(epfd==-1){                    // 检查epoll创建是否成功perror("epoll_create");      // 输出错误信息exit(-1);                    // 异常退出}//2.添加要监控的事件struct epoll_event evt = {       // 定义epoll事件结构体.events = EPOLLIN,           // 监控读事件(有数据可读)};evt.data.fd = 0;                 // 绑定标准输入文件描述符(0)// 将标准输入添加到epoll监控res = epoll_ctl(epfd,EPOLL_CTL_ADD,0,&evt);if(res==-1){                     // 检查添加是否成功perror("epoll_ctl");         // 输出错误信息exit(-1);                    // 异常退出}evt.data.fd = sockfd;            // 绑定服务器监听套接字// 将监听套接字添加到epoll监控res = epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&evt);if(res==-1){                     // 检查添加是否成功perror("epoll_ctl");         // 输出错误信息exit(-1);                    // 异常退出}//4.等待事件发生(客户端连接、消息或输入)while(1){                        // 主循环,持续运行服务器//调用epoll_wait等待事件,超时时间2000ms(2秒)// 参数:epoll描述符、接收事件的数组、最大事件数、超时时间if((res = epoll_wait(epfd,evt_arr,FD_CNT,2000))<=0){printf("timeout!\n");    // 超时或出错时输出信息continue;                // 继续下一次循环}//处理活动的描述符(遍历所有返回的事件)for(i=0;i<res;i++){//有键盘输入(标准输入有读事件)if(evt_arr[i].data.fd==0 && (evt_arr[i].events&EPOLLIN)){memset(msg,0,sizeof(msg));  // 清空消息缓冲区read(0,msg,sizeof(msg));    // 从标准输入读取数据printf("输入的内容为:%s\n",msg);  // 输出读取到的内容}//1.有客户端连接请求(监听套接字有读事件)if(evt_arr[i].data.fd==sockfd && (evt_arr[i].events&EPOLLIN)){struct sockaddr_in cilent_addr;  // 存储客户端地址socklen_t len = sizeof(cilent_addr);  // 地址长度// 接受客户端连接,返回新的套接字描述符int newfd = accept(sockfd,(struct sockaddr *)&cilent_addr,&len);if(newfd==-1){            // 检查连接是否成功perror("accept");     // 输出错误信息exit(-1);             // 异常退出}//将新连接的客户端描述符添加到epoll监控evt.data.fd = newfd;      // 绑定新客户端套接字// 添加新客户端到epoll监控列表res = epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&evt);if(res==-1){              // 检查添加是否成功perror("epoll_ctl");  // 输出错误信息exit(-1);             // 异常退出}// 输出客户端IP地址,表示有新客户端连接printf("%s到此一游!\n",inet_ntoa(cilent_addr.sin_addr));}//2.有客户端发送消息(其他套接字有读事件)// 排除标准输入和监听套接字,处理客户端消息if(evt_arr[i].data.fd!=0 && evt_arr[i].data.fd!=sockfd && (evt_arr[i].events&EPOLLIN)){res = read(evt_arr[i].data.fd,msg,sizeof(msg));  // 从客户端读取消息if(res<=0){               // 如果读取失败或客户端断开连接// 客户端断开连接,将其从epoll监控中删除res = epoll_ctl(epfd,EPOLL_CTL_DEL,evt_arr[i].data.fd,&evt_arr[i]);if(res==-1){          // 检查删除是否成功perror("epoll_ctl");  // 输出错误信息}close(evt_arr[i].data.fd);  // 关闭套接字continue;             // 继续下一次循环}//需要长时间通信可以开多任务printf("%s\n",msg);      // 输出接收到的消息if(strcmp(msg,"byebye")==0){  // 如果客户端发送"byebye"// 客户端主动断开连接,从epoll监控中删除res = epoll_ctl(epfd,EPOLL_CTL_DEL,evt_arr[i].data.fd,&evt_arr[i]);if(res==-1){          // 检查删除是否成功perror("epoll_ctl");  // 输出错误信息}close(evt_arr[i].data.fd);  // 关闭套接字continue;             // 继续下一次循环}//原路发回消息(回声功能)write(evt_arr[i].data.fd,msg,res);  // 将接收到的消息回发给客户端}}}close(sockfd);                   // 关闭监听套接字(实际不会执行到这里)return 0;                        // 程序正常退出(实际不会执行到这里)
}

程序功能说明:

这是一个基于 epoll 实现的多路复用 TCP 服务器程序,相比 select 和 poll 具有更高的效率,主要功能如下:

核心功能
  1. TCP 服务器基础功能

    • 创建 TCP 套接字、设置地址复用、绑定端口、监听连接
    • 支持多客户端并发连接,通过 epoll 机制实现高效 I/O 多路复用
  2. 多路复用监控

    • 使用 epoll 同时监控三类 I/O 事件:
      • 标准输入(键盘输入):读取并显示键盘输入内容
      • 服务器监听套接字:接受新的客户端连接请求
      • 已连接客户端套接字:接收客户端消息并提供回声服务(消息原路返回)
  3. 连接管理

    • 新客户端连接时自动将其套接字添加到 epoll 监控列表
    • 客户端断开连接(主动发送 "byebye" 或异常断开)时,自动从 epoll 中移除并关闭套接字
    • 最大支持 1000 个并发连接(由 FD_CNT 定义)
技术特点
  1. epoll 机制优势

    • 采用 epoll_create 创建专用描述符,epoll_ctl 管理监控对象,epoll_wait 等待事件
    • 仅返回活动的文件描述符,无需遍历所有监控对象,效率更高(尤其在高并发场景)
    • 事件驱动模式,避免 select/poll 的性能瓶颈(如文件描述符数量限制、每次需重置集合)
  2. 事件处理流程

    • 主循环通过 epoll_wait 阻塞等待事件,超时时间设为 2 秒
    • 收到事件后遍历活动事件数组,区分不同类型事件(键盘输入、新连接、客户端消息)
    • 针对不同事件类型执行对应处理逻辑,实现单进程高效处理多任务
适用场景
  • 高并发网络服务器开发(相比 select/poll 更适合大量客户端连接场景)
  • 演示 Linux 下高效 I/O 多路复用机制的实现
  • 可作为回声服务器、聊天服务器等基础框架扩展

该程序展示了 epoll 在网络编程中的典型用法,是 Linux 平台下高性能服务器的常用技术方案。

http://www.dtcms.com/a/319397.html

相关文章:

  • 并发编程(三)线程模型和通信
  • 【AI算法承载】海思3516DV500+IMX664方案一体机芯,开放AI算法部署二次开发
  • 蓝桥杯----数码管、按键、定时器与中断
  • PTrade详细介绍
  • 【遥感图像入门】遥感中的“景”是什么意思?
  • 深入理解 ReentrantLock和AQS底层源码
  • 专题:2025财务转型与AI赋能数字化报告|附30+份报告PDF汇总下载
  • 《深入解析缓存三大难题:穿透、雪崩、击穿及应对之道》
  • cv2.threshold cv2.morphologyEx
  • 宝塔面板配置Nacos集群
  • Plant Biotechnol J(IF=10.5)|DAP-seq助力揭示葡萄白粉病抗性机制
  • 什么是POE接口?通俗理解
  • Pytest项目_day07(pytest)
  • MySql MVCC的原理总结
  • S7-1200 串行通信介绍
  • 配送算法9 A GRASP algorithm for the Meal Delivery Routing Problem
  • React 中 useRef 使用方法
  • 设计模式 观察者模式
  • react-router/react-router-dom
  • 对话访谈|盘古信息×冠捷科技:全球制造标杆的智能化密码
  • 鸿蒙类型转化Json转map
  • 【实录】NestJS 中的 IoC
  • 动力电池点焊机:效率质量双提升,驱动新能源制造升级
  • 中小制造企业数字化转型的可持续发展:IT架构演进与管理模式迭代
  • [盛最多水的容器]
  • WPS定制设置成绿色软件
  • Go语言Ebiten坦克大战
  • ADC常用库函数(STC8系列)
  • 现代制冷系统核心技术解析:从四大件到智能控制的关键突破
  • 客户管理系统的详细项目框架结构