epoll去实现一个服务器

来源:互联网 发布:乐知少儿英语官网 编辑:程序博客网 时间:2024/04/29 21:01

http://blog.csdn.net/jfkidear/article/details/7905607

第一次用epoll去实现一个服务器, 

之前并不清楚epoll的用法, 
了解之后才发现epoll服务器的主线程其实最好和处理业务的代码分开, 
也就是说: 
epoll响应外界的io请求,当epoll得到一个请求的时候,扔到一个消息队列中,然后epoll直接返回,再去等待io请求.而消息队列会通知多个线程去处理这些业务逻辑. 

epoll第一次用,消息队列更是第一次用,开始一直在想,怎么写个阻塞的队列,而且要有主动通知的功能,想了一会儿发现pthread_cond_wait和pthread_cond_signal可以实现,于是就简单的试试,下面的代码已经可以实现我刚才想要得到的那个模型,细节就不管了. 

对消息队列熟悉的同学请帮忙提点意见,并告诉我下还有哪些方法可以实现阻塞的消息队列. 

对代码的解释和描述都写到注释中了. 

C代码  收藏代码
  1. /* 
  2. 几个用到的类型定义以及全局变量bq 
  3. */  
  4. char smtp_cmd_format;  
  5. struct epoll_event ev, events[MAX_EPOLL_SIZE];  
  6. int kdpfd,nfds;  
  7. struct block_queue  
  8. {  
  9.     int queue[THREADS_COUNT];  
  10.     long size;  
  11.     pthread_cond_t cond;  
  12.     pthread_mutex_t mutex;  
  13. }block_queue_t;  
  14. block_queue_t bq;  
  15. struct block_queue_param  
  16. {  
  17.     void* func;  
  18.     void* queue;  
  19. }block_queue_param_t;  



