搭建一个后台服务器--服务端(异步,大并发)

来源:互联网 发布:阿国网络随笔123 编辑:程序博客网 时间:2024/06/05 11:41

from:

http://blog.csdn.net/xiaofei_hah0000/article/details/8743627


上篇的阻塞模式下服务器的并发只有几K,而真正的server 像nginx, apache, yumeiz 轻轻松松处理几万个并发完全不在话下,因此大并发的场合下是不能用阻塞的。

1W的并发是一个分隔点,如果单进程模型下能达到 的话,说明至少在服务器这块你已经很厉害了。

服务器开发就像一门气功,能不能搞出大并发,容错性处理得怎么样,就是你有没有内功,内功有多深。


异步模式是专门为大并发而生,Linux下一般用 epoll 来管理事件,下面就开始我们的异步大并发服务器实战吧。

跟阻塞开发一样,先来看看设计过程:

1.创建事件模型。

2.创建监听连接并监听。

3.将监听连接加入事件模型。

4.当有事件时,判断事件类型。

5.若事件为监听连接,则产生客户连接同时加入事件模型,对客户连接接收发送。

6.若事件为客户连接,处理相应IO请求。


为了让大家一概全貌,我用一个函数实现的( 一个函数写一个2W并发的服务器,你试过么),可读性可能会差点,但是对付这道面试题是绰绰有余了。

实际开发过程如下:

先定义一个事件结构,用于对客户连接进行缓存

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. struct my_event_s  
  2. {  
  3.     int fd;  
  4.     char recv[64];  
  5.     char send[64];  
  6.   
  7.     int rc_pos;  
  8.     int sd_pos;  
  9. };  

建立缓存对象:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. struct epoll_event wait_events[ EPOLL_MAX ];  
  2. struct my_event_s my_event[ EPOLL_MAX ];  


创建监听连接:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. sock_server = socket( AF_INET, SOCK_STREAM, 0 );  
  2. flag = fcntl( sock_server, F_GETFL, 0 );  
  3. fcntl( sock_server, F_SETFL, flag | O_NONBLOCK );  


绑定地址并监听:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. flag = bind( sock_server, ( struct sockaddr* )&addr_server, sizeofstruct sockaddr ) );  
  2. if( flag < 0 )  
  3. {  
  4.     printf( "your bind is not ok\n" );  
  5.     close( sock_server );  
  6.     return 0;  
  7. }  
  8.   
  9. flag = listen( sock_server, 1024 );  
  10. if( flag < 0 )  
  11. {  
  12.     printf( "your listen is not ok\n");  
  13.     close( sock_server );  
  14.     return 0;  
  15. }  


创建事件模型:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. epfd = epoll_create( EPOLL_MAX );  
  2. if( epfd <= 0 )  
  3. {  
  4.     printf( "event module could not be setup\n");  
  5.     close( sock_server );  
  6.     return 0;  
  7. }  


将监听事件加入事件模型:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. tobe_event.events = EPOLLIN;  
  2. tobe_event.data.fd = sock_server;  
  3.   
  4. epoll_ctl( epfd,  EPOLL_CTL_ADD, sock_server,  &tobe_event );  

