1. 高性能I/O

(1)通常,recv函数没有数据可用时会阻塞等待。同样,当socket发送缓冲区没有足够多空间来发送消息时,函数send会阻塞。

(2)当socket在非阻塞模式下,这些函数不会阻塞,如果发送/接收缓冲区没有数据时,调用会失败并设置errno为EWOULDBLOCK或EAGAIN。

(3)可以调用fcntl函数实现非阻塞式I/O或调用select实现I/O多路复用以提高使用I/O而出现的效率问题。

2. 非阻塞I/O模型: fcntl函数

【编程实验】echo服务器(非阻塞IO方式实现)

(1)主线程创建一个服务于所有客户端的子线程。

(2)主线程调用accept与客户端建立连接,并将新的socket设置为非阻塞方式。然后将这个新的socket放入数组中。

(3)利用一个子线程遍历(轮询)数组中各个socket,并调用read/write(非阻塞式)与客户端进行通信。

//vector_fd.h

  1. #ifndef __VECTOR_H__
  2. #define __VECTOR_H__
  3.  
  4. #include <pthread.h>
  5.  
  6. //用于存放sock的动态数组(线程安全!)
  7. typedef struct{
  8. int *fd;
  9. int counter; //元素个数
  10. int max_counter;//最多存数个数,会动态增长
  11. pthread_mutex_t mutex;
  12. }VectorFD, *PVectorFD;
  13.  
  14. //动态数组相关的操作函数
  15. extern VectorFD* create_vector_fd(void);
  16. extern void destroy_vector_fd(VectorFD* vfd);
  17. extern int get_fd(VectorFD* vfd, int index);
  18. extern void remove_fd(VectorFD* vfd, int fd);
  19. extern void add_fd(VectorFD* vfd, int fd);
  20.  
  21. #endif

//vector_fd.c  //动态数组操作函数

  1. #include "vector_fd.h"
  2. #include <memory.h>
  3. #include <malloc.h>
  4. #include <assert.h>
  5.  
  6. //查找指定fd在数组中的索引值
  7. static int indexof(VectorFD* vfd, int fd)
  8. {
  9. int ret = -;
  10.  
  11. int i=;
  12. for(; i<vfd->counter; i++){
  13. if(vfd->fd[i] == fd){
  14. ret = i;
  15. break;
  16. }
  17. }
  18.  
  19. return ret;
  20. }
  21.  
  22. //数组空间的动态增长
  23. static void encapacity(VectorFD* vfd)
  24. {
  25. if(vfd->counter >=vfd->max_counter){
  26. int* fds = (int*)calloc(vfd->counter + , sizeof(int));
  27. assert(fds != NULL);
  28. memcpy(fds, vfd->fd, sizeof(int) * vfd->counter);
  29.  
  30. free(vfd->fd);
  31. vfd->fd = fds;
  32. vfd->max_counter += ;
  33. }
  34. }
  35.  
  36. //动态数组相关的操作
  37. VectorFD* create_vector_fd(void)
  38. {
  39. VectorFD* vfd = (VectorFD*)calloc(, sizeof(VectorFD));
  40. assert(vfd != NULL);
  41.  
  42. //分配存放fd的数组空间
  43. vfd->fd = (int*)calloc(, sizeof(int));
  44. assert(vfd->fd != NULL);
  45.  
  46. vfd->counter = ;
  47. vfd->max_counter = ;
  48.  
  49. //对互斥锁进行初始化
  50. pthread_mutex_init(&vfd->mutex, NULL);
  51.  
  52. return vfd;
  53. }
  54.  
  55. void destroy_vector_fd(VectorFD* vfd)
  56. {
  57. assert(vfd != NULL);
  58. //销毁互斥锁
  59. pthread_mutex_destroy(&vfd->mutex);
  60.  
  61. free(vfd->fd);
  62. free(vfd);
  63. }
  64.  
  65. int get_fd(VectorFD* vfd, int index)
  66. {
  67. int ret = ;
  68. assert(vfd != NULL);
  69.  
  70. pthread_mutex_lock(&vfd->mutex);
  71.  
  72. if(( <= index) && (index < vfd->counter)){
  73. ret = vfd->fd[index];
  74. }
  75.  
  76. pthread_mutex_unlock(&vfd->mutex);
  77.  
  78. return ret;
  79. }
  80.  
  81. void remove_fd(VectorFD* vfd, int fd)
  82. {
  83. assert(vfd != NULL);
  84.  
  85. pthread_mutex_lock(&vfd->mutex);
  86.  
  87. int index = indexof(vfd, fd);
  88.  
  89. if(index >= ){
  90. int i = index;
  91. for(; i<vfd->counter-; i++){
  92. vfd->fd[i] = vfd->fd[i+];
  93. }
  94.  
  95. vfd->counter--;
  96. }
  97.  
  98. pthread_mutex_unlock(&vfd->mutex);
  99. }
  100.  
  101. void add_fd(VectorFD* vfd, int fd)
  102. {
  103. assert(vfd != NULL);
  104.  
  105. encapacity(vfd);
  106. vfd->fd[vfd->counter++] = fd;
  107. }

