epoll EPOLLOUT事件未触发

epoll与socket缓冲区的恩恩怨怨 本文深挖网络编程中的缓冲区,从什么是缓冲区出发,然后分析epoll 两个模式使用阻塞与非阻塞缓冲区的区别,最后设计一个合理的读写缓冲区的TCP三次握手成功,TCP连接成功建立后,操作系统内核会为每个连接创建配套的基础设施,包括 发送缓冲区,其大小可通过套接字选项改变。程序调用write函数时,实际所做的事是把数据从应用程序中拷贝到操作系统内核中。既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。这个地方就是 socket 缓冲区。 阅读详情

记录一次epoll事件未触发的经历,由于是tcp通信,使用了io多路复用epoll,客户端只发送数据,不处理服务端响应,服务端处理对应的事件,并且会响应消息给客户端,遇到了EPOLLOUT事件未触发的情况,进行排查。

经过查阅相关资料得知,socket通信时是全双工的,都有send、recv缓冲区,当recv缓冲区满了,就会响应ACK给对方,将发送窗口置为0,这个时候EPOLLOUT事件就无法触发,EPOLLOUT触发的前提条件是发送缓冲区可写,即对应的内核缓冲区为可写状态,这个时候不满足条件,EPOLLOUT事件就触发不了,响应消息就发送不过去。

附相关代码

客户端代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<errno.h>

#define MAXLINE 1024

void cli_send_msg(FILE *fp, int sockfd)
{
    char    sendline[MAXLINE], recvline[MAXLINE];

    //while (fgets(sendline, MAXLINE, fp) != NULL) {
    while(1) {
        memset(sendline, 97, MAXLINE);
        int n = write(sockfd, sendline, strlen(sendline));
        if (n < 0) {
            fprintf(stderr, "write err errno(%d) %s\n", errno, strerror(errno));
        }
        memset(sendline, 0, sizeof(sendline));

        usleep(10);
        //if (read(sockfd, recvline, MAXLINE) == -1) {
        //    fprintf(stderr, "read err errno(%d) %s\n", errno, strerror(errno));
        //    close(sockfd);
        //}
        //memset(recvline, 0, MAXLINE);

        //Fputs(recvline, stdout);
    }
}

int main(int argc, const char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    if (argc != 3) {
        fprintf(stderr, "usage: tcpcli <ip> <port>\n");
        return -1;
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(argv[2]));
    int ret = inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    if (ret <= 0) {
        fprintf(stderr, "inet_pton err\n");
    }
    int len = 4;
    int rcvbuf_size = 3145728;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_size, len);
    rcvbuf_size = 0;
    getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_size, &len);
    printf("recive buff size is %d\n", rcvbuf_size);
    ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    if (ret < 0) {
        fprintf(stderr, "connect err\n");
    }

    cli_send_msg(stdin, sockfd);

    return 0;
}

服务端代码 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<sys/types.h>


#define IPADDRESS "0.0.0.0"
#define PORT 7001
#define MAXSIZE 1024
#define LISTENQ 5
#define FDSIZE 1000
#define EPOLLEVENTS 1024

int rcv_c = 0;
int wt_c = 0;
int socket_bind(const char *ip, int port);

void do_epoll(int listenfd);

void handle_events(int epollfd, struct epoll_event *event, int num,int listenfd,char *buf);

void handle_accept(int epollfd, int listenfd);

void do_read(int epollfd, int connfd, char *buf);

void do_write(int epollfd, int connfd, char *buf);

void add_event(int epollfd, int connfd, int state);

void delete_event(int epollfd, int connfd, int state);

void modify_event(int epollfd, int connfd, int state);

int main(void)
{   
    int listenfd;
    listenfd = socket_bind(IPADDRESS, PORT);
    listen(listenfd, LISTENQ);
    do_epoll(listenfd);
    return 0;
}

int socket_bind(const char *ip, int port)
{   
    int fd;
    struct sockaddr_in servaddr;
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {   
        perror("socket create error: ");
        exit(1);
    }
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip ,&servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    int reuse  = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    if(bind(fd, (struct sockaddr*) &servaddr, sizeof(servaddr)) == -1)
    {   
        perror("bind error: ");
        exit(1);
    }
    return fd;
}

