Chinaunix首页 | 论坛 | 博客
  • 博客访问: 46273
  • 博文数量: 13
  • 博客积分: 1857
  • 博客等级: 上尉
  • 技术积分: 100
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-05 10:56
文章分类
文章存档

2011年(10)

2010年(3)

最近访客

分类: C/C++

2010-08-25 10:57:49

从头编写高性能服务程序(一)

从头编写高性能服务程序1-单进程阻塞

标题有些大.其实自己也是在不断的摸索
觉得BLOG的作用应该和日记一样
把自己获取到的东西记录下来
到时候回头看能找到自己跌倒过的地方避免再次跌倒
同时别人看的时候也能避开这些地方
所以在自己开始写服务程序一段时间之后
把原来的代码整理一下一次次的发上来
从零开始完全手写一个服务,经过慢慢的演化
期间的收获还是很多的

最开始,
程序是从最简单的socket连接开始
只有一个进程,创建socket之后开始listen
然后accept连上来的链接
整个程序是阻塞的.当一个链接上来之后
其他链接是无法连入的
之后当程序处理完并close之后,后面的才能被处理
这个例子是不停的接受客户端发过来的字符并且显示在服务端的console
然后客户端也不会被close.
只是演示概念
外面一个循环是不停的去accept新的连接
里面一个是循环处理已经accept上来链接的数据
由于没有close,所以外面一个循环是没意义的.呵呵
只是大家知道概念就OK了.

  1. #include<sys/socket.h>
  2. #include<netinet/in.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. int main(){
  7.     int listen_fd,accept_fd,flag;
  8.     struct sockaddr_in my_addr,remote_addr;
  9.     if ( (listen_fd = socket( AF_INET,SOCK_STREAM,0 )) == -1 ){
  10.         perror("create socket error");
  11.         exit(1);
  12.     }
  13.     if ( setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag)) == -1 ){
  14.         perror("setsockopt error");
  15.     }
  16.     my_addr.sin_family = AF_INET;
  17.     my_addr.sin_port = htons(3389);
  18.     my_addr.sin_addr.s_addr = INADDR_ANY;
  19.     bzero(&(my_addr.sin_zero),8);
  20.     if ( bind( listen_fd, (struct sockaddr *)&my_addr,
  21.                 sizeof(struct sockaddr_in)) == -1 ) {
  22.         perror("bind error");
  23.         exit(1);
  24.     }
  25.     if ( listen( listen_fd,1 ) == -1 ){
  26.         perror("listen error");
  27.         exit(1);
  28.     }
  29.     for(;;){
  30.         int addr_len = sizeof( struct sockaddr_in );
  31.         accept_fd = accept( listen_fd,
  32.                                 (struct sockaddr *)&remote_addr,&addr_len );
  33.         for(;;){
  34.             char in_buf[1024];
  35.             memset(in_buf, 0, 1024);
  36.             recv( accept_fd ,&in_buf ,1024 ,0 );
  37.             printf( "accept:%s\n", in_buf );
  38.         }
  39.     }
  40.     return 0;
  41. }

从头编写高性能服务程序2-双进程阻塞

上一个模型的缺点就是
当链接数据没被处理完时,
整个服务是无法处理新链接的
这种比较适合单词处理速度很快
而且并发量=0的服务
也就是服务端和客户端1:1的样子
我丢给你请求,你给我数据,我再给你请求

这对于一个比较高性能的服务肯定是不可接受的
所以我们产生了一个想法
用父子进程,父进程管listen
有链接进来就fork一个子进程
子进程accept之后去处理.处理完之后就退出
注意,下面这段代码还是没close
所以上面所说的只能是做了一半
大家理解概念就好

