EPOLLONESHOT事件

我们先从epoll谈起

可以看看之前我的博客

EPOLLONESHOT事件

我们使用ET模式下工作,一个socket上的某个事件还是可能被多次触发。这在并发程序中就会引起一个问题。比如一个线程(或进程)在读取完某个socket上的数据后开始处理这些数据,而在数据处理的过程中该socket上又有新数据可读(EPOLLIN再次触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个或多个线程同时操作一个socket的局面。我们在写服务器的时候当然不想出现这种情况,我们想看到的当然是一个socket在任何时刻都只被一个线程处理。我们就会用到epoll中的EPOLLONESHOT事件。

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl重置该文件描述符上注册的EPOLLONESHOT事件。注册之后,当一个线程正在处理某个socket的时候,其它线程是不可能有机会操作这个socket注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,确保当有新的事件来临时,它的EPOLLIN事件能够触发,并且可以让其它线程有机会继续处理这个socket。

注意

千万不能将服务器端的监听socket设置为EPOLLONESHOT,否则程序只能处理一个客户连接! 因为后续的客户连接请求将不再触发listenfd上的EPOLLIN事件。

头文件

#ifndef _HEAD_H
#define _HEAD_H
#include<iostream>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<netdb.h>
#include<sys/epoll.h>
#include<pthread.h>

void my_err(std::string str, int line)
{
    fprintf(stderr, "line:  %d\n", line);
    std::cerr << str << std::endl;
    exit(0);
}

#endif
#include"head.h"

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 1024

struct fds 
{
    int epollfd;
    int sockfd;
};

/*设置为非阻塞*/
int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

/*将fd上的EPOLLIN和EPOLLET事件注册到epollfd指示的epoll内核事件表中,参数oneshot指定是否注册fd上的EPOLLONESHOT事件*/
void addfd(int epollfd, int  fd, bool oneshot)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET; 
    if(oneshot)
        event.events |= EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
}

/*重置fd上的事件。这样操作之后,尽管fd上的EPOLLONESHOT事件被注册,但是操作系统仍然会触发fd上的EPOLLIN事件,且只触发一次*/
void reset_oneshot(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
}

/*工作线程*/
void* worker(void *arg)
{
    int sockfd = ( (fds*)arg )->sockfd;
    int epollfd = ( (fds*)arg )->epollfd;
    std::cout << "start new thread to receive data on fd :" << sockfd << "\n";
    char buf[BUFFER_SIZE] = {0};
    /*循环读取sockfd上的数据,直到遇到EAGAIN错误*/
    while(1)
    {
        int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
        if(ret == 0)
        {
            close(sockfd);
            std::cout << "foreiner closed the connection\n";
            break;
        }
        else if(ret < 0)
        {
            if(errno == EAGAIN)
            {
                reset_oneshot(epollfd, sockfd); //处理完必须重新注册事件,不然该套接字的事件就一直得不到处理
                std::cout << "read later\n";
                break;
            }
        }
        else
        {
            std::cout << "get content: " << buf << std::endl;
            /*模拟数据处理过程*/
            sleep(5);
        }
        
    }
    std::cout << "end thread receving data on fd: " << sockfd << "\n";
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值