void do_epoll(int listenfd)
{   
    int epollfd;
    struct epoll_event events[EPOLLEVENTS];
    char buf[MAXSIZE];
    int ready = 0;
    
    memset(buf, 0, MAXSIZE);
    
    epollfd = epoll_create(FDSIZE);
    
    //添加服务监听事件
    add_event(epollfd,listenfd,EPOLLIN);
    //轮询处理事件
    for( ; ; )
    {
            printf("waiting...\n");
            ready = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
            if(ready == 0)
            {
                fprintf(stderr, "epoll_wait timeout\n");
                continue;
            }
            printf("wait_fd %d\n", ready);
            handle_events(epollfd, events, ready, listenfd, buf);
    }


}

void add_event(int epollfd, int fd, int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}

void delete_event(int epollfd, int fd, int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}

void modify_event(int epollfd, int fd, int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}

void handle_events(int epollfd, struct epoll_event *events, int num,int listenfd,char *buf)
{
    int fd = -1;
    for(int i = 0; i < num; i++ )
    {
        fd = events[i].data.fd;
        if (fd == listenfd && events[i].events & EPOLLIN )
        {
            handle_accept(epollfd, listenfd);
        }else if (events[i].events & EPOLLIN )
        {
            //处理读事件fd->buf
            do_read(epollfd, fd, buf);
        }else if ( events[i].events & EPOLLOUT )
        {
            //处理写事件buf->fd
            do_write(epollfd, fd, buf);
        } else if( events[i].events & EPOLLERR) {
            close(fd);
            fprintf(stderr, "close connect, error causeed!\n");
        }

    }

}