下载:
  1. #include<sys/socket.h>
  2. #include<netinet/in.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. int main(){
  7.     int listen_fd,accept_fd,flag;
  8.     struct sockaddr_in my_addr,remote_addr;
  9.     if ( (listen_fd = socket( AF_INET,SOCK_STREAM,0 )) == -1 ){
  10.         perror("create socket error");
  11.         exit(1);
  12.     }
  13.     if ( setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag)) == -1 ){
  14.         perror("setsockopt error");
  15.     }
  16.     my_addr.sin_family = AF_INET;
  17.     my_addr.sin_port = htons(3389);
  18.     my_addr.sin_addr.s_addr = INADDR_ANY;
  19.     bzero(&(my_addr.sin_zero),8);
  20.     if ( bind( listen_fd, (struct sockaddr *)&my_addr,
  21.                 sizeof(struct sockaddr_in)) == -1 ) {
  22.         perror("bind error");
  23.         exit(1);
  24.     }
  25.     if ( listen( listen_fd,1 ) == -1 ){
  26.         perror("listen error");
  27.         exit(1);
  28.     }
  29.     int pid;
  30.     pid=fork();
  31.     for(;;){
  32.         if( pid==0 ){
  33.             int addr_len = sizeof( struct sockaddr_in );
  34.             accept_fd = accept( listen_fd,
  35.                     (struct sockaddr *)&remote_addr,&addr_len );
  36.             for(;;){
  37.                 char in_buf[1024];
  38.                 memset(in_buf, 0, 1024);
  39.                 recv( accept_fd ,&in_buf ,1024 ,0 );
  40.                 printf( "accept:%s\n", in_buf );
  41.             }
  42.         }
  43.         else{
  44.             //do nothing
  45.         }
  46.     }
  47.     return 0;
  48. }

fork产生子进程之后返回值
如果是0,那么这个进程就是子进程
如果-1,就是错误
否则就是产生子进程的pid,可以用这个pid来控制子进程,比如kill掉

大家实际用起来会发现和单进程阻塞一样
只能处理一个客户端连接.根本没好处么
呵呵.其实只是演示一下fork
然后好处还是有的.如果子进程因为处理request而挂了
主进程可以控制,然后再产生一个
所以这种样子比单进程要可靠一点
一个干活.一个监控

那么下一步就是对应每个链接产生进程


从头编写高性能服务程序3-多进程阻塞

多fork几次
那fork到底放在哪里呢?
因为阻塞的特性会中断进程的运行知道阻塞情况接触
所以我们把fork放在accept之后
这样有实际链接之后才会产生子进程
现在这个程序在多个客户端连上来之后都能有反应了

下载:
  1. #include<sys/socket.h>
  2. #include<netinet/in.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. int main(){
  7.     int listen_fd,accept_fd,flag;
  8.     struct sockaddr_in my_addr,remote_addr;
  9.     if ( (listen_fd = socket( AF_INET,SOCK_STREAM,0 )) == -1 ){
  10.         perror("create socket error");
  11.         exit(1);
  12.     }
  13.     if ( setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag)) == -1 ){
  14.         perror("setsockopt error");
  15.     }
  16.     my_addr.sin_family = AF_INET;
  17.     my_addr.sin_port = htons(3389);
  18.     my_addr.sin_addr.s_addr = INADDR_ANY;
  19.     bzero(&(my_addr.sin_zero),8);
  20.     if ( bind( listen_fd, (struct sockaddr *)&my_addr,
  21.                 sizeof(struct sockaddr_in)) == -1 ) {
  22.         perror("bind error");
  23.         exit(1);
  24.     }
  25.     if ( listen( listen_fd,1 ) == -1 ){
  26.         perror("listen error");
  27.         exit(1);
  28.     }
  29.     int pid;
  30.     int addr_len = sizeof( struct sockaddr_in );
  31.     for(;;){
  32.         accept_fd = accept( listen_fd,
  33.                     (struct sockaddr *)&remote_addr,&addr_len );
  34.         pid=fork();
  35.         if( pid==0 ){
  36.             for(;;){
  37.                 char in_buf[1024];
  38.                 memset(in_buf, 0, 1024);
  39.                 recv( accept_fd ,&in_buf ,1024 ,0 );
  40.                 printf( "accept:%s\n", in_buf );
  41.             }
  42.         }
  43.         else{
  44.             //manager the process
  45.         }
  46.     }
  47.     return 0;
  48. }

从头编写高性能服务程序4-单进程非阻塞select

前面都是阻塞的
当我们在accept或者recv的时候
文件描述符如果没有就绪
整个进程就会被挂起
浪费啊…如果只有就绪的时候才去处理,然后调用之后立刻返回
我们就可以继续做下面的工作了
所以我们引入非阻塞概念
记住,试用setsockopt是无法设置socket接口非阻塞的
只能用fd专用的fcntl去设置fd的非阻塞性质
需要include头文件fcntl.h

还有一点,select所监听的fd_set
在每次select之后就会被清掉
所以一开始我发现每次accept一个数据之后就再也获取不到数据了
然后才发现这点,而这个在我找的资料里面都没提到
所以要把FD_SET放在每次循环里面
我想这也是select性能比较低的原因之一

