59.1 介绍

  前面介绍的函数如,recv、send、read 和 write 等函数都是阻塞性函数,若资源没有准备好,则调用该函数的进程将进入阻塞状态。我们可以使用 I/O 多路复用来解决此问题(即解决并发)。

  • I/O 多路复用的方式主要有两种实现方法

    • fcntl 函数实现(非阻塞方式)
    • select 函数实现

59.1.1 fcntl 非阻塞方式——I/O多路复用/转换

  

59.2 例子

59.2.1 动态数组模块

vector_fd.c

  1. #include <malloc.h>
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include <stdlib.h>
  5. #include <memory.h>
  6. #include "vector_fd.h"
  7.  
  8. static void encapacity(vector_fd *vfd)
  9. {
  10. if(vfd->counter >= vfd->max_counter){
  11. int *fds = (int *)calloc(vfd->counter + , sizeof(int));
  12. assert(fds != NULL);
  13. memcpy(fds, vfd->fd, sizeof(int) * vfd->counter);
  14. free(vfd->fd);
  15. vfd->fd = fds;
  16. vfd->max_counter += ;
  17. }
  18. }
  19.  
  20. static int indexof(vector_fd *vfd, int fd)
  21. {
  22. int i = ;
  23. for(; i < vfd->counter; i++){
  24. if(vfd->fd[i] == fd) return i;
  25. }
  26.  
  27. return -;
  28. }
  29.  
  30. vector_fd *create_vector_fd(void)
  31. {
  32. vector_fd *vfd = (vector_fd *)calloc(, sizeof(vector_fd));
  33. assert(vfd != NULL);
  34.  
  35. vfd->fd = (int *)calloc(, sizeof(int));
  36. assert(vfd->fd != NULL);
  37. vfd->counter = ;
  38. vfd->max_counter = ;
  39.  
  40. return vfd;
  41. }
  42.  
  43. void destroy_vector_fd(vector_fd *vfd)
  44. {
  45. assert(vfd != NULL);
  46. free(vfd->fd);
  47. free(vfd);
  48. }
  49.  
  50. int get_fd(vector_fd *vfd, int index)
  51. {
  52. assert(vfd != NULL);
  53. if(index < || index > vfd->counter - ) return ;
  54.  
  55. return vfd->fd[index];
  56. }
  57.  
  58. void remove_fd(vector_fd *vfd, int fd)
  59. {
  60. assert(vfd != NULL);
  61. int index = indexof(vfd, fd);
  62. if(index == -) return;
  63. int i = index;
  64. for(; i < vfd->counter - ; i++){
  65. vfd->fd[i] = vfd->fd[i + ];
  66. }
  67. vfd->counter--;
  68. }
  69.  
  70. void add_fd(vector_fd *vfd, int fd)
  71. {
  72. assert(vfd != NULL);
  73. encapacity(vfd);
  74. vfd->fd[vfd->counter++] = fd;
  75. }

vector_fd.h

  1. #ifndef __VECTOR_FD_H__
  2. #define __VECTOR_FD_H__
  3.  
  4. typedef struct {
  5. int *fd;
  6. int counter;
  7. int max_counter;
  8. }vector_fd;
  9.  
  10. extern vector_fd *create_vector_fd(void);
  11. extern void destroy_vector_fd(vector_fd *);
  12. extern int get_fd(vector_fd *, int index);
  13. extern void remove_fd(vector_fd *, int fd);
  14. extern void add_fd(vector_fd *, int fd);
  15.  
  16. #endif

  编译成模块:gcc -o obj/vector_fd.o -Iinclude -c src/vector_fd.c

