因为et模式需要循环读取,但是在读取过程中,如果有新的事件到达,很可能触发了其他线程来处理这个socket,那就乱了。

EPOLL_ONESHOT就是用来避免这种情况。注意在一个线程处理完一个socket的数据,也就是触发EAGAIN errno时候,就应该重置EPOLL_ONESHOT的flag,这时候,新到的事件,就可以重新进入触发流程了。

注:EPOLL_ONESHOT的原理其实是,每次触发事件之后,就将事件注册从fd上清除了,也就不会再被追踪到;下次需要用epoll_ctl的EPOLL_CTL_MOD来手动加上才行。

服务器代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h> #include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <pthread.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;
} 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);
setnonblocking(fd);
} void reset_oneshot(int epollfd, int fd) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
} void* worker(void *arg) {
int sockfd = ((fds*)arg)->sockfd;
int epollfd = ((fds*)arg)->epollfd;
pthread_t pid = pthread_self(); printf("start new thread %u to recv data on fd: %d\n", pid, sockfd);
char buf[BUFFER_SIZE];
memset(buf, '\0', BUFFER_SIZE); while() {
int ret = recv(sockfd, buf, BUFFER_SIZE-, );
if (ret == ) {
close(sockfd);
printf("foreiner closed the connection\n");
break;
}
else if (ret < ) {
if (errno == EAGAIN) {
reset_oneshot(epollfd, sockfd);
printf("EAGAIN read later\n");
break;
}
}
else {
buf[ret] = '\0';
printf("thread %u get content: %s\n", pid, buf);
printf("thread %u about to sleep\n", pid);
sleep();
printf("thread %u back from sleep\n", pid);
}
}
//printf("end thread %u receiving data on fd: %d\n", pid, sockfd); } int main(int argc, char *argv[]) {
if (argc <= ) {
printf("usage: %s port_number [ip_address]\n", basename(argv[]));
return ;
}
int port = atoi(argv[]); int ret = ;
sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
if (argc >= ) {
const char *ip =argv[];
inet_pton(AF_INET, ip, &address.sin_addr);
}
else {
address.sin_addr.s_addr = INADDR_ANY;
}
address.sin_port = htons(port);
int listenfd = socket(PF_INET, SOCK_STREAM, );
assert(listenfd >= ); ret = bind(listenfd, (sockaddr*)&address, sizeof(address));
assert(ret != -); ret = listen(listenfd, );
assert(ret != -); epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create();
assert(epollfd != -); addfd(epollfd, listenfd, false); while() {
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -);
if (ret < ) {
printf("epoll failure\n");
break;
} for (int i=; i<ret; i++) {
int sockfd = events[i].data.fd;
if (sockfd == listenfd) {
sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (sockaddr*)&client_address,
&client_addrlength); addfd(epollfd, connfd, true);
printf("new connection is added to epollfd\n");
}
else if (events[i].events & EPOLLIN) {
pthread_t thread;
fds fds_for_new_worker;
fds_for_new_worker.epollfd = epollfd;
fds_for_new_worker.sockfd = sockfd;
// new thread
pthread_create(&thread, NULL, worker,
(void*)&fds_for_new_worker);
}
else {
printf("something else happened\n");
}
}
} close(listenfd);
return ; }

以下是使用telnet客户端发送的文本,匀速敲入代码:

$telnet 127.0.0.1
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hi1
hi2
hi3
hi4
hi5
hi6
hi7
hi8
Connection closed by foreign host.

以下是服务器的运行和输出:

$./epoll_oneshot
new connection is added to epollfd
start new thread to recv data on fd:
thread get content: hi1 thread about to sleep
thread back from sleep
thread get content: hi2
hi3 thread about to sleep
thread back from sleep
thread get content: hi4
hi5
hi6 thread about to sleep
thread back from sleep
thread get content: hi7 thread about to sleep
thread back from sleep
EAGAIN read later
start new thread to recv data on fd:
thread get content: hi8 thread about to sleep
thread back from sleep
EAGAIN read later
^C

最后用Ctrl+C来结束服务器。

可以看出,在hi7文本和hi8文本之间,服务器收到了EAGAIN,表示读取告一段落。而之后的线程id也换成了新线程id。在hi7之前,因为每次服务器sleep结束之后,都还有没有读完的数据,所以线程id始终没有变,始终是同一个线程处理数据。

