不使用epoll时,惊群效应在linux内核在大于2.6的版本中已经不会出现。
使用epoll还是会出现惊群
看了一圈只看见有多线程下的惊群
放一个多进程下的惊群,作为demo,sleep很重要!
#include "my_socket.h"
#include <sys/epoll.h>
#define MAX_PROCESS 4
#define SERV_PORT 9527
#define OPEN_MAX 1024
#define MESSAGE_SIZE 10
int main(int argc,char *argv[]){
int listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt));
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERV_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
Listen(listenfd, 128);
//将新建立的socket设置成非阻塞,经测试,这里设置成阻塞不影响下面依然会惊群。
int flags= fcntl(listenfd,F_GETFL,0);
flags |= O_NONBLOCK;
fcntl(listenfd,F_SETFL,flags);
//fork创建多个子进程,
//这里先用一个for把需要的子进程创建出来,再使用。(饿汉)
pid_t subpid = -1;
int k=0;
for(k =0;k<MAX_PROCESS;++k){
if(subpid!=0) {
subpid=fork();
printf("%d\n",subpid);
}
}
if(subpid==0){//在子进程中进行监听并对监听到的事件进行处理。
//epoll初始化,每个子进程都有自己的epoll实例,且监听同一个socket:listenfd。
epfd = epoll_create(256);
if(epfd == -1) perr_exit("epoll_create error:");
tempep.events = EPOLLIN;
tempep.data.fd = listenfd;
//向epfd监听红黑树加入对listenfd的读监听
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &tempep);
if(ret == -1) perr_exit("epoll_ctl_init error:");
//while中所需参数
int i,j,connfd,nready,tempfd,readsize;
struct sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
char client_ip[BUFSIZ], readbuf[MESSAGE_SIZE];
while(1){
//阻塞监听epfd监听红黑树中的fd
nready = epoll_wait(epfd, eps, OPEN_MAX, -1);
if(nready == -1) perr_exit("epoll_wait error:");
sleep(1);//需要sleep给各个子进程一个反应时间,否则不会引起惊群效应。
for(i=0;i< nready;++i){
//if(!(eps[i].events & EPOLLIN)) continue; //对返回的结构体一次判断是否有读事件,没有则直接continue
if(eps[i].data.fd == listenfd){ //判断所返回的结构体(监听到的事件)的fd是不是listenfd 是的话,说明该事件希望建立新的tcp连接。
printf("listen event... \n");
connfd = Accept(listenfd, (struct sockaddr*)&client_addr,&client_addr_len);
//将新建立的socket设置成非阻塞
int flag = fcntl(connfd,F_GETFL,0);
flag |= O_NONBLOCK;
fcntl(connfd,F_SETFL,flag);
//返回的已建立连接的connfd,加入到epfd监听红黑树中
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip)),
ntohs(client_addr.sin_port));
// printf("cfd %d --- client %d \n",connfd, ++num);
tempep.events = EPOLLIN | EPOLLET; tempep.data.fd = connfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD,connfd,&tempep);
if(ret == -1) perr_exit("epoll_ctl_add error:");
}
else if(eps[i].events & EPOLLIN){//说明eps[i]监听到的事件不是读事件,应该是已经建立连接的fd发来的数据通信事件。
do{
tempfd = eps[i].data.fd;
memset(readbuf,0,MESSAGE_SIZE);
readsize = Readn(tempfd,readbuf, MESSAGE_SIZE);
//readsize = Read(tempfd,readbuf,sizeof(readbuf));
if(readsize ==0){
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, tempfd, NULL);
if(ret==-1) perr_exit("epoll_ctl_Del error:");
Close(tempfd);
printf("client[%d] closed connection\n",tempfd);
}
else{
if(readsize == MESSAGE_SIZE){printf("need more buffer");}//缓冲区满了,用其他的缓冲方法,比如缓冲队列。
Write(STDOUT_FILENO, readbuf, readsize );
for(j = 0;j<readsize;++j) readbuf[j] = toupper(readbuf[j]);
Write(tempfd, readbuf, readsize);
Write(STDOUT_FILENO, readbuf, readsize);
}
}while(readsize<0 && errno==EINTR);
if(readsize<0){
switch(errno){
case EAGAIN:
break;
default:
break;
}
}
}
}
}
Close(epfd);
}else{//subpid!=0,即在主线程中回收所有子进程。
do{
subpid = waitpid(-1,NULL,0);
}while(subpid!=-1);
}
Close(listenfd);
return 0;
}