#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. dubbo的架构

    dubbo架构图如下所示: 节点角色说明: Provider: 暴露服务的服务提供方. Consumer: 调用远程服务的服务消费方. Registry: 服务注册与发现的注册中心. Monitor: ...

  2. phalcon——闪存消息

    使用两种适配器来定义消息传递给Flasher后的行为: (1)Phalcon\Flash\Direct:直接输出传递给flasher的消息 (2)Phalcon\Flash\Session:将消息临时 ...

  3. LeetCode 581. Shortest Unsorted Continuous Subarray (最短无序连续子数组)

    Given an integer array, you need to find one continuous subarray that if you only sort this subarray ...

  4. Kotlin——最详细的接口使用、介绍

    在Kotlin中,接口(Interface)的使用和Java中的使用方式是有很大的区别.不管是接口中的属性定义,方法等.但是定义方式还是相同的. 目录 一.接口的声明 1.接口的声明 关键字:inte ...

  5. [Bayesian] “我是bayesian我怕谁”系列 - Variational Inference

    涉及的领域可能有些生僻,骗不了大家点赞.但毕竟是人工智能的主流技术,在园子却成了非主流. 不可否认的是:乃值钱的技术,提高身价的技术,改变世界观的技术. 关于变分,通常的课本思路是: GMM --&g ...

  6. web端/h5端账号密码的安全性问题

    firefox一直提示让浏览器记住密码会有安全问题,但是一直未曾关注过到底是什么安全问题. 国庆节回家后发生的一件小事,让我深刻认识到让浏览器记住密码有多么不安全. 事情的起因是这样,家里wifi信号 ...

  7. 初探 ELK - 每天5分钟玩转 Docker 容器技术(89)

    在开源的日志管理方案中,最出名的莫过于 ELK 了.ELK 是三个软件的合称:Elasticsearch.Logstash.Kibana. Elasticsearch一个近乎实时查询的全文搜索引擎.E ...

  8. [C#]使用ILMerge将源DLL合并到目标EXE(.NET4.6.2)

    本文为原创文章,如转载,请在网页明显位置标明原文名称.作者及网址,谢谢! 本文主要是使用微软的ILMerge工具将源DLL合并到目标EXE,因此,需要下载以下工具: https://www.micro ...

  9. 伤不起的微信小程序

    前段时间不是很忙,刚好公司需要开发一个微信小程序,于是我就入坑了(此坑还是有点深滴,请备好干粮). 我是一名iOS开发工程师,个人觉得入门开发小程序的话,需要基本的web前端知识,比如说:代码的书写格 ...

  10. Android 开发笔记___图像视图__简单截屏

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...