#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/shm.h>
#define IP "127.0.0.1"
#define PORT 8888
#define PROCESS_NUM 4
#define MAXEVENTS 64 static int
create_and_bind ()
{
int fd = socket (PF_INET, SOCK_STREAM, );
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
inet_pton (AF_INET, IP, &serveraddr.sin_addr);
serveraddr.sin_port = htons (PORT);
bind (fd, (struct sockaddr *) &serveraddr, sizeof (serveraddr));
return fd;
} static int
make_socket_non_blocking (int sfd)
{
int flags, s;
flags = fcntl (sfd, F_GETFL, );
if (flags == -)
{
perror ("fcntl");
return -;
}
flags |= O_NONBLOCK;
s = fcntl (sfd, F_SETFL, flags);
if (s == -)
{
perror ("fcntl");
return -;
}
return ;
} void
worker (int sfd, int efd, struct epoll_event *events, int k, sem_t * sem)
{
/* The event loop */
struct epoll_event event;
// struct epoll_event *events;
efd = epoll_create (MAXEVENTS);
if (efd == -)
{
perror ("epoll_create");
abort ();
}
int epoll_lock = ;
while ()
{
int n, i;
int s;
event.data.fd = sfd;
event.events = EPOLLIN;
if ( == sem_trywait (sem))
{
//拿到锁的进程将listen 描述符加入epoll
if (!epoll_lock)
{
fprintf (stderr, "%d >>>get lock\n", k);
s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
if (s == -)
{
perror ("epoll_ctl");
abort ();
}
epoll_lock = ;
}
}
else
{
fprintf (stderr, "%d not lock\n", k);
//没有拿到锁的进程 将lisfd 从epoll 中去掉
if (epoll_lock)
{
fprintf (stderr, "worker %d return from epoll_wait!\n", k);
if (- == epoll_ctl (efd, EPOLL_CTL_DEL, sfd, &event))
{
if (errno == ENOENT)
{
fprintf (stderr, "EPOLL_CTL_DEL\n");
}
}
epoll_lock = ;
}
}
//epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
// fprintf(stderr, "ok\n");
//不能设置为-1 为了能让拿不到锁的进程再次拿到锁
n = epoll_wait (efd, events, MAXEVENTS, );
for (i = ; i < n; i++)
{
if (sfd == events[i].data.fd)
{
/* We have a notification on the listening socket, which means one or more incoming connections. */
struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr;
while ((infd = accept (sfd, &in_addr, &in_len)) > )
{
fprintf(stderr, "get one\n");
close (infd);
}
}
}
if (epoll_lock)
{
//这里将锁释放
sem_post (sem);
epoll_lock = ;
epoll_ctl (efd, EPOLL_CTL_DEL, sfd, &event);
}
}
} int
main (int argc, char *argv[])
{
int shmid;
sem_t *acctl;
//建立共享内存
shmid = shmget (IPC_PRIVATE, sizeof (sem_t), );
acctl = (sem_t *) shmat (shmid, , );
//进程间信号量初始化 要用到上面的共享内存
sem_init (acctl, , );
int sfd, s;
int efd;
// struct epoll_event event;
// struct epoll_event *events;
sfd = create_and_bind ();
if (sfd == -)
{
abort ();
}
s = make_socket_non_blocking (sfd);
if (s == -)
{
abort ();
}
s = listen (sfd, SOMAXCONN);
if (s == -)
{
perror ("listen");
abort ();
}
efd = ;
int k;
for (k = ; k < PROCESS_NUM; k++)
{
printf ("Create worker %d\n", k + );
int pid = fork ();
if (pid == )
{
struct epoll_event *events;
events = calloc (MAXEVENTS, sizeof (struct epoll_event));
worker (sfd, efd, events, k, acctl);
break;
}
}
int status;
wait (&status);
close (sfd);
return EXIT_SUCCESS;
}
/*
* 这里处理惊群 用到了进程的锁(信号量, 共享内存), 根据试验的结果多个进程时accept接收客户端连接的效率并没有提高太多
* 但是处理其他可读可写(非监听描述符)时, 要比单个进程要快很多。
*/

  在早期的kernel中, 多线程或多进程调用accept就会出现如下情况, 当前多个进程阻塞在accept中, 此时有客户端连接时, 内核就会通知阻塞在accept的所有进程, 这时就会造成惊群现象, 也就是所有accept都会返回 但是只有一个能拿到有效的文件描述符, 其他进程最后都会返回无效描述符。但在linux kernel 版本2.6 以上时, accept惊群的问题已经解决, 大致方案就是选一个阻塞在accept的进程返回。

  但是在IO复用中, select/poll/epoll 还是存在这种现象,其原因就是这些阻塞函数造成了以上同样的问题。这里就给出了类似Nginx的解决方案, 给监听描述符竞争枷锁, 保证只有一个进程处理监听描述符。 这里还可以控制锁的频率,如果一个进程接受连接的数量达到一定数量就不再申请锁。  这里要注意的是epoll_create的位置要在fork之后的子进程中, 这是因为若在父进程中create 那么fork之后子进程保留这个描述符副本,epoll_create其实是内核中建立的文件系统 保存在内核中, 那么其子进程都会共用这个文件系统, 那么多任务的epoll_ctl就会出现问题。子进程中建立各自的epoll fd 就可以避免这种情况。

epoll 惊群处理的更多相关文章

  1. accept与epoll惊群 转载

    今天打开 OneNote,发现里面躺着一篇很久以前写的笔记,现在将它贴出来. 1. 什么叫惊群现象 首先,我们看看维基百科对惊群的定义: The thundering herd problem occ ...

  2. epoll惊群原因分析

    考虑如下情况(实际一般不会做,这里只是举个例子): 在主线程中创建一个socket.绑定到本地端口并监听 在主线程中创建一个epoll实例(epoll_create(2)) 将监听socket添加到e ...

  3. 源码剖析Linux epoll实现机制及Linux上惊群

    转载:https://blog.csdn.net/tgxallen/article/details/78086360 看源码是对一个技术认识最直接且最有效的方式了,之前用Linux Epoll做过一个 ...

  4. Linux惊群效应详解

    Linux惊群效应详解(最详细的了吧)   linux惊群效应 详细的介绍什么是惊群,惊群在线程和进程中的具体表现,惊群的系统消耗和惊群的处理方法. 1.惊群效应是什么?        惊群效应也有人 ...

  5. nginx&http 第三章 惊群

    惊群:概念就不解释了. 直接说正题:惊群问题一般出现在那些web服务器上,Linux系统有个经典的accept惊群问题,这个问题现在已经在内核曾经得以解决,具体来讲就是当有新的连接进入到accept队 ...

  6. NGINX怎样处理惊群的

    写在前面 写NGINX系列的随笔,一来总结学到的东西,二来记录下疑惑的地方,在接下来的学习过程中去解决疑惑. 也希望同样对NGINX感兴趣的朋友能够解答我的疑惑,或者共同探讨研究. 整个NGINX系列 ...

  7. “惊群”,看看nginx是怎么解决它的

    在说nginx前,先来看看什么是“惊群”?简单说来,多线程/多进程(linux下线程进程也没多大区别)等待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群.可以想见,效率很 ...

  8. Linux网络编程“惊群”问题总结

    1.前言 我从事Linux系统下网络开发将近4年了,经常还是遇到一些问题,只是知其然而不知其所以然,有时候和其他人交流,搞得非常尴尬.如今计算机都是多核了,网络编程框架也逐步丰富多了,我所知道的有多进 ...

  9. Nginx学习之一-惊群现象

    惊群问题(thundering herd)的产生 在建立连接的时候,Nginx处于充分发挥多核CPU架构性能的考虑,使用了多个worker子进程监听相同端口的设计,这样多个子进程在accept建立新连 ...

随机推荐

  1. Servlet 笔记-读取表单数据

    Servlet 处理表单数据,这些数据会根据不同的情况使用不同的方法自动解析: getParameter():您可以调用 request.getParameter() 方法来获取表单参数的值. get ...

  2. RabbitMQ 笔记-基本概念

    ConnectionFactory.Connection.Channel ConnectionFactory.Connection.Channel,这三个都是RabbitMQ对外提供的API中最基本的 ...

  3. ASP动态网站建设之连接数据库相关操作

    连接数据库: string str = @"server=服务器名称;Integrated Security=SSPI;database=数据库名称;"; 注意封装公共类,将常用重 ...

  4. [原创]浅谈JAVA在ACM中的应用

    由于java里面有一些东西比c/c++方便(尤其是大数据高精度问题,备受广大ACMer欢迎),所以就可以灵活运用这三种来实现编程,下面是我自己在各种大牛那里总结了一些,同时加上自己平时遇到的一些jav ...

  5. python常用模块上篇

    python常见模块 分两篇分别介绍下述模块 time模块 random模块 hashlib模块 os模块 sys模块 logging模块 序列号模块 configparser模块 re模块 time ...

  6. 【推荐】地推统计结算工具SDK,手机开发首选

    地推是推广app的一种重要手段,同时地推结算对地推统计的精度的要求非常高,而openinstall就是一款符合要求的地推统计结算工具.它不仅多渠道统计能力强,安装设备识别精准,渠道统计精度高.还支持地 ...

  7. npm常用命令及版本号浅析

    npm 包管理器的常用命令 测试环境为node>=8.1.3&&npm>=5.0.3 1, 首先是安装命令 //全局安装 npm install 模块名 -g //本地安装 ...

  8. 将非常规Json字符串转换为常用的json对象

    如下所示,这是一个已经转换为Json对象的非常规Json字符串,原来是一个Json类型的字符串,在转换为Json对象时,查询资料发现有两种转换法,.parse()和.eval()方法,但是前辈们都极其 ...

  9. 对SQL Server事务的4个隔离级别的理解

    事务隔离级别的简单理解   ANSI/ISO SQL标准定义了4种事务隔离级别,这些隔离级别是根据事务并行出现的4个"现象"定义的. 4个现象是: 1.更新丢失(Lost Upda ...

  10. .net core 同时实现网站管理员后台、会员、WebApi登录及权限控制

    我们在开网站信息系统时,常常有这样几个角色,如后台的管理员,前台的会员,以及我们各种应用的WebAPI 都需要进行登录操作及权限控制,那么在.net core如何进行设计呢. 首先我使用的是.net ...