下载:
  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <fcntl.h>
  7. int main(){
  8.     int listen_fd,accept_fd,flag;
  9.     struct sockaddr_in my_addr,remote_addr;
  10.     if ( (listen_fd = socket( AF_INET,SOCK_STREAM,0 )) == -1 ){
  11.         perror("create socket error");
  12.         exit(1);
  13.     }
  14.     if ( setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag)) == -1 ){
  15.         perror("setsockopt error");
  16.     }
  17.     int flags = fcntl(listen_fd, F_GETFL, 0);
  18.     fcntl(listen_fd, F_SETFL, flags|O_NONBLOCK);
  19.     my_addr.sin_family = AF_INET;
  20.     my_addr.sin_port = htons(3389);
  21.     my_addr.sin_addr.s_addr = INADDR_ANY;
  22.     bzero(&(my_addr.sin_zero),8);
  23.     if ( bind( listen_fd, (struct sockaddr *)&my_addr,
  24.                 sizeof(struct sockaddr_in)) == -1 ) {
  25.         perror("bind error");
  26.         exit(1);
  27.     }
  28.     if ( listen( listen_fd,1 ) == -1 ){
  29.         perror("listen error");
  30.         exit(1);
  31.     }
  32.     fd_set fd_sets;
  33.     int max_fd = listen_fd ;
  34.     int events=0;
  35.     int accept_fds[1024];
  36.     int i = 0;
  37.     for(;;){
  38.         FD_ZERO( &fd_sets );
  39.         FD_SET(listen_fd,&fd_sets);
  40.         int k=0;
  41.         for(k=0; k<=i; k++){
  42.             FD_SET(accept_fds[k],&fd_sets);
  43.         }
  44.         events = select( max_fd + 1, &fd_sets, NULL, NULL, NULL );
  45.         if( events ){
  46.                 printf("events:%d\n",events);
  47.                 if( FD_ISSET(listen_fd,&fd_sets) ){
  48.                         printf("listen event :1\n");
  49.                         int addr_len = sizeof( struct sockaddr_in );
  50.                         accept_fd = accept( listen_fd,
  51.                                                 (struct sockaddr *)&remote_addr,&addr_len );
  52.                         int flags = fcntl(accept_fd, F_GETFL, 0);
  53.                         fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
  54.                         accept_fds[i] = accept_fd;
  55.                         i++;
  56.                         max_fd = accept_fd ;
  57.                         printf("new accept fd:%d\n",accept_fd);
  58.                 }
  59.                 int j=0;
  60.                 for( j=0; j<=i; j++ ){
  61.                     if( FD_ISSET(accept_fds[j],&fd_sets) ){
  62.                         printf("accept event :%d\n",j);
  63.                         char in_buf[1024];
  64.                         memset(in_buf, 0, 1024);
  65.                         recv( accept_fds[j] ,&in_buf ,1024 ,0 );
  66.                         printf( "accept:%s\n", in_buf );
  67.                     }
  68.                 }
  69.         }
  70.     }
  71.     return 0;
  72. }

从头编写高性能服务程序5-单进程非阻塞epoll

 

select已经是很古老的非阻塞复用I/O方式了
Linux下最常用的IO复用是epoll了
所以我们用单进程来实验epoll
epoll_wait和select一样
都是用一个fd的阻塞来代替一堆fd

下面程序有一个问题
由于使用了ET模式
所以当listen_fd有客户端连接上来时
只会被通知一次
如果是并发连接,如果只accept了一次
则第二个链接会被忽略
也就是真正高并发情况下.会出现丢客户端请求的情况

由于使用了复用IO
所以即使是单进程也能处理多客户端连接了

