百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Linux并发服务器模型五 -- epoll linux服务器实现

liebian365 2024-10-27 13:12 26 浏览 0 评论

多进程和多线程模型在实现中相对简单, 但其开销和CPU高度比较大, 一般不用多线程和多进程来实现服务器多路模型.

select由于其跨平台, 但其最高上限默认为1024, 修改突破1024的话需要重新编译linux内核, poll虽然解决了select1024的限制, 但由于poll本质实现上也是轮询机制, 所以对于客户端的增加也会使效率降低.

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率

  • 分析epoll API
//生成一个专用的文件描述符, size写多少就是多少, 但是如果运行过程中添加的越来越多, 后面的其实还会监听
 int epoll_create(int size); 
 //用于控制某个epoll文件描述符事件,可以注册、修改、删除
 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 //epfd: 生成的专用描述符, 相当于一个树(红黑树)的根节点, 如果再有其他的添加进来, 则系统将其会挂载到其节点下面
 //op: EPOLL_CTL_ADD -- 注册, EPOLL_CTL_MOD -- 修改, EPOLL_CTL_DEL -- 删除
 //fd: 关联的文件描述符
//event: struct epoll_event 结构体类型的指针, 告诉内核要监听什么事件
//其中event结构体分析如下
struct epoll_event {
 uint32_t events; //结构体
 epoll_data_t data; //联合体
 }
/*
 events:
 - EPOLLIN - 读
 - EPOLLOUT - 写
 - EPOLLERR - 异常
 */
/*
typedef union epoll_data { 
 void *ptr;
 int fd; //常用
 uint32_t u32;
 uint64_t u64;
} epoll_data_t;
 */
/*等待IO事件发生 - 可以设置阻塞的函数, 对应select和poll函数
该函数能够告诉我们是哪个文件描述符发生变化, 并放到下面的数组中
因为传进去的时候结构体中包含结构体中含有fd和event*/
int epoll_wait(int epfd,
 struct epoll_event* events, //数组
 int maxevents,
 int timeout);
/*参数:
 epfd: 要检测的句柄
 events:用于回传待处理事件的数组
 maxevents:告诉内核这个events的大小 
 timeout:为超时时间
-1: 永久阻塞; 0: 立即返回; >0: 阻塞多久*/

server

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <ctype.h>
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 1000
int main(int argc, char *argv[]) {
 // 记录是第几个连接上来的客户端
 int num = 0;
 
 // 创建监听的套接字
 int listenfd = socket(AF_INET, SOCK_STREAM, 0);
 
 // 端口复用
 int opt = 1;
 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 
 struct sockaddr_in servaddr;
 bzero(&servaddr, sizeof(servaddr));
 servaddr.sin_family = AF_INET;
 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 servaddr.sin_port = htons(SERV_PORT);
 //绑定
 bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
 
 listen(listenfd, 20);
 
 //创建epoll模型, efd指向红黑树根节点
 int efd = epoll_create(OPEN_MAX);
 if(efd == -1) {
 perror("epoll_create error");
 exit(1);
 }
 
 // tep: epoll_ctl参数
 struct epoll_event tep;
 //指定lfd的监听时间为"读"
 tep.events = EPOLLIN;
 tep.data.fd = listenfd;
 //将lfd及对应的结构体设置到树上,efd可找到该树
 int res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
 if(res == -1) {
 perror("epoll_ctl error");
 exit(1);
 }
 
 socklen_t clilen;
 struct sockaddr_in cliaddr;
 // ep[] : epoll_wait参数
 struct epoll_event ep[OPEN_MAX];
 char buf[MAXLINE], str[INET_ADDRSTRLEN];
 
 while(1) {
 /*epoll为server阻塞监听事件, ep为struct epoll_event类型数组, OPEN_MAX为数组容量, -1表永久阻塞*/
 int nready = epoll_wait(efd, ep, OPEN_MAX, -1);
 if(nready == -1) {
 perror("epoll_wait error");
 exit(1);
 }
 
 for (int i = 0; i < nready; i++) {
 //如果不是"读"事件, 继续循环
 if (!(ep[i].events & EPOLLIN)) {
 continue;
 }
 
 //判断满足事件的fd是不是lfd
 if (ep[i].data.fd == listenfd) {
 clilen = sizeof(cliaddr);
 // 接受连接请求
 int connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
 
 printf("received from %s at PORT %d\n",
 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
 ntohs(cliaddr.sin_port));
 printf("cfd %d---client %d\n", connfd, ++num);
 
 tep.events = EPOLLIN;
 tep.data.fd = connfd;
 res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
 if(res == -1) {
 perror("epoll_ctl error");
 exit(1);
 }
 } else { // 不是监听的文件描述符, 通信的fd
 int sockfd = ep[i].data.fd;
 int n = read(sockfd, buf, MAXLINE);
 
 //读到0,说明客户端关闭链接
 if (n == 0) {
 //将该文件描述符从红黑树摘除
 res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
 if(res == -1) {
 perror("epoll_ctl error");
 exit(1);
 }
 //关闭与该客户端的链接
 close(sockfd);
 printf("client[%d] closed connection\n", sockfd);
 } else if (n < 0) { //出错
 perror("read n < 0 error: ");
 res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
 close(sockfd);
 } else { // 实际读到了字节数
 for (int j = 0; j < n; j++) {
 buf[j] = toupper(buf[j]); //转大写,写回给客户端
 }
 write(STDOUT_FILENO, buf, n); //发送到终端, 也可以打印出来
 write(sockfd, buf, n); //发送给客户端
 }
 }
 }
 }
 close(listenfd);
 close(efd);
 
 return 0;
}

相关推荐

go语言也可以做gui,go-fltk让你做出c++级别的桌面应用

大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...

旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗

这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...

codeblocks和VS2019下的fltk使用中文

在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...

FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库

FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...

中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux

IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...

Linux 5.13内核有望合并对苹果M1处理器支持的初步代码

预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...

Ubuntu系统下COM口测试教程(ubuntu port)

1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...

湖北嵌入式软件工程师培训怎么选,让自己脱颖而出

很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...

新阁上位机开发---10年工程师的Modbus总结

前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...

创建你的第一个可运行的嵌入式Linux系统-5

@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...

如何在Linux下给zigbee CC2530实现上位机

0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...

Python实现串口助手 - 03串口功能实现

 串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...

为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口

UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...

同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理

串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...

嵌入式linux为什么可以通过PC上的串口去执行命令?

1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...

取消回复欢迎 发表评论: