因为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. 有三个线程T1 T2 T3,如何保证他们按顺序执行-转载

    T3先执行,在T3的run中,调用t2.join,让t2执行完成后再执行t3 在T2的run中,调用t1.join,让t1执行完成后再让T2执行 public class Test { // 1.现在 ...

  2. [充电]Code Review

    参考:http://blog.jobbole.com/83595/ http://www.kuqin.com/shuoit/20150319/345323.html 让 Code Review成为一种 ...

  3. 转载-python学习笔记之文件I/O

    Python 文件I/O 本章只讲述所有基本的的I/O函数,更多函数请参考Python标准文档. 打印到屏幕 最简单的输出方法是用print语句,你可以给它传递零个或多个用逗号隔开的表达式.此函数把你 ...

  4. Codeforces Round #373 (Div. 2) B

    Description Anatoly lives in the university dorm as many other students do. As you know, cockroaches ...

  5. 2016年11月26日 星期六 --出埃及记 Exodus 20:17

    2016年11月26日 星期六 --出埃及记 Exodus 20:17 "You shall not covet your neighbor's house. You shall not c ...

  6. java-cmd-命令行编译和运行java文件

    一.使用的工具 1.javac 2.java 二.命令 项目目录只这样的 D:/project/src/com/example/Child.java D:/project/src/com/exampl ...

  7. centos6.5 ssh安全优化,修改默认端口名,禁止root远程登录

    一.修改默认端口号 第一步: vi /etc/sysconfig/iptables 添加修改后的端口号的配置 -A INPUT -p tcp -m state --state NEW -m tcp - ...

  8. shutdown命令

    其实不需要使用软件,就可以实现自动关机或重启等功能的:Windows XP的关机是由Shutdown.exe程序来控制的,位于Windows\System32文件夹中.如 果想让Windows 200 ...

  9. form 表单

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

  10. wooyunAPI

    经常要爬去乌云的信息,但是每次都是硬爬,写完了发现乌云有提供API的,整理给大家: 1. WooYun Api是什么 通过WooYun开放的Api接口,其它网站或应用可以根据自己获取的权限调用WooY ...