当前位置: 首页 > news >正文

Socket 编程IO Multiplexing

   Linux Socket 编程中I/O Multiplexing 主要通过三个函数来实现:select, poll,epoll来实现。I/O Multiplexing,先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已准备好可以进行I/O。本文具体介绍一下select 和poll的用法,给出简单的demo代码,简要分析一下这两个函数的使用易出错的地方。        

#include<sys/select.h>

int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, struct timeval* restrict tvptr);

//Returns: count of ready descriptors, 0 on timeout, -1 on error

  中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或出于异常条件的各个描述符,设置为NULL则表示不关心。每个描述符集存放在一个fd_set数据类型中。这种数据类型为每一可能的描述符保持一位。描述符集的函数接口(可能实现为宏)包括:调用FD_ZERO将一个指定的fd_set变量的所有位设置为0;调用FD_SET设置一个fd_set变量的指定位;调用FD_CLR将一指定位清楚;调用FD_ISSET测试一指定位是否设置。

#include <sys/select.h>

int FD_ISSET(int fd, fd_set *fdset);

  //Returns: nonzero if fd is in set, 0 otherwise

void FD_CLR(int fd, fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset);
  

 

  文件描述符集fdset中的文件描述符的个数是有限制的,最大值由FD_SETSIZE指定,一般为1024.

  Select 最后一个参数用于设置超时值,当select监听达到超时值时还未有关心的事件发生则返回,函数返回值为0.

struct timeval{

  long tv_sec;//second

  long tv_usec;//microsecond

}

 

  超时参数如果设置为 NULL 则无限等待。

  下面来是一个简单的select Echo server:

// simpleEcho.cpp
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <vector> #include <string.h> #include <stdlib.h> #include <fcntl.h> #define SEVER_PORT 1314 #define MAX_LINE_LEN 1024 using namespace std; int main() { struct sockaddr_in cli_addr, server_addr; socklen_t sock_len; vector<int> client(FD_SETSIZE,-1); fd_set rset,allset; int listenfd, connfd, sockfd, maxfd, nready, ix,maxid, nrcv,one; char addr_str[INET_ADDRSTRLEN],buf[MAX_LINE_LEN]; bzero(&server_addr,sizeof server_addr); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SEVER_PORT); listenfd = socket(AF_INET,SOCK_STREAM,0); one = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&one, sizeof(one)); if(bind (listenfd ,(struct sockaddr *)&server_addr ,sizeof server_addr) < 0 ) { printf("socket bind error" ); return 0; } listen(listenfd ,10); FD_ZERO(&allset); FD_SET(listenfd ,&allset ); maxfd = listenfd ; maxid = -1 ; while(1 ) { rset = allset; //! nready = select (maxfd + 1, &rset,NULL,NULL,NULL); if(nready < 0 ) { printf("select error! \n" ); exit(1 ); } if(FD_ISSET (listenfd , &rset )) { sock_len = sizeof cli_addr; connfd = accept (listenfd ,(struct sockaddr *)&cli_addr , &sock_len); printf("recieve from : %s at port %d\n" , inet_ntop(AF_INET,&cli_addr .sin_addr ,addr_str ,INET_ADDRSTRLEN ),cli_addr .sin_port ); for(ix = 0 ; ix < static_cast< int>(client .size()); ix++) { if(client[ix] < 0 ) { client[ix] = connfd ; break; } } printf("client[%d] = %d\n" ,ix ,connfd ); if( FD_SETSIZE == ix) { printf("too many client! \n" ); exit(1 ); } if( connfd > maxfd) { maxfd = connfd; } FD_SET(connfd, &allset ); if(ix > maxid ) { maxid = ix; } if(--nready == 0) { continue; } } for(ix = 0 ; ix <= maxid; ix++) //<= { if((sockfd = client [ix ]) < 0) { continue; } if(FD_ISSET (sockfd ,&rset )) { if( 0 == (nrcv = read(sockfd,buf,MAX_LINE_LEN ))) { close(sockfd); client[ix] = -1 ; FD_CLR(sockfd ,&allset ); } else { printf("RECIEVE: %s \n" ,buf ); write(sockfd,buf,nrcv); } } if(--nready == 0) { break; } } } return 0; }

  在使用select 的时候要注意两点:

    第一个参数需要是当前所关心的文件描述符中最大的一个+1

    第二需要注意的是select的中间3个参数采用了“value-result”(UNP1的说法)的方式,设置了关心的文件描述符进行select,select返回之后对应描述的fdset中只有有事件发生的对应fd会被设置,其它关心但是没有事件发生的描述符将会从fdset中清除掉,如果不进行重新赋值,下次select就不会关注这些描述符了,因此上述代码中allset每次对rset进行复制。

   来看看如果只在while(1) 之前设置rset,在while(1) 中不在每次select之前赋值会发生什么,在控制台输入: strace ./simpleEcho,另外打开一个控制台窗口输入:nc localhost 1314,这作为一个连接Echo server 的 client,然后输入你想发往Echo Server内容。关键我们来看一下Echo server的情况:

  可以看到 select 首先关注的文件描述符 fd == 3,该描述符是listenfd,然后有client连过来,select关注了 fd 3 和 4,4是accept函数打开的用于与client通信的描述符,当client向server写数据之后select关注的描述就只剩下 fd 4了,也就是当前处于连接状态的描述符,如果client主动关闭,select返回之后,下次监听就没有关注的描述符了,可见select函数的“value-result” 返回方式是这样工作的:每次只返回监听描述符中处于active的,其它处于监听的但是当前没有事件发生的描述符则会从监听的fdset中清除掉。因此在每次select之前需要给关注的fdset重新赋值。

  注1:在进行系统调用调试的时候 strace 是一个利器,简单使用方式如上面在运行程序之前加上 strace 即可。在调试代码逻辑的时候当然还是使用gdb了。

  注2:Netcat 或者叫 nc 是 Linux 下的一个用于调试和检查网络工具包。可用于创建 TCP/IP 连接,最大的用途就是用来处理 TCP/UDP 套接字。

  

  select 什么时候会处于准备好并返回呢? UNPv1 上进行了详细介绍:

  下面四个条件任何一个满足的时候套件字准备好读:

  1. 套接口接受缓冲区的数据字节数大于等于套接口接受缓冲区的低潮限度当前值。对这样的套接口读操作将不阻塞并返回一个大于0的值(既准备好读入的数据量)。我们可以用套接口选项SO_RCVLOWAT来设置此低潮限度,对于TCP和UDP套接口,其缺省值为1。

  2. 连接的读这一半关闭(也就是接收了FIN的TCP连接)。对这样的套接口读操作将不阻塞并返回0(记文件结束符)。

  3. 套接口是一个监听的套接口且已完成的连接数为非0。正常情况下这样的套接口上的accpet不会被阻塞。

  4. 有一个套接口错误待处理。对这样的套接口操作将不阻塞并返回一个错误-1,errno设置成明确的错误条件。

 

  以下三个条件的任何一个满足时,套接口准备好写操作:

  1. 套接口发送缓冲区中可用空间的字节数大于等于套接口发送缓冲区低潮限度的当前值,且或者(i)套接口已连接,或者(ii)套接口不需要连接(例如UDP套接字)。这意味着,如果我们将这样的套接口设置为非阻塞,写操作将不阻塞且返回一个正值(例如由传输层传入的字节数)。我们可以用套接口选项SO_SNDLOWAT来设置此低潮限度,对于TCP和UDP套接口其缺省值为2048.

  2. 连接的写这一半关闭,对这样的套接口写操作将产生信号SIGPIPE。

  3. 有一个套接口错误待处理。对这样的套接口操作写操作将不阻塞且返回一个错误-1,errno设置成明确的错误条件。这些待处理的错误也可通过指定套接口选项SO_ERROR调用getsockopt来取得并清除。

  

  如果一个套接口存在带外数据或者仍处于带外标记,那他有异常条件待处理。

   poll留到下一篇吧……

  但,I/O multiplexing 就是这样用的吗?

