在前面我们介绍了循环服务器,并发服务器模型。简单的循环服务器每次只能处理一个请求,即处理的请求是串行的,效率过低;并发服务器可以通过创建多个进程或者是线程来并发的处理多个请求。但是当客户端增加时,就需要创建更多的进程或者线程,就会导致系统负载最终转移到进程或线程的切换开销上。

为了减少这类开销,而使系统处理能力集中在核心业务上,就要求我们降低并发的进程或线程数目,因此又实现了一个更高级的IO复用循环服务器。I/O复用的循环服务器一般创建两个线程,一个是客户端连接处理线程,专门用来处理客户端的连接,当有客户端到来的时候,此线程把客户端的套接字描述符放到一块公共的区域中。另一个是业务处理线程,此线程轮循(select)客户端套接字描述符集合中有没有数据到来,如果有数据到来,那么就进行处理。这样,客户 端的增加并不会造成系统进程或线程数的明显增加,而使其处理能力与CPU和内存直接相关。

TCP并发服务器模型 I/O多路复用模型伪代码

  1. /* TCP并发服务器模型 I/O多路复用 */
  2. /* 服务器主进程 */
  3. socket();
  4. bind();
  5. listen();
  6. pthread_create( ); //创建客户端连接线程和业务处理线程
  7. pthread_join( ); //等待线程结束
  8. close( ); //关闭服务器套接字
  9. /* 连接处理线程 */
  10. while()
  11. {
  12. accept( ); //接受一个客户端连接
  13. store();//存储客户端套接字描述符到一个公共集合中
  14. }
  15.  
  16. /* 业务处理线程 */
  17. while()
  18. {
  19. get( ); //取出可用的客户端套接字描述符
  20. select( ); //设置监听读写文件描述符集合
  21. recv( );
  22. process( );
  23. send( );
  24. close( );
  25. }