另外,要注意的是,EPOLL_ONESHOT既可以在et下也可以在lt下设置。效果是一样的,都是同一个fd上面的相同事件只会触发一次。上面是et的例子,对于lt,如果设置了EPOLL_ONESHOT,也是需要把数据读完,然后重置event。而不能像原始lt编程方式那样依赖于事件通知来读取数据了。

epoll中et+多线程模式中很重要的EPOLL_ONESHOT实验的更多相关文章

  1. 制作类似ThinkPHP框架中的PATHINFO模式功能

    一.PATHINFO功能简述 搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大.这也是我接触学习的第一个框架.TP框架中的URL默认模式即是PathInfo ...

  2. (原创)拨开迷雾见月明-剖析asio中的proactor模式(二)

    在上一篇博文中我们提到异步请求是从上层开始,一层一层转发到最下面的服务层的对象win_iocp_socket_service,由它将请求转发到操作系统(调用windows api),操作系统处理完异步 ...

  3. Java多线程编程中Future模式的详解

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  4. Java多线程编程中Future模式的详解<转>

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  5. 多线程模式下高并发的环境中唯一确保单例模式---DLC双端锁

    DLC双端锁,CAS,ABA问题 一.什么是DLC双端锁?有什么用处? 为了解决在多线程模式下,高并发的环境中,唯一确保单例模式只能生成一个实例 多线程环境中,单例模式会因为指令重排和线程竞争的原因会 ...

  6. 细说.NET 中的多线程 (一 概念)

    为什么使用多线程 使用户界面能够随时相应用户输入 当某个应用程序在进行大量运算时候,为了保证应用程序能够随时相应客户的输入,这个时候我们往往需要让大量运算和相应用户输入这两个行为在不同的线程中进行. ...

  7. C#中的多线程 - 同步基础

    原文:http://www.albahari.com/threading/part2.aspx 文章来源:http://blog.gkarch.com/threading/part2.html 1同步 ...

  8. C#中的多线程 - 基础知识

    原文:http://www.albahari.com/threading/ 文章来源:http://blog.gkarch.com/threading/part1.html 1简介及概念 C# 支持通 ...

  9. ASP.NET Web Froms开发模式中实现程序集的延迟加载

    延迟加载是一个很大的诱惑,可以达到一些比较好的效果,比如: 1.在实体框架中,由于关联数据的数量和使用时机是不确定的,通过延迟加载,仅在使用的时候去执行关联数据的查询操作,减少无谓的数据查询操作,可以 ...

随机推荐

  1. 在Oracle 中使用CONNECT BY PRIOR START WITH 语句详解

    语法:connect by 是结构化查询中用到的,其基本语法如下: start with,connect by主要目的:从表中取出树状数据.可以假想成表中存成的各条数据是分别是树中的一个结点. sel ...

  2. Cocos2dx lua 3D实例代码

    用cocoside 创建一个项目 cocos2dx lua 项目即可 ,然后替换掉gamescene 就可以,具体效果还有函数的参数,相信大家一看就明白.简单说下ide 创建的 cocos lua 项 ...

  3. hdu4932 Miaomiao's Geometry

    这是一道搜索题,我们很容易得到目标值的上下界,然后就只能枚举了. 就是将x轴上的点排序之后从左到右依次考察每个点,每个点要么在线段的左端点,要么在线段的右端点. 点编号从0到n-1,从编号为1的点开始 ...

  4. Uva 116,单向TSP

    题目链接:https://uva.onlinejudge.org/external/1/116.pdf 和矩形嵌套,巴比伦塔差不多. 题意: 给出矩阵,这个矩阵是环形的,就是说第一行的上一行是最后一行 ...

  5. ubuntu下查看cpu信息

    查看CPU信息cat /proc/cpuinfo 几个cpu more /proc/cpuinfo |grep "physical id"|uniq|wc -l 每个cpu是几核( ...

  6. PowerDesigner导出Excel

    1.打开PowerDesigner,创建物理模型(Physical Data Model) 2.在PowerDesigner菜单栏中,依次点击“Tools ->Excute Commands-& ...

  7. [转]SIP穿越NAT&FireWall解决方案

    原文链接(也是转载)http://blog.csdn.net/yetyongjin/article/details/6881491.我修改了部分错字.   SIP从私网到公网会遇到什么样的问题呢? 1 ...

  8. application 网站计数器

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  9. BDC、CATT批量数据维护

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  10. JavaScript Replace 多个字符

    <html> <head> <title></title> <script language="javascript"> ...