下载:
  1. #include<sys/socket.h>
  2. #include<netinet/in.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. #include <fcntl.h>
  7. #include<sys/epoll.h>
  8. int main(){
  9.     int listen_fd,accept_fd,flag;
  10.     struct sockaddr_in my_addr,remote_addr;
  11.     if ( (listen_fd = socket( AF_INET,SOCK_STREAM,0 )) == -1 ){
  12.         perror("create socket error");
  13.         exit(1);
  14.     }
  15.     if ( setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag)) == -1 ){
  16.         perror("setsockopt error");
  17.     }
  18.     int flags = fcntl(listen_fd, F_GETFL, 0);
  19.     fcntl(listen_fd, F_SETFL, flags|O_NONBLOCK);
  20.     my_addr.sin_family = AF_INET;
  21.     my_addr.sin_port = htons(3389);
  22.     my_addr.sin_addr.s_addr = INADDR_ANY;
  23.     bzero(&(my_addr.sin_zero),8);
  24.     if ( bind( listen_fd, (struct sockaddr *)&my_addr,
  25.                 sizeof(struct sockaddr_in)) == -1 ) {
  26.         perror("bind error");
  27.         exit(1);
  28.     }
  29.     if ( listen( listen_fd,1 ) == -1 ){
  30.         perror("listen error");
  31.         exit(1);
  32.     }
  33.     struct epoll_event ev,events[20];
  34.     int epfd = epoll_create(256);
  35.     int ev_s=0;
  36.     ev.data.fd=listen_fd;
  37.     ev.events=EPOLLIN|EPOLLET;
  38.     epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
  39.     for(;;){
  40.         ev_s = epoll_wait( epfd,events,20,500 );
  41.         int i=0;
  42.         for(i=0; i<ev_s; i++){
  43.             if(events[i].data.fd==listen_fd){
  44.                 printf("listen event :1\n");
  45.                 int addr_len = sizeof( struct sockaddr_in );
  46.                 accept_fd = accept( listen_fd,
  47.                                         (struct sockaddr *)&remote_addr,&addr_len );
  48.                 int flags = fcntl(accept_fd, F_GETFL, 0);
  49.                 fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
  50.                 ev.data.fd=accept_fd;
  51.                 ev.events=EPOLLIN|EPOLLET;
  52.                 epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev);
  53.                 printf("new accept fd:%d\n",accept_fd);
  54.             }
  55.             else if(events[i].events&EPOLLIN){
  56.                 printf("accept event :%d\n",i);
  57.                 char in_buf[1024];
  58.                 memset(in_buf, 0, 1024);
  59.                 recv( events[i].data.fd ,&in_buf ,1024 ,0 );
  60.                 printf( "accept:%s\n", in_buf );
  61.             }
  62.         }
  63.    }
  64.     return 0;
  65. }

从头编写高性能服务程序6-多进程阻塞完整版

前面所有的程序都是缺少链接的后期管理的
我们在多进程的情况下测试子进程处理完后自行退出
而主进程继续监听的情况
一开始我认为只要子进程处理完之后exit就可以了
结果碰到了问题
子进程在主进程未退出的情况下先行退出
会变成僵尸进程
反之则会变成孤儿进程

僵尸进程的表现是子进程在ps下后面多个

defunct

解决方法是在主进程这里需要wait
用来处理子进程的各种状态

还有我们加上了客户端退出时候的处理
当客户端退出时
我们recv到的数据返回值会是0
而之前会被阻塞在那边
所以加上对recv的返回值0的处理就可以了

下载:
  1. #include<sys/socket.h>
  2. #include<netinet/in.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. int main(){
  7.     int listen_fd,accept_fd,flag;
  8.     struct sockaddr_in my_addr,remote_addr;
  9.     if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
  10.         perror("create socket error");
  11.         exit(1);
  12.     }
  13.     if (setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR
  14.                     ,(char *)&flag,sizeof(flag)) == -1){
  15.         perror("setsockopt error");
  16.     }
  17.     my_addr.sin_family = AF_INET;
  18.     my_addr.sin_port = htons(3389);
  19.     my_addr.sin_addr.s_addr = INADDR_ANY;
  20.     bzero(&(my_addr.sin_zero), 8);
  21.     if (bind(listen_fd, (struct sockaddr *)&my_addr,
  22.                 sizeof(struct sockaddr_in)) == -1) {
  23.         perror("bind error");
  24.         exit(1);
  25.     }
  26.     if (listen(listen_fd,1) == -1){
  27.         perror("listen error");
  28.         exit(1);
  29.     }
  30.     int pid=-1;
  31.     int addr_len = sizeof(struct sockaddr_in);
  32.     int max_process_num = 10;
  33.     int i;
  34.     int child_process_status;
  35.     for(i = 0; i< max_process_num; i++){
  36.         if(pid != 0){
  37.             pid = fork();
  38.         }
  39.     }
  40.     if(pid == 0){
  41.         for(;;){
  42.             accept_fd = accept(listen_fd
  43.                                 ,(struct sockaddr *)&remote_addr,&addr_len);
  44.             int recv_num;
  45.             for(;;){
  46.                 char in_buf[1024];
  47.                 memset(in_buf, 0, 1024);
  48.                 recv_num = recv(accept_fd ,&in_buf ,1024 ,0);
  49.                 if(recv_num == 0){
  50.                     printf("close socket\n");
  51.                     close(accept_fd);
  52.                     break;
  53.                 }
  54.                 else{
  55.                     printf("accept:%d:%s\n", recv_num, in_buf);
  56.                 }
  57.             }
  58.         }
  59.     }
  60.     else{
  61.         //manager the process
  62.         wait(&child_process_status);
  63.     }
  64.    
  65.     return 0;
  66. }