一个I/O多路复用模型的例子

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <netinet/in.h>
  4. #include <time.h>
  5. #include <string.h>
  6. #include <stdio.h>
  7. #include <pthread.h>
  8. #include <sys/select.h>
  9. #define BUFFLEN 1024
  10. #define SERVER_PORT 12349
  11. #define BACKLOG 5
  12. #define CLIENTNUM 1024 /**最大支持客户端数量*/
  13.  
  14. /*可连接客户端的文件描述符数组*/
  15. int connect_host[CLIENTNUM];
  16. int connect_number = ;
  17. static void *handle_request(void *argv)
  18. {
  19. time_t now; /*时间*/
  20. char buff[BUFFLEN];/*收发数据缓冲区*/
  21. int n = ;
  22.  
  23. int maxfd = -;/*最大侦听文件描述符*/
  24. fd_set scanfd; /*侦听描述符集合*/
  25. struct timeval timeout; /*超时*/
  26. timeout.tv_sec = ; /* 阻塞1秒后超时返回 */
  27. timeout.tv_usec = ;
  28.  
  29. int i = ;
  30. int err = -;
  31. for(;;)
  32. {
  33. /*最大文件描述符值初始化为-1*/
  34. maxfd = -;
  35. FD_ZERO(&scanfd);/*清零文件描述符集合*/
  36. for(i=;i<CLIENTNUM;i++)/*将文件描述符放入集合*/
  37. {
  38. if(connect_host[i] != -)/*合法的文件描述符*/
  39. {
  40. FD_SET(connect_host[i], &scanfd);/*放入集合*/
  41. if(maxfd < connect_host[i])/*更新最大文件描述符值*/
  42. {
  43. maxfd = connect_host[i];
  44. }
  45. }
  46. }
  47. /*select等待*/
  48. err = select(maxfd + , &scanfd, NULL, NULL, &timeout) ;
  49. switch(err)
  50. {
  51. case :/*超时*/
  52. break;
  53. case -:/*错误发生*/
  54. break;
  55. default:/*有可读套接字文件描述符*/
  56. if(connect_number<=)
  57. break;
  58. for(i = ;i<CLIENTNUM;i++)
  59. {
  60. /*查找激活的文件描述符*/
  61. if(connect_host[i] != -)
  62. if(FD_ISSET(connect_host[i],&scanfd))
  63. {
  64. memset(buff, , BUFFLEN);/*清零*/
  65. n = recv(connect_host[i], buff, BUFFLEN,);/*接收发送方数据*/
  66. if(n > && !strncmp(buff, "TIME", ))/*判断是否合法接收数据*/
  67. {
  68. memset(buff, , BUFFLEN);/*清零*/
  69. now = time(NULL);/*当前时间*/
  70. sprintf(buff, "%24s\r\n",ctime(&now));/*将时间拷贝入缓冲区*/
  71. send(connect_host[i], buff, strlen(buff),);/*发送数据*/
  72. }
  73. /*关闭客户端*/
  74. close(connect_host[i]);
  75. /*更新文件描述符在数组中的值*/
  76. connect_host[i] = -;
  77. connect_number --; /*客户端计数器减1*/
  78. }
  79. }
  80. break;
  81. }
  82. }
  83.  
  84. return NULL;
  85. }
  86.  
  87. static void *handle_connect(void *argv)
  88. {
  89. int s_s = *((int*)argv) ;/*获得服务器侦听套接字文件描述符*/
  90. int s_c = -;/*连接客户端文件描述符*/
  91. struct sockaddr_in from;
  92. int len = sizeof(from);
  93. /*接收客户端连接*/
  94. for(;;)
  95. {
  96. int i = ;
  97. int s_c = accept(s_s, (struct sockaddr*)&from, &len);/*接收客户端的请求*/
  98. printf("a client connect, from:%s\n",inet_ntoa(from.sin_addr));
  99. /*查找合适位置,将客户端的文件描述符放入*/
  100. for(i=;i<CLIENTNUM;i++)
  101. {
  102. if(connect_host[i] == -)/*找到*/
  103. {
  104. /*放入*/
  105. connect_host[i]= s_c;
  106.  
  107. /*客户端计数器加1*/
  108. connect_number ++;
  109. /*继续轮询等待客户端连接*/
  110. break;
  111. }
  112. }
  113. }
  114. return NULL;
  115. }
  116.  
  117. int main(int argc, char *argv[])
  118. {
  119. int s_s; /*服务器套接字文件描述符*/
  120. struct sockaddr_in local; /*本地地址*/
  121. int i = ;
  122. memset(connect_host, -, CLIENTNUM);
  123.  
  124. /*建立TCP套接字*/
  125. s_s = socket(AF_INET, SOCK_STREAM, );
  126.  
  127. /*初始化地址接哦股*/
  128. memset(&local, , sizeof(local));/*清零*/
  129. local.sin_family = AF_INET;/*AF_INET协议族*/
  130. local.sin_addr.s_addr = htonl(INADDR_ANY);/*任意本地地址*/
  131. local.sin_port = htons(SERVER_PORT);/*服务器端口*/
  132.  
  133. /*将套接字文件描述符绑定到本地地址和端口*/
  134. int err = bind(s_s, (struct sockaddr*)&local, sizeof(local));
  135. err = listen(s_s, BACKLOG);/*侦听*/
  136.  
  137. pthread_t thread_do[];/*线程ID*/
  138. /*创建线程处理客户端连接*/
  139. pthread_create(&thread_do[],/*线程ID*/
  140. NULL,/*属性*/
  141. handle_connect,/*线程回调函数*/
  142. (void*)&s_s); /*线程参数*/
  143. /*创建线程处理客户端请求*/
  144. pthread_create(&thread_do[],/*线程ID*/
  145. NULL,/*属性*/
  146. handle_request,/*线程回调函数*/
  147. NULL); /*线程参数*/
  148. /*等待线程结束*/
  149. for(i=;i<;i++)
  150. pthread_join(thread_do[i], NULL);
  151.  
  152. close(s_s);
  153.  
  154. return ;
  155. }