59.2.2 服务器端

  echo_tcp_server_fcntl.c

  1. #include <netdb.h>
  2. #include <netinet/in.h>
  3. #include <sys/socket.h>
  4. #include <sys/wait.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <memory.h>
  10. #include <signal.h>
  11. #include <fcntl.h>
  12. #include <time.h>
  13. #include <arpa/inet.h>
  14. #include <errno.h>
  15. #include <pthread.h>
  16. #include "vector_fd.h"
  17.  
  18. vector_fd *vfd;
  19. int sockfd;
  20.  
  21. void sig_handler(int signo)
  22. {
  23. if(signo == SIGINT){
  24. printf("server close\n");
  25. /** 步骤6: 关闭 socket */
  26. close(sockfd);
  27. /** 销毁动态数组 */
  28. destroy_vector_fd(vfd);
  29. exit();
  30. }
  31. }
  32.  
  33. /**
  34. * fd 对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式)
  35. */
  36. void do_service(int fd)
  37. {
  38. char buff[];
  39. memset(buff, , sizeof(buff));
  40.  
  41. /**
  42. * 因为采用非阻塞方式,若读不到数据直接返回,
  43. * 直接服务于下一个客户端,
  44. * 因此不需要判断 size < 0 的情况 */
  45. ssize_t size = read(fd, buff, sizeof(buff));
  46.  
  47. if(size == ){
  48. /** 客户端已经关闭连接 */
  49. char info[] = "client closed";
  50. write(STDOUT_FILENO, info, sizeof(info));
  51. /** 从动态数组中删除对应的 fd */
  52. remove_fd(vfd, fd);
  53. /** 关闭对应客户端的 socket */
  54. close(fd);
  55. }
  56. else if(size > ){
  57. write(STDOUT_FILENO, buff, sizeof(buff));
  58. if(write(fd, buff, size) < ){
  59. if(errno == EPIPE){
  60. /** 客户端关闭连接 */
  61. perror("write error");
  62. remove_fd(vfd, fd);
  63. close(fd);
  64. }
  65. perror("protocal error");
  66. }
  67. }
  68. }
  69.  
  70. void out_addr(struct sockaddr_in *clientaddr)
  71. {
  72. char ip[];
  73. memset(ip, , sizeof(ip));
  74. int port = ntohs(clientaddr->sin_port);
  75. inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
  76. printf("%s(%d) connected!\n", ip, port);
  77. }
  78.  
  79. void *th_fn(void *arg)
  80. {
  81. int i;
  82. while(){
  83. i = ;
  84. /** 遍历动态数组中的 socket 描述符 */
  85. for(; i < vfd->counter; i++){
  86. do_service(get_fd(vfd, i));
  87. }
  88. }
  89.  
  90. return (void *);
  91. }
  92.  
  93. int main(int argc, char *argv[])
  94. {
  95. if(argc < ){
  96. printf("usage: %s #port\n", argv[]);
  97. exit();
  98. }
  99.  
  100. if(signal(SIGINT, sig_handler) == SIG_ERR){
  101. perror("signal sigint error");
  102. exit();
  103. }
  104.  
  105. /** 步骤1: 创建 socket(套接字)
  106. * 注: socket 创建在内核中,是一个结构体.
  107. * AF_INET: IPV4
  108. * SOCK_STREAM: tcp 协议
  109. * AF_INET6: IPV6
  110. */
  111. sockfd = socket(AF_INET, SOCK_STREAM, );
  112. if(sockfd < ){
  113. perror("socket error");
  114. exit();
  115. }
  116.  
  117. /**
  118. * 步骤2: 调用 bind 函数将 socket 和地址(包括 ip、port)进行绑定
  119. */
  120. struct sockaddr_in serveraddr;
  121. memset(&serveraddr, , sizeof(struct sockaddr_in));
  122. /** 往地址中填入 ip、port、internet 地址族类型 */
  123. serveraddr.sin_family = AF_INET; ///< IPV4
  124. serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口
  125. serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址
  126. if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
  127. perror("bind error");
  128. exit();
  129. }
  130.  
  131. /**
  132. * 步骤3: 调用 listen 函数启动监听(指定 port 监听)
  133. * 通知系统去接受来自客户端的连接请求
  134. * (将接受到的客户端连接请求放置到对应的队列中)
  135. * 第二个参数: 指定队列的长度
  136. */
  137. if(listen(sockfd, ) < ){
  138. perror("listen error");
  139. exit();
  140. }
  141.  
  142. /** 创建放置套接字描述符 fd 的动态数组 */
  143. vfd = create_vector_fd();
  144.  
  145. /** 设置线程的分离属性 */
  146. pthread_t th;
  147. pthread_attr_t attr;
  148. pthread_attr_init(&attr);
  149. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  150. int err;
  151. if((err = pthread_create(&th, &attr, th_fn, (void *))) != ){
  152. perror("pthread create error");
  153. exit();
  154. }
  155. pthread_attr_destroy(&attr);
  156.  
  157. /**
  158. * 1)主控线程获得客户端的链接,将新的 socket 描述符放置到动态数组中
  159. * 2)启动的子线程负责遍历动态数组中 socket
  160. * 描述符,并和对应的客户端进行双向通信(采用非阻塞方式读写)
  161. */
  162. struct sockaddr_in clientaddr;
  163. socklen_t len = sizeof(clientaddr);
  164.  
  165. while(){
  166. /**
  167. * 步骤4: 调用 accept 函数从队列中获得一个客户端的请求连接, 并返回新的
  168. * socket 描述符
  169. * 注意: 若没有客户端连接,调用此函数后会阻塞, 直到获得一个客户端的连接
  170. */
  171. /** 主控线程负责调用 accept 去获得客户端的连接 */
  172. int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
  173. if(fd < ){
  174. perror("accept error");
  175. continue;
  176. }
  177.  
  178. out_addr(&clientaddr);
  179.  
  180. /** 将读写修改为非阻塞方式 */
  181. int val;
  182. fcntl(fd, F_GETFL, &val); ///< 获取原来的状态标志
  183. val |= O_NONBLOCK;
  184. fcntl(fd, F_SETFL, val); ///< 添加新的状态标志
  185.  
  186. /** 将返回的新的 socket 描述符加入到动态数组中 */
  187. add_fd(vfd, fd);
  188.  
  189. }
  190.  
  191. return ;
  192. }

  编译:

  gcc -o bin/echo_tcp_server_fcntl -Iinclude obj/vector_fd.o src/echo_tcp_server_fcntl.c -lpthread

