epoll单台设备支持百万并发连接
一些概念:
linux下一切接文件,文件描述符fd,文件I/O(包含socket,文本文件等),I/O多路复用,reactor模型,水平触发,边沿触发,多线程模型,阻塞和非阻塞,同步和异步
设备有限,目前实测能达到 11万并发连接。当客户端端口耗尽退出时,服务端异常退出待解决。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#define BUFFER_LEN 128
#define MAX_CON 1024
#define EVENTS_LEN 128
#define ITEM_LEN 4096
struct sock_item {
int fd;
char *rbuffer;
int rlength;
char *wbuffer;
int wlength;
int event;
void (*recv_cb)(int fd, char *buffer, int length);
void (*send_cb)(int fd, char *buffer, int length);
void (*accept_cb)(int fd, char *buffer, int length);
};
struct eventblock {
struct sock_item *items;
struct eventblock *next;
};
struct reactor {
int epfd;
int blkcnt;
struct eventblock *evblk;
};
int reactor_alloc(struct reactor *r){
if(!r) return -1;
struct eventblock *blk = r->evblk;
while(blk != NULL && blk->next != NULL) blk = blk->next;
struct sock_item *item = (struct sock_item *)calloc(1, ITEM_LEN * sizeof(struct sock_item));
if(!item){
printf("no memory ret:%d %s\n", errno, strerror(errno));
return -1;
}
struct eventblock *block = (struct eventblock *)calloc(1, sizeof(struct eventblock));
if(!block) {
free(item);
printf("no memory ret:%d %s\n", errno, strerror(errno));
return -1;
}
block->items = item;
block->next = NULL;
//blk == NULL 时表示首个节点
if(!blk)
r->evblk = block;
else
blk->next = block;
r->blkcnt++;
printf("create block:%p, cnt:%d\n", block, r->blkcnt);
return 0;
}
struct sock_item* reactor_lookup(struct reactor *r, int sockfd){
if(!r || !r->evblk || sockfd <= 0) return NULL;
int ret;
int blkidx = sockfd / ITEM_LEN;
while(blkidx >= r->blkcnt){
ret = reactor_alloc(r);
if(ret != 0) return NULL;
}
int i = 0;
struct eventblock *blk = r->evblk;
while(i++ < blkidx) blk = blk->next;
return &blk->items[sockfd % ITEM_LEN];
}
void *routine(void *arg){
int ret;
int clientfd = *(int *)arg;
while(1){
unsigned char buf[BUFFER_LEN] = {0};
ret = recv(clientfd, buf, BUFFER_LEN, 0);
if(ret == 0){ //客户端close,这里不做处理会产生 close_wait 状态
close(clientfd);
break;
}
printf("clientfd:%d buf:%s %d\n", clientfd, buf, ret);
ret = send(clientfd, buf, ret, 0);
printf("clientfd send %d\n", ret);
}
}
int main(){
//socket 插座,理解 socketfd 是插,bind sockaddr_in 是座
int ret;
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1) return -1;
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
ret = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(ret == -1) return -2;
//fd 默认阻塞
//设置非阻塞
#if 0
int flag = fcntl(listenfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(listenfd, F_SETFL, flag);
#endif
listen(listenfd, 10); //类似酒店迎宾的人
#if 0
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len);
while(1){
unsigned char buf[BUFFER_LEN] = {0};
ret = recv(clientfd, buf, BUFFER_LEN, 0);
printf("clientfd:%d %s %d\n", clientfd, buf, ret);
ret = send(clientfd, buf, ret, 0);
printf("clientfd send %d\n", ret);
}
#elif 0
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
//类似酒店服务员
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len);
pthread_t tid;
pthread_create(&tid, NULL, routine, &clientfd);
}
#elif 0 //<apue> pg404 select
fd_set rfds, wfds, rset, wset;
FD_ZERO(&rfds);
FD_SET(listenfd, &rfds);
FD_ZERO(&wfds);
int maxfd = listenfd; //对 maxfd 的理解?内核 bitmap 的下标上限?
unsigned char buf[MAX_CON][BUFFER_LEN + 1] = {{0}};
while(1){
rset = rfds;
wset = wfds;
//内核循环上限 可读集合 可写集合 出错 超时时间
select(maxfd + 1, &rset, &wset, NULL, NULL); //只监听listen的读事件
if(FD_ISSET(listenfd, &rset)){
printf("listenfd event:%d\n", listenfd);
//listen 已经有读事件,accpet该连接
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len); //accept会清空listen读事件
FD_SET(clientfd, &rfds); //设置客户端的监听读事件
if(clientfd > maxfd) maxfd = clientfd;
}
int i = 0;
for(i = listenfd + 1; i <= maxfd; ++i){
if(i >= MAX_CON) {
printf("over fd %d\n", i);
continue;
}
if(FD_ISSET(i, &rset)){
ret = recv(i, buf[i], BUFFER_LEN, 0);
if(ret == 0){
close(i);
FD_CLR(i, &rfds);
printf("close fd:%d\n", i);
continue;
}
printf("clientfd:%d %s %d\n", i, buf[i], ret);
FD_SET(i, &wfds); //监听是否可写
}
if(FD_ISSET(i, &wset)){
ret = send(i, buf[i], ret, 0);
printf("clientfd:%d send %d\n", i, ret);
FD_CLR(i, &wfds); //清空监听写
}
}
}
#elif 0 //搜索系统调用 SYSCALL_DEFINE(N) N表示该系统调用的参数个数
int epfd = epoll_create(1);
struct epoll_event ev, events[EVENTS_LEN];
ev.events = EPOLLIN; //这里选择水平还是边沿触发;如果是边沿触发,怎么做?(while accept listenfd 读事件)
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //将listenfd加入到红黑树中, 边沿触发监听listenfd的读事件
char buf[BUFFER_LEN] = {0};
while(1){
int nready = epoll_wait(epfd, events, EVENTS_LEN, -1); //-1阻塞,0立刻返回, 1000为1秒返回
printf("epoll_wait ret ready:%d\n", nready);
int i = 0;
for(i = 0; i < nready; ++i){
int clientfd = events[i].data.fd;
if(listenfd == clientfd){
//accept 建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr *)&client, &len);
printf("accept connfd:%d\n", connfd);
ev.events = EPOLLET | EPOLLIN; //水平触发 和 边沿触发 的区别?
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}else if(events[i].events & EPOLLIN){
ret = recv(clientfd, buf, BUFFER_LEN, 0);
if(ret == 0){
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &events[i]);
close(clientfd);
printf("clientfd:%d closed\n", clientfd);
//continue;
}
ev.events = EPOLLOUT; //改为监听可写事件
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}else if(events[i].events & EPOLLOUT){
ret = send(clientfd, buf, ret, 0);
if(ret <= 0){
char *err = strerror(errno);
printf("clientfd:%d closed ? ret %d errno %d:%s\n",
clientfd, ret, errno, err == NULL ? "unknow": err);
}
printf("clientfd:%d send:%s num:%d\n", clientfd, buf, ret);
ev.events = EPOLLIN; //改为监听可写事件
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}
}
}
#elif 1 //reactor 百万并发实现?
struct reactor *r = (struct reactor *)calloc(1, sizeof(struct reactor));
if(!r) {
printf("memory failed\n");
return -1;
}
ret = reactor_alloc(r);
if(ret != 0) return -1;
r->epfd = epoll_create(1);
struct epoll_event ev;
struct epoll_event events[EVENTS_LEN]; //准备好的事件缓冲区
ev.events = EPOLLIN; //默认水平触发
ev.data.fd = listenfd;
epoll_ctl(r->epfd, EPOLL_CTL_ADD, listenfd, &ev);
while(1){
int nready = epoll_wait(r->epfd, events, EVENTS_LEN, -1); //-1阻塞,0立刻返回, 1000为1秒返回
printf("epoll_wait ret ready:%d\n", nready);
int i = 0;
for(i = 0; i < nready; ++i){
int clientfd = events[i].data.fd;
if(listenfd == clientfd){
//accept 建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr *)&client, &len);
printf("accept connfd:%d\n", connfd);
if(connfd <= 0) continue;
ev.events = EPOLLIN; //水平触发 和 边沿触发 的区别?
ev.data.fd = connfd;
epoll_ctl(r->epfd, EPOLL_CTL_ADD, connfd, &ev);
#if 0
//填充 sock_item
assert(r->items[connfd].rbuffer == NULL);
r->items[connfd].rbuffer = (char *)calloc(1, BUFFER_LEN);
r->items[connfd].rlength = 0;
assert(r->items[connfd].wbuffer == NULL);
r->items[connfd].wbuffer = (char *)calloc(1, BUFFER_LEN);
r->items[connfd].wlength = 0;
r->items[connfd].event = EPOLLIN;
assert(r->items[connfd].fd == 0);
r->items[connfd].fd = connfd;
#endif
struct sock_item *item = reactor_lookup(r, connfd);
if(item == NULL) {
printf("error lookup item\n");
continue;
}
item->fd = connfd;
assert(item->rbuffer == NULL);
item->rbuffer = calloc(1, BUFFER_LEN);
item->rlength = 0;
assert(item->wbuffer == NULL);
item->wbuffer = calloc(1, BUFFER_LEN);
item->wlength = 0;
printf("get item:%p, cfd:%d\n", item, item->fd);
}else if(events[i].events & EPOLLIN){
struct sock_item *item = reactor_lookup(r, clientfd);
printf("opr recv item:%p, item->fd:%d, connfd:%d\n", item, item->fd, clientfd);
assert(item != NULL && item->fd == clientfd);
char *rbuf = item->rbuffer;
char *wbuf = item->wbuffer;
assert(rbuf != NULL && wbuf != NULL);
ret = recv(item->fd, rbuf, BUFFER_LEN, 0);
if(ret == 0){
epoll_ctl(r->epfd, EPOLL_CTL_DEL, item->fd, &events[i]);
free(item->rbuffer); item->rbuffer = NULL;
free(item->wbuffer); item->wbuffer = NULL;
close(item->fd);
printf("clientfd:%d closed\n", item->fd);
item->fd = 0;
continue;
}
if(ret < 0) {
printf("clientfd:%d recv error ret:%d %s\n", item->fd, ret, strerror(errno));
continue;
}
item->rlength = ret;
memcpy(wbuf, rbuf, item->rlength);
item->wlength = ret;
printf("opr recv item:%p, clientfd:%d send:%s num:%d\n", item, item->fd, rbuf, ret);
ev.events = EPOLLOUT; //改为监听可写事件
ev.data.fd = item->fd;
epoll_ctl(r->epfd, EPOLL_CTL_MOD, item->fd, &ev);
}else if(events[i].events & EPOLLOUT){
struct sock_item *item = reactor_lookup(r, clientfd);
printf("opr send item:%p, item->fd:%d, connfd:%d\n", item, item->fd, clientfd);
assert(item != NULL && item->fd == clientfd);
char *wbuf = item->wbuffer;
assert(wbuf != NULL);
ret = send(item->fd, wbuf, item->wlength, 0);
if(ret <= 0){
char *err = strerror(errno);
printf("clientfd:%d closed ? ret %d errno %d:%s\n",
item->fd, ret, errno, err == NULL ? "unknow": err);
continue;
}
printf("opr send item:%p clientfd:%d send:%s num:%d\n", item, item->fd, wbuf, ret);
ev.events = EPOLLIN; //改为监听可写事件
ev.data.fd = item->fd;
epoll_ctl(r->epfd, EPOLL_CTL_MOD, item->fd, &ev);
}
}
}
#endif
return 0;
}
相关文章:
epoll单台设备支持百万并发连接
一些概念: linux下一切接文件,文件描述符fd,文件I/O(包含socket,文本文件等),I/O多路复用,reactor模型,水平触发,边沿触发,多线程模型,阻塞和非阻塞…...
网络字节序
文章目录网络字节序网络字节序 内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 网络数据流的地址统一按大端处理 发送主机通常将发送缓冲区中的数据按内存地址从低到高的…...