转载于:https://www.cnblogs.com/zhuyp1015/p/3550354.html

相关文章:

  • Android 上传文件
  • [IE9] GPU硬件加速到底是实用创新还是噱头
  • ObjectUtils 类的方法
  • VMware-WorkStation 序列号(转)
  • OGRE On iPhone ----Ogre的iPhone基础模版框架源代码分析
  • 为Web页中的Table对象创建一个映射表
  • 基于V4L2的视频驱动开发(2)
  • IMP-00017: 由于 ORACLE 的 20001 错误
  • java中的内部类
  • [IE9] IE9 Beta崩溃问题解决方案
  • ASP.NET MVC Filters 4种默认过滤器的使用【附示例】
  • 总结程序设计几大原则
  • 使用tcpdump抓取HTTP包
  • 客户端的测试
  • 云存储离我们还有多远?
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • canvas 高仿 Apple Watch 表盘
  • Hibernate最全面试题
  • JAVA_NIO系列——Channel和Buffer详解
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • Java方法详解
  • Java深入 - 深入理解Java集合
  • mockjs让前端开发独立于后端
  • PHP的Ev教程三(Periodic watcher)
  • SpiderData 2019年2月23日 DApp数据排行榜
  • Travix是如何部署应用程序到Kubernetes上的
  • 编写符合Python风格的对象
  • 从零开始在ubuntu上搭建node开发环境
  • 从重复到重用
  • 翻译--Thinking in React
  • 关于Flux,Vuex,Redux的思考
  • 聚类分析——Kmeans
  • 前言-如何学习区块链
  • 使用agvtool更改app version/build
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 一、python与pycharm的安装
  • 中文输入法与React文本输入框的问题与解决方案
  • Java性能优化之JVM GC(垃圾回收机制)
  • ​低代码平台的核心价值与优势
  • $.ajax()
  • (3)llvm ir转换过程
  • (libusb) usb口自动刷新
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (四)库存超卖案例实战——优化redis分布式锁
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • (转载)从 Java 代码到 Java 堆
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .dwp和.webpart的区别
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .net打印*三角形