从头编写高性能服务程序7-多进程非阻塞epoll版

我们有了前面的所有经验
多进程,非阻塞,IO复用
然后我们要把这些特定揉合起来了
先是在accept上阻塞
然后当有新链接过来就会fork
当然这样一个结构并不比多进程阻塞版本好太多
因为每个进程虽然使用epoll来处理
但是一个进程内只有一个accept_fd在被处理
所以效果是一样的
在下一个版本中
我们将在一个进程使用epoll处理多个accept

下载:
  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <fcntl.h>
  7. #include <sys/epoll.h>
  8. int main(){
  9.     int listen_fd,accept_fd,flag;
  10.     struct sockaddr_in my_addr,remote_addr;
  11.     if ( (listen_fd = socket( AF_INET,SOCK_STREAM,0 )) == -1 ){
  12.         perror("create socket error");
  13.         exit(1);
  14.     }
  15.     if ( setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag)) == -1 ){
  16.         perror("setsockopt error");
  17.     }
  18.     my_addr.sin_family = AF_INET;
  19.     my_addr.sin_port = htons(3389);
  20.     my_addr.sin_addr.s_addr = INADDR_ANY;
  21.     bzero(&(my_addr.sin_zero),8);
  22.     if ( bind( listen_fd, (struct sockaddr *)&my_addr,
  23.                 sizeof(struct sockaddr_in)) == -1 ) {
  24.         perror("bind error");
  25.         exit(1);
  26.     }
  27.     if ( listen( listen_fd,1 ) == -1 ){
  28.         perror("listen error");
  29.         exit(1);
  30.     }
  31.     for(;;){
  32.         int addr_len = sizeof( struct sockaddr_in );
  33.         accept_fd = accept( listen_fd,
  34.                                 (struct sockaddr *)&remote_addr,&addr_len );
  35.         int flags = fcntl(accept_fd, F_GETFL, 0);
  36.         fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
  37.        
  38.         int pid = fork();
  39.         if( pid == 0 ){
  40.                 struct epoll_event ev,events[20];
  41.                 int epfd = epoll_create(256);
  42.                 int ev_s=0;
  43.                 ev.data.fd=accept_fd;
  44.                 ev.events=EPOLLIN|EPOLLET;
  45.                 epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev);
  46.                 for(;;){
  47.                     ev_s = epoll_wait( epfd,events,20,500 );
  48.                     int i=0;
  49.                     for(i=0; i<ev_s; i++){
  50.                         if(events[i].events&EPOLLIN){
  51.                             printf("accept event :%d\n",i);
  52.                             char in_buf[1024];
  53.                             memset(in_buf, 0, 1024);
  54.                             recv( events[i].data.fd ,&in_buf ,1024 ,0 );
  55.                             printf( "accept:%s\n", in_buf );
  56.                         }
  57.                     }
  58.                 }
  59.         }
  60.         else{
  61.             //do nothing
  62.         }
  63.        
  64.     }
  65.     return 0;
  66. }

从头编写高性能服务程序8-多进程非阻塞epoll-prefork

上一个版本在accept的位置仍旧会被阻塞
这样当一个链接进来的时候就会产生一个新的进程
进程的不断开启和释放会造成很大的性能影响
而一般Apache和Nginx的做法就是先产生N个进程用以备用
当有实际链接的时候,就由这些进程获取并进行处理
(注:Nginx的线程模式只在Windows下有效,在Linux下是使用进程模型的)
这样我们就有两个地方需要改造

第一个是将listen端口也变为非阻塞
这样当有新链接进来的时候我们得到通知才去处理
这样accept调用就不会被阻塞在进程导致进程无法进行后续的工作

第二是进程一启动之后就fork N个进程
这些进程启动之后就自行获取各自的accept
然后自行处理各自获取的链接并管理其生命周期内的所有内容

将listen也放置到epoll中
就需要在每次获得epoll events的时候判断下
这个events是前面放进去的listen,如果是listen就要accept
如果是accept的就进行数据传输处理