59.2.3 客户端

  echo_tcp_client_fcntl.c

  1. #include <sys/types.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <memory.h>
  5. #include <unistd.h>
  6. #include <sys/socket.h>
  7. #include <netdb.h>
  8. #include <signal.h>
  9. #include <string.h>
  10. #include <time.h>
  11. #include <arpa/inet.h>
  12.  
  13. int main(int argc, char *argv[])
  14. {
  15. if(argc < ){
  16. printf("usage: %s ip port\n", argv[]);
  17. exit();
  18. }
  19.  
  20. /** 步骤1: 创建 socket */
  21. int sockfd = socket(AF_INET, SOCK_STREAM, );
  22. if(sockfd < ){
  23. perror("socket error");
  24. exit();
  25. }
  26.  
  27. /** 往 serveraddr 中填入 ip、port 和地址族类型(ipv4) */
  28. struct sockaddr_in serveraddr;
  29. memset(&serveraddr, , sizeof(struct sockaddr_in));
  30. serveraddr.sin_family = AF_INET;
  31. serveraddr.sin_port = htons(atoi(argv[]));
  32. /** 将 ip 地址转换成网络字节序后填入 serveraddr 中 */
  33. inet_pton(AF_INET, argv[], &serveraddr.sin_addr.s_addr);
  34.  
  35. /**
  36. * 步骤2: 客户端调用 connect 函数连接到服务器端
  37. */
  38. if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < ){
  39. perror("connect error");
  40. exit();
  41. }
  42.  
  43. /** 步骤3: 调用 IO 函数(read/write)和服务器端进行双向通信 */
  44. char buff[];
  45. ssize_t size;
  46. char *prompt = "==>";
  47. while(){
  48. memset(buff, , sizeof(buff));
  49. write(STDOUT_FILENO, prompt, );
  50. size = read(STDIN_FILENO, buff, sizeof(buff));
  51. if(size < ) continue;
  52. buff[size - ] = '\0';
  53.  
  54. if(write(sockfd, buff, sizeof(buff)) < ){
  55. perror("write msg error");
  56. continue;
  57. }
  58. else {
  59. if(read(sockfd, buff, sizeof(buff)) < ){
  60. perror("read msg error");
  61. continue;
  62. }
  63. else {
  64. printf("%s\n", buff);
  65. }
  66. }
  67. }
  68.  
  69. /** 步骤4: 关闭 socket */
  70. close(sockfd);
  71.  
  72. return ;
  73. }

  编译:gcc -o bin/echo_tcp_client_fcntl src/echo_tcp_client_fcntl.c

59.2.4 测试运行

  