//echo_tcp_server_fcntl.c

  1. #include <netdb.h>
  2. #include <sys/socket.h>
  3. #include <unistd.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <memory.h>
  7. #include <time.h>
  8. #include <pthread.h>
  9. #include <signal.h>
  10. #include <errno.h>
  11. #include "vector_fd.h"
  12. #include <fcntl.h>
  13.  
  14. /*基于非阻塞IO的高并发服务器编程
  15. 测试:telnet 127.0.0.1 xxxx
  16. http://xxx.xxx.xxx.xxx:端口号
  17. 注意:演示时可关闭服务器的防火墙,防火墙口被过滤
  18. #service iptables status 查看防火墙
  19. #service iptables stop 关闭防火墙
  20. */
  21.  
  22. VectorFD* vfd;
  23. int sockfd;
  24. int bStop = ;
  25.  
  26. void sig_handler(int signo)
  27. {
  28. if(signo == SIGINT){
  29. bStop = ;
  30. printf("server close\n");
  31. exit();
  32. }
  33. }
  34.  
  35. void out_addr(struct sockaddr_in* clientAddr)
  36. {
  37. char ip[];
  38. memset(ip, , sizeof(ip));
  39. int port = ntohs(clientAddr->sin_port);
  40. inet_ntop(AF_INET, &clientAddr->sin_addr.s_addr, ip, sizeof(ip));
  41.  
  42. printf("%s(%d) connnected!\n", ip, port);
  43. }
  44.  
  45. /*服务程序
  46. * fd对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
  47. */
  48. void do_service(int fd)
  49. {
  50. /*服务端和客户端进行读写操作(双向通信)*/
  51. char buff[];
  52.  
  53. memset(buff, , sizeof(buff));
  54. size_t size = read(fd, buff, sizeof(buff));
  55.  
  56. //读取客户端发送过来的消息
  57. //由于采用非阻塞方式,若读不到数据直接返回了,直接服务于下一个客户端
  58. //因此不需要判断size小于0的情况。
  59. if(size == ){ //客户端己关闭连接
  60. char info[] = "client close\n";
  61. write(STDOUT_FILENO, info, sizeof(info));
  62.  
  63. //将fd从动态数组中删除
  64. remove_fd(vfd, fd);
  65. close(fd);
  66. }else if(size > ){
  67. write(STDOUT_FILENO, buff, sizeof(buff));//显示客户端发送的消息
  68. //写回客户端(回显功能)
  69. if(write(fd, buff, sizeof(buff)) != size){
  70. if(errno == EPIPE){
  71. //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号
  72. //并将errno设置为EPIPE
  73. perror("write error");
  74. remove_fd(vfd, fd);
  75. close(fd);
  76. }
  77. }
  78. }
  79. }
  80.  
  81. //线程函数
  82. void* th_fn(void* arg)
  83. {
  84. int i= ;
  85. //轮询动态数组中的socket描述符
  86. while(!bStop){
  87. for(i=; i<vfd->counter; i++){
  88. do_service(get_fd(vfd, i));
  89. }
  90. }
  91.  
  92. return (void*);
  93. }
  94.  
  95. int main(int argc, char* argv[])
  96. {
  97. if(argc < ){
  98. printf("usage: %s port\n", argv[]);
  99. exit();
  100. }
  101.  
  102. //按ctrl-c时中止服务端程序
  103. if(signal(SIGINT, sig_handler) == SIG_ERR){
  104. perror("signal sigint error");
  105. exit();
  106. }
  107.  
  108. /*步骤1:创建socket(套接字)
  109. *注:socket创建在内核中,是一个结构体
  110. *AF_INET:IPv4
  111. *SOCK_STREAM:tcp协议
  112. */
  113. sockfd = socket(AF_INET, SOCK_STREAM, );
  114.  
  115. /*步骤2:将sock和地址(包括ip、port)进行绑定*/
  116. struct sockaddr_in servAddr; //使用专用地址结构体
  117. memset(&servAddr, , sizeof(servAddr));
  118. //往地址中填入ip、port和Internet地址族类型
  119. servAddr.sin_family = AF_INET;//IPv4
  120. servAddr.sin_port = htons(atoi(argv[])); //port
  121. servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP
  122.  
  123. if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < ){
  124. perror("bind error");
  125. exit();
  126. }
  127.  
  128. /*步骤3:调用listen函数启动监听
  129. * 通知系统去接受来自客户端的连接请求
  130. */
  131. if(listen(sockfd, ) < ){ //队列中最多允许10个连接请求
  132. perror("listen error");
  133. exit();
  134. }
  135.  
  136. //创建放置套接字描述符的动态数组
  137. vfd = create_vector_fd();
  138.  
  139. //设置线程的分离属性
  140. pthread_t th;
  141. pthread_attr_t attr;
  142. pthread_attr_init(&attr);
  143. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  144. //启动子线程
  145. int err;
  146. if((err = pthread_create(&th, &attr, th_fn, (void*))) != ){
  147. perror("pthread create error");
  148. exit();
  149. }
  150. pthread_attr_destroy(&attr);
  151.  
  152. /*(1)主线程获得客户端连接,将新的socket描述符放置到动态数组中
  153. *(2)子线程负责遍历动态数组中socket描述符并和对应的客户端进行
  154. * 双向通信(采用非阻塞方式读写)
  155. */
  156.  
  157. struct sockaddr_in clientAddr;
  158. socklen_t len = sizeof(clientAddr);
  159.  
  160. while(!bStop){
  161. /*步骤4:调用accept函数,从请求队列中获取一个连接
  162. * 并返回新的socket描述符
  163. * */
  164. int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &len);
  165.  
  166. if(fd < ){
  167. perror("accept error");
  168. continue;
  169. }
  170.  
  171. //将读写修改为非阻塞方式
  172. int val;
  173. fcntl(fd, F_GETFL, &val);
  174. val |= O_NONBLOCK; //非阻塞式
  175. fcntl(fd, F_SETFL, val);
  176.  
  177. //输出客户端信息
  178. out_addr(&clientAddr);
  179.  
  180. //将返回的新socket描述符加入到动态数组中
  181. add_fd(vfd, fd);
  182. }
  183.  
  184. close(sockfd);
  185. destroy_vector_fd(vfd);
  186.  
  187. return ;
  188. }
  189. /*输出结果
  190. * [root@localhost 15.AdvNet]# gcc -o bin/echo_tcp_client src/echo_tcp_client.c
  191. * [root@localhost 15.AdvNet]# bin/echo_tcp_server_fcntl 8888
  192. * 127.0.0.1(40694) connnected!
  193. * abcdefaadeafcdafacdaegadeageadfacadegadaddeagdafddeagd^Cserver close
  194. */