下载:
  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <fcntl.h>
  7. #include <sys/epoll.h>
  8. int main(){
  9.     int listen_fd,accept_fd,flag;
  10.     struct sockaddr_in my_addr,remote_addr;
  11.     if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
  12.         perror("create socket error");
  13.         exit(1);
  14.     }
  15.     if (setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR
  16.                     ,(char *)&flag,sizeof(flag)) == -1){
  17.         perror("setsockopt error");
  18.     }
  19.     int flags = fcntl(listen_fd, F_GETFL, 0);
  20.     fcntl(listen_fd, F_SETFL, flags|O_NONBLOCK);
  21.     my_addr.sin_family = AF_INET;
  22.     my_addr.sin_port = htons(3389);
  23.     my_addr.sin_addr.s_addr = INADDR_ANY;
  24.     bzero(&(my_addr.sin_zero), 8);
  25.     if (bind(listen_fd, (struct sockaddr *)&my_addr,
  26.                 sizeof(struct sockaddr_in)) == -1) {
  27.         perror("bind error");
  28.         exit(1);
  29.     }
  30.     if (listen(listen_fd,1) == -1){
  31.         perror("listen error");
  32.         exit(1);
  33.     }
  34.     int pid=-1;
  35.     int addr_len = sizeof(struct sockaddr_in);
  36.     int max_process_num = 3;
  37.     int child_id;
  38.     int i;
  39.     int child_process_status;
  40.     for(i = 0; i < max_process_num; i++){
  41.         if(pid != 0){
  42.             pid = fork();
  43.         }
  44.         else{
  45.             child_id = i;
  46.         }
  47.     }
  48.     if(pid == 0){
  49.         int accept_handles = 0;
  50.         struct epoll_event ev,events[20];
  51.         int epfd = epoll_create(256);
  52.         int ev_s = 0;
  53.         ev.data.fd = listen_fd;
  54.         ev.events = EPOLLIN|EPOLLET;
  55.         epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
  56.         for(;;){
  57.             ev_s = epoll_wait( epfd,events, 20, 500 );
  58.             int i = 0;
  59.             for(i = 0; i<ev_s; i++){
  60.                 if(events[i].data.fd == listen_fd){
  61.                     int max_process_accept = 3;
  62.                     if(accept_handles < max_process_accept){
  63.                         accept_handles++;
  64.                         int addr_len = sizeof( struct sockaddr_in );
  65.                         accept_fd = accept( listen_fd,
  66.                                                 (struct sockaddr *)&remote_addr, &addr_len );
  67.                         int flags = fcntl(accept_fd, F_GETFL, 0);
  68.                         fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
  69.                         ev.data.fd = accept_fd;
  70.                         ev.events = EPOLLIN|EPOLLET;
  71.                         epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev);
  72.                         printf("Child:%d,ProcessID:%d,EPOLLIN,fd:%d,accept:%d\n", child_id, getpid(), listen_fd, accept_fd);
  73.                     }
  74.                 }
  75.                 else if(events[i].events&EPOLLIN){
  76.                     char in_buf[1024];
  77.                     memset(in_buf, 0, 1024);
  78.                     int recv_num = recv( events[i].data.fd, &in_buf, 1024, 0 );
  79.                     if( recv_num ==0 ){
  80.                         close(events[i].data.fd);
  81.                         accept_handles--;
  82.                         printf("Child:%d,ProcessID:%d,EPOLLIN,fd:%d,closed\n", child_id, getpid(), events[i].data.fd);
  83.                     }
  84.                     else{
  85.                         printf("Child:%d,ProcessID:%d,EPOLLIN,fd:%d,recv:%s\n", child_id, getpid(), events[i].data.fd, in_buf);
  86.                     }
  87.                 }
  88.             }
  89.         }
  90.     }
  91.     else{
  92.         //manager the process
  93.         wait(&child_process_status);
  94.     }
  95.    
  96.     return 0;
  97. }

这个问题就是多进程里面的群惊现象
当所有进程都阻塞在listen端口时
一旦系统获得listen端口的状态变化
会把所有的线程都唤醒,而只有一个获取后进行处理
其他的在被阻塞后继续沉睡
不过在Linux 2.4以后这个群惊的现象已经被内核给处理掉了
实际测试下来只有一个进程会被再次唤醒,而其他保持阻塞,
这样阻塞在listen端口其实效率不比阻塞在同享锁差.毕竟调度是在内核级别的.

[转]http://blog.chinaunix.net/u/6889/showart.php?id=2296856
阅读(1350) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-08-27 08:48:40

Download More than 1000 free IT eBooks: http://free-ebooks.appspot.com