五十九、linux 编程—— I/O 多路复用 fcntl的更多相关文章

  1. 六十、linux 编程—— I/O 多路复用 select

    60.1 介绍 60.2 例子 echo_tcp_server_select.c #include <netdb.h> #include <netinet/in.h> #inc ...

  2. 第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装

    第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装 elasticsearch(搜索引擎)介绍 ElasticSearch是一个基于 ...

  3. “全栈2019”Java第五十九章:抽象类与抽象方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. SpringBoot进阶教程(五十九)整合Codis

    上一篇博文<详解Codis安装与部署>中,详细介绍了codis的安装与部署,这篇文章主要介绍介绍springboot整合codis.如果之前看过<SpringBoot进阶教程(五十二 ...

  5. JAVA学习第五十九课 — 网络编程概述

    网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...

  6. C#编程(五十九)----------集合的性能

    各种集合的性能 许多集合类提供了相同的功能,例如,SortedList类与SortedDictionary类的功能几乎完全相同.但是,其性能常常有很大的区别.SortedList集合使用的内存少,So ...

  7. Linux学习之CentOS(十九)------linux 下压缩与解压之 tar、gzip、bzip2、zip、rar

    将文件存储到归档文件中或者从归档文件中获取原始文件,以及为文件创建归档文件 tar [option] [modifiers] [file-list] 参数 file-list是tar进行归档和提取的文 ...

  8. 五十三、linux 编程——TCP 编程基本介绍

    53.1 socket 套接字 53.1.1 介绍 Socket(套接字)是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如 TCP/UDP 灯网络协议进行网络通讯的手段 ...

  9. Linux学习之十九-Linux磁盘管理

    Linux磁盘管理 1.相关知识 磁盘,是计算机硬件中不可或缺的部分磁盘,是计算机的外部存储器中类似磁带的装置,将圆形的磁性盘片装在一个方的密封盒子里,这样做的目的是为了防止磁盘表面划伤,导致数据丢失 ...

随机推荐

  1. docker根据配置文件启动redis

    更多docker基本命令请自行查询. 1.首先拉取合适版本的docker镜像 docker pull redis:5 2.如果不需要更改什么配置或者仅仅测试用可以直接启动镜像运行容器,这里要说明的是根 ...

  2. Mysql事务与锁详解

    脏读: 不可重复读: 幻读: 锁: 表级别的意向锁为了提高效率, 我们能给一张表成功加上一个表锁的前提是:没有任何一个事务对这张表的某些行加了锁. 如果没有意向表锁: 如果现在要给一个表加上表锁. 如 ...

  3. 「Python」为什么Python里面,整除的结果会是小数?

    2018-06-08 参考资料:Python学习笔记(4)负数除法和取模运算 先来看三个式子(!这是在Python3.0下的运算结果): 输出结果: ‘//’明明是整除,为什么结果不是整数,而会出现小 ...

  4. Web前端教程-HTML及标签的使用

    目录 1. HTML简介 1.1. HTML文档基本结构 2. 标签 2.1. 标签语法 1.2. 标签的属性和值 1.3. 常见的标签 1. 基础标签 2. 格式标签 3. 表单标签 4. 框架标签 ...

  5. 一个ELK日志检索实施案例

    figure:first-child { margin-top: -20px; } #write ol, #write ul { position: relative; } img { max-wid ...

  6. 前端架构师 摘自《前端架构设计》-micah godbolt

    作为前端架构师,你经常需要制定,或至少能够掌握以上所列的每一项内容.流程中的任何一个环节出现问题,都会迅速演变为开发人员的痛苦,或者导致网站无法持续满足用户要求,甚至崩溃.  前端架构师的用户是开发人 ...

  7. FLOAT 和 DOUBLE区别

    以下是 FLOAT 和 DOUBLE 的区别: float : 单精度浮点数 double : 双精度浮点数 ·浮点数以 8 位精度存储在 FLOAT 中,并且有四个字节. ·浮点数存储在 DOUBL ...

  8. 优先队列Priority Queue和堆Heap

    对COMP20003中的Priority queue部分进行总结.图片来自于COMP20003 queue队列,顾名思义特点先进先出 priority queue优先队列,出来的顺序按照优先级prio ...

  9. NodeJs之定时器与队列

    NodeJs之定时器与队列 一,介绍与需求 1.1,介绍 定时任务(node-schedule),是针对Node.js的一种灵活的cron-like和not-cron-like作业调度程序.它允许您使 ...

  10. oracle密码过期

    1.使用sys,以sysdba登录. sqlplus /nolog conn sys/123456 as sysdba 2.将密码有效期由默认的180天修改成“无限制”,修改之后不需要重启动数据库,会 ...