事件模型处理:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. e_num = epoll_wait( epfd, wait_events, EPOLL_MAX, WAIT_TIME_OUT );  
  2. if( e_num <= 0 )  
  3. {  
  4.     continue;  
  5. }  
  6.   
  7. for( i = 0; i < e_num; ++i )  
  8. {  


监听处理:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. if( sock_server == wait_events[ i ].data.fd )  
  2. {  while(1){  

连接客户端:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. sock_client = accept( sock_server, ( struct sockaddr* )&addr_client, ( socklen_t*)&size );  
  2. if( sock_client < 0 )  
  3. {  
  4.     if( errno == EAGAIN )  
  5.     {  
  6.         break;  
  7.     }  
  8.     if( errno == EINTR )  
  9.     {  
  10.         continue;  
  11.     }  
  12.     break;  
  13. }  



将客户端连接设置为异步:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. flag = fcntl( sock_client, F_GETFL, 0 );  
  2. fcntl( sock_client, F_SETFL, flag | O_NONBLOCK );  

将客户端连接加入到 事件模型:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. tobe_event.events = EPOLLIN | EPOLLET;  
  2. tobe_event.data.u32 = my_empty_index;  
  3.   
  4. epoll_ctl( epfd, EPOLL_CTL_ADD, sock_client, &tobe_event );  
统计每秒并发数并得到系统当前时间:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. ++num;  
  2. current = time( 0 );  
  3. if( current > last )  
  4. {  
  5.     printf( "last sec qps:%d\n", num );  
  6.     num = 0;  
  7.     last = current;  
  8. }  


将时间填充到连接缓存中:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. memcpy( tobe_myevent->send, &current, sizeof(time_t) );  

接收连接内容:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. flag = recv( sock_client, tobe_myevent->recv, 64, 0 );  
  2. if( flag < 64 )  
  3. {  
  4.     if( flag > 0 )  
  5.         tobe_myevent->rc_pos += flag;  
  6.     continue;  
  7. }  

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. if( tobe_myevent->recv[31] || tobe_myevent->recv[63] )  
  2. {  
  3.     printf( "your recv does follow the protocal\n");  
  4.     tobe_myevent->fd = 0;  
  5.     close( sock_client );  
  6.     continue;  
  7. }  
  8.                       
  9.   
  10. flag = send( sock_client, tobe_myevent->send, sizeoftime_t ), 0 );  
  11. if( flag < sizeoftime_t ) )  
  12. {  
  13. <span style="white-space:pre">  </span>tobe_event.events =  EPOLLET | EPOLLOUT;  
  14. <span style="white-space:pre">  </span>epoll_ctl( epfd, EPOLL_CTL_MOD, sock_client, &tobe_event );  
  15.     if( flag > 0 )  
  16.         tobe_myevent->sd_pos += flag;  
  17.     continue;  
  18. }  
  19. tobe_myevent->fd = 0;  
  20. close( sock_client );  

后面进行普通连接事件处理,错误处理:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. if( event_flag & EPOLLHUP )  
  2. {  
  3.     tobe_myevent->fd = 0;  
  4.     close( sock_client );  
  5.     continue;  
  6. }  
  7. else if( event_flag & EPOLLERR )  
  8. {  
  9.     tobe_myevent->fd = 0;  
  10.     close( sock_client );  
  11.     continue;  
  12. }  

写事件:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. else if( event_flag & EPOLLOUT )  
  2. {  
  3.     if( tobe_myevent->rc_pos != 64 )  
  4.     {  
  5.         continue;  
  6.     }  
  7.   
  8.     if( tobe_myevent->sd_pos >= sizeoftime_t ) )  
  9.     {  
  10.         tobe_myevent->fd = 0;  
  11.         close( sock_client );  
  12.         continue;  
  13.     }  
  14.   
  15.     flag = send( sock_client, tobe_myevent->send + tobe_myevent->sd_pos, sizeoftime_t ) - tobe_myevent->sd_pos, 0 );  
  16.     if( flag < 0 )  
  17.     {  
  18.         if( errno == EAGAIN )  
  19.         {  
  20.             continue;  
  21.         }  
  22.         else if( errno == EINTR )  
  23.         {  
  24.             continue;  
  25.         }  
  26.         tobe_myevent->fd = 0;  
  27.         close( sock_client );  
  28.         continue;  
  29.     }  
  30.   
  31.     if( flag >0 )  
  32.     {  
  33.         tobe_myevent->sd_pos += flag;  
  34.         if( tobe_myevent->sd_pos >= sizeoftime_t ) )  
  35.         {  
  36.             tobe_myevent->fd = 0;  
  37.             close( sock_client );  
  38.             continue;  
  39.         }  
  40.     }  
  41. }  

读事件:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. if( event_flag & EPOLLIN )  
  2. {  
  3.     if( tobe_myevent->rc_pos < 64 )  
  4.     {  
  5.         flag = recv( sock_client, tobe_myevent->recv + tobe_myevent->rc_pos, 64 - tobe_myevent->rc_pos, 0 );  
  6.         if( flag <= 0 )  
  7.         {  
  8.             continue;  
  9.         }  
  10.   
  11.         tobe_myevent->rc_pos += flag;  
  12.   
  13.         if( tobe_myevent->rc_pos < 64 )  
  14.         {  
  15.             continue;  
  16.         }  
  17.   
  18.         if( tobe_myevent->recv[31] || tobe_myevent->recv[63] )  
  19.         {  
  20.             printf( "your recv does follow the protocal\n");  
  21.             tobe_myevent->fd = 0;  
  22.             close( sock_client );  
  23.             continue;  
  24.         }  
  25.   
  26.         flag = send( sock_client, tobe_myevent->send, sizeoftime_t ), 0 );  
  27.         if( flag < sizeoftime_t ) )  
  28.         {  
  29.             if( flag > 0 )  
  30.                 tobe_myevent->sd_pos += flag;  
  31.         <span style="white-space:pre">  </span>tobe_event.events = EPOLLET | EPOLLOUT;  
  32.             tobe_event.data.u32 = wait_events[i].data.u32;  
  33.             epoll_ctl( epfd, EPOLL_CTL_MOD, sock_client, &tobe_event );  
  34.             continue;  
  35.         }  
  36.         tobe_myevent->fd = 0;  
  37.         close( sock_client );  
  38.     }  
  39.   
  40. }  
  41.       
到此,一个异步服务器搭建完毕,轻松实现2W的并发量,既完成了任务,又是实实在在的锻练了一回。
0 0
原创粉丝点击