03- SVC 支持向量机做人脸识别 (项目三)
数据集描述: sklearn的lfw_people函数在线下载55个外国人图片文件夹数据集来精确实现人脸识别并提取人脸特征向量数据集地址: sklearn.datasets.fetch_lfw_people — scikit-learn 1.2.1 documentationPCA降维: pca PCA(n_components0.9) 数据拆分: X_train, X_test, y_tra…...

浅谈指向二维数组元素的指针变量
(1)指向数组元素的指针变量 例1.有一个3X4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值. 编写程序 1 #include <stdio.h>2 int main()3 {4 int a[3][4] { 1,3,5,7,9,11,13,15,17,19,21,23 };5 int *p;6 for (p a[0]; p < a[0] 12; p) …...
左右值引用和移动语义
文章首发公众号:iDoitnow 1. 左右值和左右值引用 什么是左值、右值呢?一种极不严谨的理解为:在赋值的时候,能够被放到等号左边的值为左值,放在右边的值为右值。例如: int sum(int x, int y){return x y;…...

一起学习用Verilog在FPGA上实现CNN----(七)全连接层设计
1 全连接层设计 1.1 Layer 进行线性计算的单元layer,原理图如图所示: 1.2 processingElement Layer中的线性计算单元processingElement,原理图如图所示: processingElement模块展开原理图,如图所示,包含…...

tomcat打debug断点调试
windows debug调试 jdk版本:1.8.0_181 tomcat版本:apache-tomcat-9.0.68.0 idea版本:2020.1 方法一 修改catalina.bat 在%CATALINA_HOME%\bin\catalina.bat中找到 set “JAVA_OPTS%JAVA_OPTS% -Djava.protocol.handler.pkgsorg.apache…...
如果持有互斥锁的线程没有解锁退出了,该如何处理?
文章目录如果持有互斥锁的线程没有解锁退出了,该如何处理?问题引入PTHREAD_MUTEX_ROBUST 和 pthread_mutex_consistent登场了结论:如果持有互斥锁的线程没有解锁退出了,该如何处理? 问题引入 看下面一段代码…...

信息论绪论
本专栏针包含信息论与编码的核心知识,按知识点组织,可作为教学或学习的参考。markdown版本已归档至【Github仓库:information-theory】,需要的朋友们自取。或者关注公众号【AIShareLab】,回复 信息论 也可获取。 文章目…...

Buffer Status Reporting(BSR)
欢迎关注同名微信公众号“modem协议笔记”。 以一个实网中的异常场景开始,大概流程是有UL data要发送,UE触发BSR->no UL grant->SR->no UL grant->trigger RACH->RACH fail->RLF->RRC reestablishment:简单描述就是UE触…...

代码随想录LeetCode | 单调栈问题
前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。 预:看到题目后的思路和实现的代码。 见:参考…...

C++之可调用对象、bind绑定器和function包装器
可调用对象在C中,可以像函数一样调用的有:普通函数、类的静态成员函数、仿函数、lambda函数、类的非静态成员函数、可被转换为函数的类的对象,统称可调用对象或函数对象。可调用对象有类型,可以用指针存储它们的地址,可…...

MongoDB--》文档查询的详细具体操作
目录 统计查询 分页列表查询 排序查询 正则的复杂条件查询 比较查询 包含查询 条件连接查询 统计查询 统计查询使用count()方法,其语法格式如下: db.collection.count(query,options) ParameterTypeDescriptionquerydocument查询选择条件optio…...

网络协议(六):网络层
网络协议系列文章 网络协议(一):基本概念、计算机之间的连接方式 网络协议(二):MAC地址、IP地址、子网掩码、子网和超网 网络协议(三):路由器原理及数据包传输过程 网络协议(四):网络分类、ISP、上网方式、公网私网、NAT 网络…...

热启动预示生态起航的Smart Finance,与深度赋能的SMART通证
2023年初加密市场的回暖,意味着各个赛道都将在新的一年里走向新的叙事。最近,我们看到GameFi赛道也在市场回暖的背景下,逐渐走出阴霾。从融资数据上看,1月获得融资的GameFi项目共12个,融资突破8000万美元,1…...

提分必练,中创教育PMP全真模拟题分享
湖南中创教育每日五题分享来啦,“日日行,不怕千万里;常常做,不怕千万事。”,每日五题我们练起来! 1、在系统测试期间,按已识别原因的类型或类别记录了失败测试的数量。项目经理首先需要从最大故…...

PID控制算法基础介绍
PID控制的概念 生活中的一些小电器,比如恒温热水器、平衡车,无人机的飞行姿态和飞行速度控制,自动驾驶等等,都有应用到 PID——PID 控制在自动控制原理中是一套比较经典的算法。 为什么需要 PID 控制器呢? 你一定用…...

Ajax 学习笔记
一、Ajax1.1 什么是AjaxAJAX Asynchronous JavaScript and XML(异步的JavaScript和XML)。Ajax是一种在无需加载整个网页的情况下,能够更新部分网页的技术,它不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术…...
力扣解法汇总1234. 替换子串得到平衡字符串
目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣 描述: 有一个只含有 Q, W, E, R 四种字符,且长度为 n 的字符串。 假如在该…...

C++关键字之const、inline、static
C 关键字总结 1.const const是 constant 的缩写,本意是不变的、不易改变的意思。在C中用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数使用如下: //修饰普通类型变量 const int a 7; int ba;…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...