进程池模型需要通过system V IPC机制或管道、信号、文件锁等进行同步。以下是进程池的一般模型。
Linux惊群现象:
惊群:惊群是指多个进程/线程在等待同一资源时,每当资源可用,所有的进程/线程都来竞争资源的现象。
accept、select、epoll实现进程池模型时的惊群现象:
1).Linux多进程accept系统调用的惊群问题(注意,这里没有使用select、epoll等事件机制),在linux 2.6版本之前的版本存在,在之后的版本中解决掉了。
2).使用select epoll等事件机制,在linux早期的版本中,惊群问题依然存在(epoll_create在fork之前)。 原因与之前单纯使用accept导致惊群,原因类似。Epoll的惊群问题,同样在之后的某个版本部分解决了。
3).Epoll_create在fork之后调用,不能避免惊群问题,Nginx使用互斥锁,解决epoll惊群问题。
进程池中的进程调用accept如果阻塞在同一个listen队列中,有可能产生惊群现象(取决于Linux版本):当一connect到达时,所有accept都会唤醒,但只有一个accept会返回正确结果,其他的都会返回错误码。
accept多进程实现:让一个进程bind一个网络地址(可能是AF_INET,AF_UNIX或者其他任何你想要的),然后fork这个进程自己:
int s = socket(...)
bind(s, ...)
listen(s, ...)
fork()
Fork自己几次之后,每个进程阻塞在accept()函数这里
for(;;) {
int client = accept(...); //子进程阻塞在这了
if (client < 0) continue;
...
}
在较老的unix系统中,当有连接到来时,accept()在每个阻塞在这的进程里被唤醒。但是,只有这些进程中的一个能够真正的accept这个连接,其他的进程accept将返回EAGAIN惊群造成结果是系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。
实现代码:
服务器端代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFLEN 1024
#define PIDNUM 3
/*******************并发服务器模型之一:预先分配好了进程的个数**********************/
static void handle_fork(int sockfd){
int newfd;
struct sockaddr_in c_addr;
char buf[BUFLEN];
socklen_t len;
time_t now;
while(1){
len = sizeof(struct sockaddr);
if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){
perror("accept");
exit(errno);
}else
printf("\n*****************通信开始***************\n");
printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
/******处理客户端请求*******/
bzero(buf,BUFLEN);
len = recv(newfd,buf,BUFLEN,0);
if(len >0 && !strncmp(buf,"TIME",4)){
bzero(buf,BUFLEN);
/*获取系统当前时间*/
now = time(NULL);
/*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/
sprintf(buf,"%24s\r\n",ctime(&now));
//******发送系统时间*******/
send(newfd,buf,strlen(buf),0);
}
/*关闭通讯的套接字*/
close(newfd);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in s_addr;
unsigned int port, listnum;
pid_t pid[PIDNUM];
/*建立socket*/
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(errno);
}else
printf("socket create success!\n");
/*设置服务器端口*/
if(argv[2])
port = atoi(argv[2]);
else
port = 4567;
/*设置侦听队列长度*/
if(argv[3])
listnum = atoi(argv[3]);
else
listnum = 3;
/*设置服务器ip*/
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(port);
if(argv[1])
s_addr.sin_addr.s_addr = inet_addr(argv[1]);
else
s_addr.sin_addr.s_addr = INADDR_ANY;
/*把地址和端口帮定到套接字上*/
if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
perror("bind");
exit(errno);
}else
printf("bind success!\n");
/*侦听本地端口*/
if(listen(sockfd,listnum) == -1){
perror("listen");
exit(errno);
}else
printf("the server is listening!\n");
/*处理客户端的连接*/
int i = 0;
for(i = 0; i < PIDNUM; i++){
pid[i] = fork();
if(pid[i] == 0)
handle_fork(sockfd);
}
/*关闭服务器的套接字*/
close(sockfd);
return 0;
}
客户端代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFLEN 1024
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in s_addr;
socklen_t len;
unsigned int port;
char buf[BUFLEN];
/*建立socket*/
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(errno);
}else
printf("socket create success!\n");
/*设置服务器端口*/
if(argv[2])
port = atoi(argv[2]);
else
port = 4567;
/*设置服务器ip*/
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(port);
if (inet_aton(argv[1], (struct in_addr *)&s_addr.sin_addr.s_addr) == 0) {
perror(argv[1]);
exit(errno);
}
/*开始连接服务器*/
if(connect(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr)) == -1){
perror("connect");
exit(errno);
}else
printf("conncet success!\n");
/******缓冲区清零*******/
bzero(buf,BUFLEN);
strcpy(buf,"TIME");
/******发送消息*******/
send(sockfd,buf,strlen(buf),0);
/******缓冲区清零*******/
bzero(buf,BUFLEN);
/******接收消息*******/
len = recv(sockfd,buf,BUFLEN,0);
if(len > 0)
printf("服务器的系统时间是:%s\n",buf);
close(sockfd); /*关闭连接*/
return 0;
}