Linux网络编程服务器模型选择之IO复用循环并发服务器的更多相关文章

  1. Linux网络编程服务器模型选择之并发服务器(上)

    与循环服务器的串行处理不同,并发服务器对服务请求并发处理.循环服务器只能够一个一个的处理客户端的请求,显然效率很低.并发服务器通过建立多个子进程来实现对请求的并发处理.并发服务器的一个难点是如何确定子 ...

  2. Linux网络编程服务器模型选择之循环服务器

    在网络程序里面,通常都是一个服务器处理多个客户机,为了出个多个客户机的请求,服务器端的程序有不同的处理方式.本节开始介绍Linux下套接字编程的服务器模型选择,主要包括循环服务器模型.并发服务器模型. ...

  3. Linux网络编程服务器模型选择之并发服务器(下)

    前面两篇文章(参见)分别介绍了循环服务器和简单的并发服务器网络模型,我们已经知道循环服务器模型效率较低,同一时刻只能为一个客户端提供服务,而且对于TCP模型来说,还存在单客户端长久独占与服务器的连接, ...

  4. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  5. Linux 网络编程的5种IO模型:信号驱动IO模型

    Linux 网络编程的5种IO模型:信号驱动IO模型 背景 上一讲 Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 我们讲解了多路复用等方面的知识,以及有关例程. ...

  6. Linux 网络编程的5种IO模型:异步IO模型

    Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...

  7. Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO

    背景 整理之前学习socket编程的时候复习到了多路复用,搜索了有关资料,了解到多路复用也有局限性,本着打破砂锅问到底的精神,最终找到了关于IO模型的知识点. 在<Unix网络编程>一书中 ...

  8. Linux网络编程一步一步学【转】

    转自:http://blog.chinaunix.net/uid-10747583-id-297982.html Linux网络编程一步一步学+基础  原文地址:http://blogold.chin ...

  9. Linux网络编程(五)

    /*Linux网络编程(五)——多路IO复用之select() 网络编程中,使用IO复用的典型场合: 1.当客户处理多个描述字时(交互式输入以及网络接口),必须使用IO复用. 2.一个客户同时处理多个 ...

随机推荐

  1. MySQL性能调优与架构设计——第10章 MySQL数据库Schema设计的性能优化

    第10章 MySQL Server性能优化 前言: 本章主要通过针对MySQL Server(mysqld)相关实现机制的分析,得到一些相应的优化建议.主要涉及MySQL的安装以及相关参数设置的优化, ...

  2. MS EXCEL2013添加Oracle Web ADI菜单

  3. delphi Overload 和override的区别

    overload是重载;相同的函数名,参数不同,使用不同的函数体   override   是对父类声明的vitural或dynamic方法进行覆盖 overload的使用方法: [delphi] v ...

  4. Github 的注册教程和初步使用体验

    我叫许晴,是网工143的学生,学号是1413042064,兴趣包括手绘,看书和手游.学习过c++和汇编语言课程,但在编程方面没什么独立实践经验. 我的Githup用户名是 XQ123 .下面是我在gi ...

  5. [LeetCode 题解]: Linked List Cycle II

    Given a linked list, return the node where the cycle begins. If there is no cycle, return null. Foll ...

  6. Jenkins pipeline中使用内置全局变量

    在pipeline中不像在windows batch command中直接%WORKSPACE%这样 需要写成这样: echo env.WORKSPACE

  7. DNS本机可解析,其他主机通过本机无法解析问题

    新建了一个redhat虚拟机,将此虚拟机作为dns服务器使用,配置完以后宿主机的dns服务器设置为配置好的虚拟机地址,结果总是显示no Server Reached,没有服务器可以到达,花了很长时间终 ...

  8. sql查询优化--数字转换字符串字段

    SELECT top 1 pt.* FROM t1where id='20180731223014' SELECT top 1 pt.* FROM t1where id='0180731223014 ...

  9. 使用Emit实现给实体赋值

    Dapper.net的速度很快,最近看源码,原来他orm的实现是通过编写大量IL代码实现的. 使用DynamicMethod,自己编织一个给实体赋值的方法.这种写法效率很高,接近直接对属性赋值.比使用 ...

  10. C# BackgroundWorker 的使用、封装

    示例代码: PT_USER_INFO user = new PT_USER_INFO(); IList<TES_COMBAT_TASK> taskList = new List<TE ...