//echo_tcp_client.c

  1. #include <netdb.h>
  2. #include <sys/socket.h>
  3. #include <unistd.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <memory.h>
  7.  
  8. int main(int argc, char* argv[])
  9. {
  10. if(argc < ){
  11. printf("usage: %s ip port\n", argv[]);
  12. exit();
  13. }
  14.  
  15. /*步骤1: 创建socket(套接字)*/
  16. int sockfd = socket(AF_INET, SOCK_STREAM, );
  17. if(sockfd < ){
  18. perror("socket error");
  19. }
  20.  
  21. //往servAddr中填入ip、port和地址族类型
  22. struct sockaddr_in servAddr;
  23. memset(&servAddr, , sizeof(servAddr));
  24. servAddr.sin_family = AF_INET;
  25. servAddr.sin_port = htons(atoi(argv[]));
  26. //将ip地址转换成网络字节序后填入servAdd中
  27. inet_pton(AF_INET, argv[], &servAddr.sin_addr.s_addr);
  28.  
  29. /*步骤2: 客户端调用connect函数连接到服务器端*/
  30. if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < ){
  31. perror("connect error");
  32. exit();
  33. }
  34.  
  35. /*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/
  36. char buff[];
  37. size_t size;
  38. char* prompt = ">";
  39.  
  40. while(){
  41. memset(buff, , sizeof(buff));
  42. write(STDOUT_FILENO, prompt, );
  43. size = read(STDIN_FILENO, buff, sizeof(buff));
  44. if(size < ) continue;
  45.  
  46. buff[size-] = '\0';
  47. //将键盘输入的内容发送到服务端
  48. if(write(sockfd, buff, sizeof(buff)) < ){
  49. perror("write error");
  50. continue;
  51. }else{
  52. memset(buff, , sizeof(buff));
  53. //读取来自服务端的消息
  54. if(read(sockfd, buff, sizeof(buff)) < ){
  55. perror("read error");
  56. continue;
  57. }else{
  58. printf("%s\n", buff);
  59. }
  60. }
  61. }
  62.  
  63. /*关闭套接字*/
  64. close(sockfd);
  65. }
  66. /*输出结果
  67. *[root@localhost 15.AdvNet]# bin/echo_tcp_client 127.0.0.1 8888
  68. >abcdef
  69. abcdef
  70. >aade
  71. aade
  72. >afcdaf
  73. afcdaf
  74. >acdaeg
  75. acdaeg
  76. >^C
  77. */