void handle_accept(int epollfd, int listenfd)
{   
    int connfd;
    struct sockaddr_in cliaddr;
    socklen_t socklen;
    socklen = sizeof(cliaddr);
    if ( (connfd = accept(listenfd,(struct sockaddr*)&cliaddr, &socklen)) == -1 )
    {   
        perror("accept error: ");
        return;
    }
    
    fprintf(stdout, "accept a client : %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
    
    //添加对客户端监听事件
    add_event(epollfd, connfd, EPOLLIN|EPOLLERR);
}

void do_read(int epollfd, int fd, char *buf)
{
    int n;
    n = recv(fd, buf, MAXSIZE, 0);

    if ( n == -1 )
    {
        char msg[64] = {0};
        int n = 0;
        if (errno == ECONNRESET) {
            n = sprintf(msg, "a client( %d ) close connection. ", fd);
            if (n < 0) {
                fprintf(stderr, "sprintf err\n");
            }
        }
        sprintf(msg+n, "[ERROR][%d]", errno);
        perror(msg);
        close(fd);
        delete_event(epollfd, fd, EPOLLIN);
    }else if ( n == 0 )
    {
        fprintf(stdout, "a client( %d ) close connection. [OK]\n", fd);
        close(fd);
        delete_event(epollfd, fd, EPOLLIN);
    }else
    {
        rcv_c++;
        printf("receive[%d]: %s\n", rcv_c, buf);
        modify_event(epollfd, fd, EPOLLOUT);
        printf("add write\n");
    }

}

void do_write(int epollfd, int fd, char *buf)
{
    int nwrite;
    nwrite = write(fd, buf, strlen(buf));
    if (nwrite == -1)
    {
        perror("write error:");
        close(fd);
        delete_event(epollfd, fd, EPOLLOUT);
    } else {
        wt_c++;
        printf("write count %d\n", wt_c);
        modify_event(epollfd, fd, EPOLLIN);
    }
    memset(buf, 0, MAXSIZE);
}

简约而不简单epoll之EPOLLOUT 本篇主要分析epoll边缘触发,通过模拟各种场景,来介绍EPOLLOUT,不涉及epoll底层源码实现。 一、前提 epoll_wait返回的条件 1、等待时间到期 2、发生信号事件,例如ctrl+c 3、The associated file is available for read(2) operations,如果注册了EPOLLIN, socket接收缓冲区,有新的数据到来 4... 阅读详情

相关推荐

epoll 水平触发与边缘触发

epoll也是实现I/O多路利用的一种方法,为了深入了解epoll的原理,我们先来看下epoll水平触发(level trigger, LT,LT为epoll的默认工作模式)与边缘触发(edge trigger, ET)两种工作模式。使用脉冲信号来解释LT和ET可能更加贴切。Level是指信号只需要处于水平,就一直会触发;而edge则是指信号为上升沿或者下降沿时触发。说得还有点玄乎,我们以生活中的一

Leo的博客 3万+

epoll边缘触发下客户端connect问题

epoll模型 Linux会维护一个建立连接的队列,当服务器第二次收到syn后,会将该连接放在建立连接的队列。 调用一次accpet会从该队列中取出一个连接用fd标识。 边缘触发下,多个连接在epoll_wait之前完成三次握手,边缘触发仅仅产生一次,若调用一次accpet则队列内仍有连接未取出,无法完成数据传输。 建议:监听套接字在epoll下使用电平触发 ...

weixin_46609502的博客 474

epoll的两种触发方式

epoll的两种触发方式

拓荒日记 7690

Socket/Epoll主要流程对socket错误码正确处理

Socket/Epoll主要流程对socket错误码正确处理 【https://blog.youkuaiyun.com/whycold/article/details/48179659】 注:转载一个同事的工作笔记。 以下是对相关流程和socket错误码正确处理的小结。 一. Socket/Epoll主要遇到的问题: (1) 非阻塞socket下,接收流程(recv/recvfrom)对错误(EIN...

突围 1649

关于epoll边缘触发模式(ET)下的EPOLLOUT触发

ET模式下,EPOLLOUT触发条件有: 1.缓冲区满-->缓冲区非满; 2.同时监听EPOLLOUT和EPOLLIN事件 时,当有IN 事件发生,都会顺带一个OUT事件; 3.一个客户端connect过来,accept成功后会触发一次OUT事件。 其中2最令人费解,内核代码这块有注释,说是一般有IN 时候都能OUT,就顺带一个,多给了个事件。。 以上,当只监听IN事件,

Tristone的专栏 5302

epoll_wait返回事件的测试

由epoll返回的事件可以有EPOLLIN,EPOLLOUT,EPOLLRDHUP,EPOLLPRI,EPOLLERR,EPOLLHUP。 关于EPOLLIN,EPOLLOUT,EPOLLPRI相信大家都熟悉,EPOLLIN代表有数据可读,EPOLLOUT代表可写,EPOLLPRI代表有带外数据可读。 接下来让我们关注一下EPOLLRDHUP,EPOLLERR,EPOLLHUP事件...

xiaowenmu1的博客 3959

Epoll原理解析

1、网络编程 //创建socket int s = socket(AF_INET, SOCK_STREAM, 0); //绑定 bind(s, ...) //监听 listen(s, ...) //接受客户端连接 int c = accept(s, ...) //接收客户端数据 recv(c, ...); //将数据打印出来 printf(...) Recv 是个阻塞方法,当程序运行到 Recv 时,它会一直等待,直到接收到数据才往下执行。那么阻塞的原理是什么? (1)工作队

weixin_42282999的博客 264

epoll的再次认识

使用mmap加速内核与用户空间的消息传递。 这 点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很 重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。 顺便看一下 EPOLLET 和 E

yolo模型训练找我,AI游戏合作找我 1412

qmessagebox关闭触发事件_Linux epoll边缘触发+非阻塞IO EAGAIN/EWOULDBLOCK处理

一直很困惑在Linux下非阻塞IO的返回值为EAGAIN/EWOULDBLOCK的情况怎么处理。经过自己的实践和查阅资料,终于搞懂了,下面总结下。其实这可以分为send写和recv两种情况。我们都知道边缘触发下EPOLLIN和EPOLLOUT只会触发一次,这就是处理的关键。写数据情况EPOLLOUT。写数据时,若一次发送的数据超过TCP发送缓冲区,则返EAGAIN/EWOULDBLOCK,表示数据...

weixin_39985365的博客 413

epoll_event 事件类型详解

本文详细解析了Linux epoll机制中的epoll_event事件类型,包括核心事件(EPOLLIN/OUT/RDHUP)和高级特性(EPOLLET/ONESHOT/EXCLUSIVE)。通过对比水平触发与边缘触发模式,分析各事件的触发条件和典型应用场景,如TCP服务器监听、数据收发等。文章提供了实用的代码示例和事件组合建议,帮助开发者高效使用epoll进行I/O多路复用编程。

weixin_42108533的博客 1317

保姆级教程:用Kvaser DB Editor从零创建你的第一个DBC文件(附CAN信号属性详解)

本文提供了一份详细的Kvaser DB Editor教程,指导用户从零开始创建DBC文件,并深入解析CAN信号属性的关键设置。通过步骤清晰的安装指南、信号属性配置详解和实用技巧,帮助工程师和学生快速掌握CAN总线通信的核心工具,提升工作效率。

weixin_27076351的博客 250

Linux服务器开发(LE和LT模式介绍)

之前关于介绍了EPOLLONESHOT:《inux服务器开发epoll的使用(EPOLLONESHOT的作用及如何使用)长连接短连接,粘包问题》;设置EPOLLONESHOT的目的是为了防止同时使用套接字connfd,每次重新使用connfd使用EPOLL_CTL_MOD将其重新加入epoll队列中才能被epoll_wait();检测到;回顾一下为什么使用EPOLLONESHOT;简单点来讲就是:同一时间不应该有多个工作线程使用同一个connfd,否则会产生网络错误;

hpx12345678的博客 1534

国密USB Key开发实战:SKF库调用全流程解析(附代码示例)

本文详细解析了国密USB Key开发中SKF库的调用全流程,涵盖从设备枚举、应用容器遍历到数字签名与验证的核心步骤。通过实战代码示例,深入探讨了SKF库的层次化模型、SM2签名实现细节以及纯软件验签方案,为开发电子签章、安全登录等合规应用提供了一套完整的调用开发指南。

oo7890的博客 524

epoll 边缘触发 (ET) 模式实战:3 个常见陷阱与规避方案

本文深入探讨了epoll边缘触发(ET)模式在高性能网络编程中的实战应用,揭示了3个常见陷阱及其规避方案。通过对比I/O多路复用技术中的select、poll和epoll,重点分析了ET模式下的数据读取完整性、非阻塞socket使用和事件处理等关键问题,提供了经过生产验证的最佳实践代码模板和性能优化技巧。

chuanzhuanxian8669的博客 376

LabVIEW波形图表时间轴设置:三种方法实现横坐标显示日期时间

在数据采集与设备监控场景中,波形图表的横坐标默认显示采样点序号而非日期时间,这给工程师分析历史数据带来不便。理解时间标识(Timestamp)的本质——一个自1904年起算的双精度秒数,是解决问题的关键。时间轴设置既是显示格式问题,更是数据层与显示层的协同设计。通过属性节点动态生成刻度标签、将时间标识数组直接作为X轴数据、或使用波形数据(Waveform)类型绑定t0与dt,分别适用于固定点数显示、非等间隔事件记录和等间隔连续采样等不同需求。合理选择方案并注意缓冲区预分配与时间连续性处理,可让波形图表准确呈

weixin_33416697的博客 253

QuickFix Java 实战(一)协议解析、环境搭建与首个应用

本文是QuickFix Java实战系列的第一篇,详细介绍了FIX协议的核心原理与QuickFix/J引擎的实战应用。内容涵盖FIX协议的标签化通信机制、QuickFix/J库的两种下载方法、协议定义文件解析,并通过一个完整的模拟订单发送示例,指导读者快速搭建首个FIX应用环境,实现消息生成与会话通信。

dog123的博客 495

一个nginx 回源限速的bug处理过程记录

一个生产环境,nginx占用cpu很高。 top - 15:04:19 up 1 day, 14:16, 5 users, load average: 13.26, 13.20, 13.20 Tasks: 881 total, 14 running, 865 sleeping, 0 stopped, 2 zombie Cpu(s): 21.3%us, 18.0%sy, 0...

weixin_33816946的博客 293

k230 +canMV+ LVGL控件 仿手表表盘触摸屏滚动、选中后弹窗效果完整示例程序

现在智能手表用的越来越多,其交互方式比较有特点,现在k230开发板上,基于LVGL(Light and Versatile Graphics Library)编写一个嵌入式GUI应用程序,使用LVGL配合触摸屏实现模仿智能手表的表盘滚动效果,实际效果如下:程序使用LVGL图形库和MediaManager程序,创建带有触摸屏交互的GUI应用。程序主要功能包括显示初始化、LVGL初始化、创建GUI界面(包含一个可滚动的按钮列表,每个按钮点击会弹出消息框),以及处理触摸屏输入。

weixin_47446386的博客 1175

EPOLLOUT例子 EPOLLOUT触发条件

下面的代码都在ET下工作 群里小伙伴没搞懂EPOLLOUT 再写2个例子; 2个例子都是回声服务器代码; 关于EPOLLET的基础 , 以及EPOLLIN|EPOLLOUT|EPOLLET 一起注册的例子:EPOLLET简单例子 下面2个例子都是关于EPOLLOUT, 第一个例子利用EPOLL_CTL_MOD来触发EPOLLOUT,这种方式不太好,需要利用一次系统调用epoll_w...

x 7871

【Zabbix实战之部署篇】Docker部署Zabbix+grafana监控平台

【Zabbix实战之部署篇】docker部署Zabbix+grafana监控平台

jks212454的博客 2577

HyperLynx(二十二)DDR(五)DDRx总线时序模型设计

DDR3 的数据选通信号与时钟信号的频率是一样的,在数据选通信号写操作时,控制器典型的输出方式是数据选通信号边沿与时钟近似对齐。该界面中显示了所有需要编辑的参数,包括地址/命令信号的时序、控制信号的时序、读和写操作的数据选通和数据信号时序、写操作的数据选通和数据掩码信号时序,以及建立和保持时间的降额参数。由于DDRx总线对时序的要求非常严格,随着速率的提升,时序的要求会更高。反之也是一样的,如果数据选通信号的上升沿在时钟信号上升沿的后面到达 DRAM,那么数据选通信号的下降沿就会在时钟信号的前面到达。

小幽余生不加糖 1936

有关于epollout事件触发的实验&epoll坑

epollout事件触发机制 & epoll坑 文章目录epollout事件触发机制 & epoll坑epoll机制实验服务器端客户端验证开始验证一:epoll底层红黑树只允许插入单个key结论验证二:EPOLLOUT的触发时机LTET 在网上看了很多关于epollout事件触发的文章,说的有点千奇百怪。一头雾水。之前自己做的实验也无法验证,一直没有思路,昨天晚上看epoll原理图...

心灵迷宫 3779

slew dreate,slew threshold与transition计算

​只看rise情况下,from pin取slew threshold10%-90%的区间,slew derate为1,计算得到transition为1.002e-10,to pin的slew threshold 为30%-70%,slew derate 0.5,从波形列表上取相近的值3.75e-10和8.75e-10,计算得到transition为(8.75e-10-3.75e-10)/0.5=1e-9,考虑精度和取值区间问题,基本符合工具计算的值,如下:​。如下示例,在.lib库中有定义以下内容。

m0_61544122的博客 793

彻底弄懂EPOLLOUT事件

epoll是linux下实现IO multiplex的利器。一般编程实现方式如下: 使用epoll_create创建一个epoll fd 使用epoll_ctl往epoll fd里添加需要监听的fd,并注册需要监听的事件(由于EPOLLLET边缘触发方式更加高效,所以一般都使用边缘触发方式) 使用epoll_wait等待事件,然后依次处理各个事件,反复循环 其最常用的网络事件为EPOLLIN和EPOLLOUT,EPOLLIN对应为有socket缓冲区数据可读(当又收到了对端的一些数据,就会触发;或者作为

专注探索c++,Linux等后台开发相关机制,欢迎关注,欢迎交流 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

epoll读写事件触发的条件

先上个人理解: EPOLLIN(读事件) EPOLLOUT(写事件) 以下触发条件只要满足一点即可。 LT模式: EPOLLIN触发条件: 处于可读状态。 从不可读状态变为可读状态。 EPOLLOUT触发条件: 处于可写状态。 从不可写状态变为可写状态。 说白了,LT模式就是能读的时候就可读,能写的时候就可写。这并不是废话。 什么叫可读状态?什么叫不可读状态? 一个水杯里有水...

回头太难 8181

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

epoll触发事件的分析

我们知道,可以将套接字放入到epoll中监听,当套接字上有事件发生时,epoll就会通知我们。epoll机制可以监听套接字上的以下6种事件: EPOLLIN:套接字可读 EPOLLOUT:套接字可写 EPOLLRDHUP:对端关闭了套接字,或者对端关闭了写 EPOLLPRI:套接字上有紧急数据到达 EPOLLHUP:对端挂断了套接字 那么,什么情况下会在套接字上触发以上

halfclear的博客 5777

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

上一篇: 【AVL】平衡二叉树
Perfect_chen
博客等级 码龄8年 2粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值