C代码  收藏代码
  1. void *block_queue(void * param)  
  2. {  
  3.     void(* func)(void* );  
  4.     int fd,i;  
  5.     /* 
  6. 由于block_queue是pthread_create的回调方法, 
  7. 所以block_queue的参数必须是void*类型 
  8.        */  
  9.     block_queue_t* bque = (block_queue_param_t*)param->queue;  
  10.         /* 
  11. param->func是block_queue解锁时需要调用的函数, 
  12. 而这个函数的参数是一个int fd, 
  13. 该fd是消息队列中刚刚插入的一个元素. 
  14.        */  
  15.     func = (block_queue_param_t*)param->func;  
  16.       
  17.     for(;;)  
  18.     {  
  19. /* 
  20. lock->wait->unlock 
  21. 这是经典的模式, 
  22. 切记: 
  23. pthread_cond_wait的方法自带了先解锁,再等待,最后再加锁的代码 
  24. */  
  25.         pthread_mutex_lock(bque->mutex);  
  26. /* 
  27. 线程在pthread_cond_wait这里被block住了, 
  28. 当听到pthread_cond_signal通知的时候, 
  29. 内核会从阻塞队列里面通过先进先出的原则唤醒一个线程, 
  30. 这个线程会执行pthread_cond_wait之后的代码. 
  31. */  
  32.         pthread_cond_wait(bque->cond,bque->mutex);  
  33.           
  34.         if(bque->size==0)  
  35.         {  
  36. /* 
  37. 啥也不做 
  38. */  
  39.         }else  
  40.         {  
  41.             fd = bque->queue[0];  
  42. /* 
  43. 移动队列, 
  44. 由于该队列是简单的用数组保存fd, 
  45. 所以移动这个操作必不可少,但肯定性能比链表差很多, 
  46. 这是懒惰的代价 
  47. */  
  48.             for(i = 0; i < bque->size; ++i)  
  49.                 bque->queue[i] = bque->queue[i+1];  
  50.                 bque->queue[bque->size-1] = 0;  
  51.             bque->size--;  
  52. /* 
  53. 执行被唤醒后的方法,参数是刚刚插入到队列中的一个fd 
  54. */  
  55.                         func(fd);  
  56.         }  
  57.           
  58.         pthread_mutex_unlock(bque->mutex);  
  59.     }  
  60. }  
  61.   
  62. void insert_queue(struct block_queue *bque,int fd)  
  63. {  
  64. /* 
  65. 加锁->通知->解锁 
  66.  
  67. 将元素插入队列之前需要先加锁 
  68. */  
  69.     pthread_mutex_lock(bque->mutex);  
  70. /* 
  71. 检查队列目前的大小, 
  72. 检查1: 
  73. 当大小已经达到定义的数组大小THREADS_COUNT时, 
  74. 抛弃该fd,说明服务器忙不过来了,消息队列已经满了 
  75.  
  76. 检查2: 
  77. 当大小超过数组定义的大小THREADS_COUNT时, 
  78. 肯定发生了异常,那就直接退出服务吧. 
  79. */  
  80.     if(bque->size == THREADS_COUNT)  
  81.         return;  
  82. /* 
  83. bque->size其实也是队列末尾位置指针, 
  84. 当插入一个元素后,这个指针自然也要向后移动一位. 
  85. */  
  86.     bque->queue[bque->size+1] = fd;  
  87.     if(++bque->size > THREADS_COUNT)  
  88.     {  
  89.         fprintf(stderr,"Queue size over folow.%d",bque->size);  
  90.         exit 1;  
  91.     }  
  92. /* 
  93. 当元素插入bque队列时, 
  94. 该通过pthread_cond_signal通知内核去调度wait的线程了 
  95. */  
  96.     pthread_cond_signal(bque->cond);  
  97.     pthread_mutex_unlock(bque->mutex);  
  98.       
  99. }  
  100.   
  101. /* 
  102. init_threads代码是初始化线程组的, 
  103. 随便写写的,大家知道怎么实现就行 
  104. */  
  105. int init_threads()  
  106. {  
  107.     size_t i=0;  
  108.     block_queue_param_t bqp;  
  109. /* 
  110. smtp_echo是处理epoll扔进队列中的fd的函数, 
  111. 该方法实现了整个模型的业务逻辑, 
  112. 整体代码的IO处理+消息队列以及业务处理分的很清晰, 
  113. 三个模块每个只有一处代码和其它模块通讯,没有多少耦合. 
  114. */  
  115.     bqp.func = (void*)smtp_echo;  
  116.     bqp.queue = (void*)bq;  
  117.     pthread_cond_init(bqp.cond,NULL);  
  118.     pthread_mutex_init(bqp.mutex,NULL);  
  119.     for( i = 0; i < THREADS_COUNT; ++i)  
  120.     {  
  121.         pthread_t child_thread;  
  122.         pthread_attr_t child_thread_attr;  
  123.         pthread_attr_init(&child_thread_attr);  
  124.         pthread_attr_setdetachstate(&child_thread_attr,PTHREAD_CREATE_DETACHED);  
  125.         if( pthread_create(&child_thread,&child_thread_attr,block_queue, (void *)bqp) < 0 )  
  126.         {  
  127.             printf("pthread_create Failed : %s\n",strerror(errno));  
  128.             return 1;  
  129.         }  
  130.         else  
  131.         {  
  132.             printf("pthread_create Success : %d\n",(int)child_thread);  
  133.             return 0;  
  134.         }  
  135.     }  
  136.   
  137. }  
  138.   
  139. /* 
  140. handler是主线程访问的方法, 
  141. 主线程通过handler把一个fd扔到消息队列之后, 
  142. 不再做任何事情就直接返回了. 
  143.  
  144. 在我的应用中,主线程是个epoll实现的服务器, 
  145. 由于epoll被响应的时候会知道哪些fd已经就位, 
  146. 于是直接把就位的fd扔到消息队列中就好了, 
  147. 主线程在继续等待其它fd的响应,而不需要去关心fd如何被处理. 
  148. */  
  149.   
  150. int handler(void* fd)  
  151. {  
  152.     printf("handler:fd => %d\n",*(int *)(fd));  
  153.     insert_queue(&bq,fd);  
  154.     return 0;  
  155. }  
  156.   
  157.   
  158. /* 
  159. main函数是整个程序的入口点, 
  160. 也是epoll服务器的实现, 
  161. epoll的思想很精髓,用法很简单, 
  162. 只要把man 4 epoll_ctl的例子copy出来,就可用了, 
  163. 不过那个例子语法有点问题, 
  164. 而且events数组是用指针,应该用[]实现,因为指针没有分配空间. 
  165. */  
  166. int main(int argc, char **argv)  
  167. {     
  168.     int server_socket = init_smtp();  
  169.     int n;  
  170.       
  171.     if(init_threads() == 0)  
  172.         printf("Success full init_threads.");  
  173.       
  174.     smtp_cmd_format = "^([a-zA-Z0-9]) (.*)$";  
  175.     kdpfd = epoll_create(MAX_EPOLL_SIZE);  
  176.     ev.events = EPOLLIN | EPOLLET;  
  177.     ev.data.fd = server_socket;  
  178.     if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, server_socket, &ev) < 0) {  
  179.         fprintf(stderr, "epoll set insertion error: fd=%d < 0",  
  180.                 server_socket);  
  181.         return -1;  
  182.     }  
  183.       
  184. /* 
  185. epoll的使用看这里 
  186. */  
  187.     for(;;) {  
  188.         struct sockaddr_in local;  
  189.         socklen_t length = sizeof(local);  
  190.         int client;  
  191.           
  192.         nfds = epoll_wait(kdpfd, events, MAX_EPOLL_SIZE, -1);  
  193.   
  194. /* 
  195. 当没有事件要处理时,epoll会阻塞住, 
  196. 否则,内核会填充events数组,里面的每一个events[n].data.fd就是发生io时间的文件句柄 
  197. */  
  198.   
  199.         for(n = 0; n < nfds; ++n) {  
  200. /* 
  201. 这里要判断一下请求的来源, 
  202. if(events[n].data.fd == server_socket) { 
  203. 这里是新建的连接, 
  204. 因为io发生在server_socket上 
  205. } 
  206. else{ 
  207. 这里是已有的连接, 
  208. 因为fd!= server_socket 
  209. 那fd肯定是之前从server_socket接收到, 
  210. 并且通过epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) 
  211. 加入到kdpfd所指向的内存空间中. 
  212. kdpfd其实是个文件句柄,在epoll_create(MAX_EPOLL_SIZE)时得到 
  213. } 
  214. */  
  215.             if(events[n].data.fd == server_socket) {  
  216.                 client = accept(server_socket, (struct sockaddr *) &local,&length);  
  217.                 if(client < 0){  
  218.                     perror("accept");  
  219.                     continue;  
  220.                 }  
  221.                 setnonblocking(client);  
  222.                 smtp_cmd("220",client);  
  223.                 ev.events = EPOLLIN | EPOLLET;  
  224.                 ev.data.fd = client;  
  225.                 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {  
  226.                     fprintf(stderr, "epoll set insertion error: fd=%d < 0",  
  227.                             client);  
  228.                     return -1;  
  229.                 }  
  230.             }  
  231.             else  
  232. /* 
  233. 当已有的fd发生io操作时, 
  234. 执行如下代码.也就是把fd扔到消息队列中. 
  235. */  
  236.                 if(handler((void *)&events[n].data.fd) != 0)  
  237.                     perror("handler ret != 0");  
  238.         }  
  239.     }  
  240.   
  241.     close(server_socket);  
  242.     return 0;  
  243. }  
block_queue 线程的处理。

pthread_mutex_lock(bque->mutex);
// ???????
pthread_cond_wait(bque->cond,bque->mutex);

我认为 在//????? 的地方先加入 一个if(bque->size==0) 判断,就可以先判断出来是否队列有数据需要处理。。避免陷入cond_wait函数。。经典的

pthread_mutex_lock
wile(条件) {
pthread_cond_wait
}
pthread_mutex_unlock 模型。

另外我认为还有个问题。是func(fd); 被放入mutex锁范围内执行的。这个叫极度影响效率。我猜测你的func应该会去处理socket的recv以及业务数据的处理。。相信这个过程会很消耗时间。应该把func(fd)放到mutex 外面去。。

在前面看到你应该使用的是epoll的边缘触发,(其实边缘触发的概念应该来源,电路上的概念,从高电平变低电平,这个变化,或者是低变高,这个变化作为触发条件) 那么func(fd)里面应该一直recv 数据 这是一个nonblock socket 一直recv 到EAGAIN 错误。停止处理。再次等待epoll的通知。
0 0
原创粉丝点击