第15章 高并发服务器编程(1)_非阻塞I/O模型的更多相关文章

  1. 第15章 高并发服务器编程(2)_I/O多路复用

    3. I/O多路复用:select函数 3.1 I/O多路复用简介 (1)通信领域的时分多路复用 (2)I/O多路复用(I/O multiplexing) ①同一线程,通过“拨开关”方式,来同时处理多 ...

  2. SpringCloud、Nginx高并发核心编程 【2020年11月新书 】

    文章太长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典极品 : 三大本< Java 高并发 三部曲 > 面试 + 大厂 + 涨薪必备 疯狂创客圈 经 ...

  3. Linux + C + Epoll实现高并发服务器(线程池 + 数据库连接池)(转)

    转自:http://blog.csdn.net/wuyuxing24/article/details/48758927 一, 背景 先说下我要实现的功能,server端一直在linux平台下面跑,当客 ...

  4. Linux下高并发网络编程

      Linux下高并发网络编程 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时, 最高的并发数量都要受到系统对用户单一进程同时可打 ...

  5. JAVA NIO non-blocking模式实现高并发服务器

    JAVA NIO non-blocking模式实现高并发服务器 分类: JAVA NIO2014-04-14 11:12 1912人阅读 评论(0) 收藏 举报 目录(?)[+] Java自1.4以后 ...

  6. PHP写的异步高并发服务器,基于libevent

    PHP写的异步高并发服务器,基于libevent 博客分类: PHP PHPFPSocketLinuxQQ  本文章于2013年11月修改. swoole已使用C重写作为PHP扩展来运行.项目地址:h ...

  7. JAVA NIO non-blocking模式实现高并发服务器(转)

    原文链接:JAVA NIO non-blocking模式实现高并发服务器 Java自1.4以后,加入了新IO特性,NIO. 号称new IO. NIO带来了non-blocking特性. 这篇文章主要 ...

  8. 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。

    1. [推荐]高并发服务器建议调小 TCP 协议的 time_wait 超时时间. 说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服 务器端会因为处于 ...

  9. 为一个支持GPRS的硬件设备搭建一台高并发服务器用什么开发比较容易?

    高并发服务器开发,硬件socket发送数据至服务器,服务器对数据进行判断,需要实现心跳以保持长连接. 同时还要接收另外一台服务器的消支付成功消息,接收到消息后控制硬件执行操作. 查了一些资料,java ...

随机推荐

  1. tomcat部署和启动2

    catalina run 启动服务器后,按下CTRL+C,停止服务器,选择"y",退回到正常命令行. catalina stop

  2. Kaggle新手入门之路(完结)

    学完了Coursera上Andrew Ng的Machine Learning后,迫不及待地想去参加一场Kaggle的比赛,却发现从理论到实践的转变实在是太困难了,在此记录学习过程. 一:安装Anaco ...

  3. P1002 谁拿了最多奖学金

    P1002 谁拿了最多奖学金 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 NOIP2005复赛提高组第一题 描述 某校的惯例是在每学期的期末考试之后发放奖 ...

  4. asp.net mvc 快捷下拉列表

    各种表单中可能经常会遇到使用各种下拉列表的地方, 有些数据是从数据库来的, 有些则是固定数值, 为了方便, 快速的构造一个可以保持状态的下拉列表, 就出现了下面的方法 2分钟构思的代码, 比较粗糙, ...

  5. magento -- 如何在magento中进行产品的批量上传

    花费了好多时间,阅读了magento官方论坛上几乎所有的批量上传产品的相关帖子,分析了大量相关magento代码,终于可以完全实现指产品批量上传的功能,免除网速慢,在页面之间跳来跳去,以及重复输入数据 ...

  6. python3 线性同余发生器 ( random 随机数生成器 ) 伪随机数产生周期的一些探究

    import random x=[str(random.randint(0, 5)) for i in range(10)] x_str=''.join(x) y=[str(random.randin ...

  7. 【机器学习算法】Boostrapping算法

    参考 1.AdaBoost从原理到实现: 完

  8. Activity的四大启动模式

    在自己清单中的Activity里配置这四大启动之一. stander    标准模式   先进后出 singletop     会检查栈顶如果有,那么就复用,不会重新开启. singletask    ...

  9. 大家一起做训练 第一场 G CD

    题目来源:UVA 624 题目的意思就是:我现在需要从 t 张CD中拿出一部分来,尽可能的凑出接近 N 这么久的音乐,但是不能超过 N. CD不超过20张,每张长度不超过 N ,不能重复选. 一个很简 ...

  10. (1)集合 ---遍历map集合

    Map接口     实现Map接口的类用来存储键(key)-值(value) 对.Map 接口的实现类有HashMap和TreeMap等.Map类中存储的键-值对通过键来标识,所以键值不能重复. Ha ...