Linux 系统应用编程——网络编程(I/O模型)

Unix下可用的5种I/O模型:
阻塞I/O
非阻塞I/O
I/O复用(select和poll)
信号驱动I/O(SIGIO)
异步I/O(POSIX的aio_系列函数)
一个输入操作通常包括两个不同的阶段:
1)等待数据准备好;
2)从内核向进程复制数据;
对于一个套接字的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核中某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
阻塞I/O
        最流行的I/O模型是阻塞式I/O(blocking I/O) 模型,默认情况下,所有的套接字都是阻塞的。阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回
以数据包套接字为例,如图
进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区或者发生错误才返回。最常见的错误是系统调用被信号中断。我们说进程从调用recvfrom开始到它返回的整段时间内是被阻塞的,recvfrom成功返回后,进程开始处理数据报。
非阻塞I/O
 非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
进程把一个套接口设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。
前三次调用recvfrom 时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK 错误。第四次调用 recvfrom 时已有一个数据报准备好,它被复制到应用程序缓冲区,于是recvfrom 成功返回。我们接着处理数据。
当一个应用进程像这样对一个非阻塞描述符循环调用 recvfrom 时,我们称之为轮询(polling)。应用程序持续轮询内核,以查看某个操作是否就绪。这样做往往耗费大量CPU 时间。
I/O复用
主要可以调用select和epoll;对一个IO端口,两次调用,两次返回,比阻塞IO并没有什么优越性;关键是能实现同时对多个IO端口进行监听,可以等待多个描述符就绪;
I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数

信号驱动I/O模型
       我们也可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们。我们称这种模型为信号驱动I/O(signal-driven I/O)。
       我们首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用立即发回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。
       无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间,进程不被阻塞。主循环可以继续执行,只要不时等待来自信号处理函数的通知:既可以是数据已经准备好被处理,也可以是数据报已准备好被读取。

异步I/O模型
      异步I/O(asynchronous I/O)
有POSIX规范定义。后来演变成当前POSIX规范的各种早期标准定义的实时函数中存在的差异已经取得一致。一般地说,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到我们自己的缓冲区)完成后通知我们。这种模型与前与前面介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
 


各种模型的比较 

可以看出,前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区起见,进程阻塞与recvfrom 调用,相反。异步I/O模型在这两个阶段都需要处理,从而不同于其他四种模型。

同步I/O与异步I/O对比
POSIX把这两个术语定义如下:
·同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成。
·异步I/O(asynchronous I/O operation)不导致请求进程阻塞。
       根据上述定义,我们前4种模型----阻塞I/O模型、非阻塞I/O模型、I/O复用模型和信号去驱动I/O模型都是同步I/O模型因为其中真正的I/O操作(recvfrom)将阻塞进程。只有异步I/O模型与POSIX定义的异步I/O相匹配。


select 函数
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。
作为一个例子,我们可以调用select,告知内核仅在下列情况发生时才返回:
1)集合{ 1, 4, 5 } 中任何描述符准备好读;
2)集合{ 2, 7 } 中任何描述符准备好写;
3)集合{ 1, 4 } 中任何描述符有异常条件待处理;
也就是说,我们调用 select 告知内核对哪些描述符(就读、写或异常条件)感兴趣以及等待多长时间。我们感兴趣的描述符不局限于套接字,任何描述符都可以用select 来测试。函数描述如下:
[cpp] view plaincopy
  1. #include <sys/select.h>  
  2. #include <sys/time.h>  
  3.   
  4. int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,   
  5.              const struct timeval *timeout);  

从最后一个参数timeout 开始介绍,它告知内核等待所指定描述符中任何一个就绪可花多长时间。其timeval结构用于指定这段时间的秒数和微妙数。
[cpp] view plaincopy
  1. struct timeval  
  2. {  
  3.     long tv_sec; //seconds  
  4.     long tv_usec; //mircoseconds  
  5. }  
这个参数有以下三种可能:
1)永远的等待下去:仅在有一个描述符准备好I/O时才返回。为此,我们把这个参数设置为空指针;
2)等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval 结构中指定的秒数和微秒数;
3)根本不等待:检查描述符后立即反悔,这称为轮询(polling)。为此,该参数必须指向一个timeval结构,而且其中的定时器值(由该结构指定的秒数和微秒数)必须为0;
中间的三个参数 readset 、writeset 和 exceptset 指定我们要让内核测试读、写和异常条件的描述符。
select 使用描述符集,通常是同一个整数数组,其中每个整数中的每一位对于一个描述符。举例来说,假设使用32位整数,那么该数组的每一个元素对应于描述符0~31,第二位元素对应于描述符32~63,依次类推, 它们隐藏 为 fd_set 的数据类型和以下四个宏中:
[cpp] view plaincopy
  1. void FD_ZERO(fd_set *fdset); //从fdset中清除所有的文件描述符  
  2. void FD_SET(int fd, fd_set *fdset); //将fd加入到fdset  
  3. void FD_CLR(int fd, fd_set *fdset); //将fd从fdset里面清除  
  4. int FD_ISSET(int fd, fd_set *fdset); //判断fd是否在fdset集合中  
举个例子,以下代码用于定义一个fd_set 类型的变量,然后打开描述符 1、4 和 5 的对应位;
[cpp] view plaincopy
  1. fd_set rset;  
  2.   
  3. FD_ZERO(&rset);  
  4. FD_SET(1, &rset);  
  5. FD_SET(4 &rset);  
  6. FD_SET(5, &rset);  
描述符集的初始化非常重要,因为作为自动变量分配的一个描述符集如果没有初始化,那么可能发生不可预期的后果。
select 函数修改由指针 readset 、writeset 和 exceptset 所指向的描述符集,因而这三个参数都是值-结果参数。调用该函数时,我们指定所关心的描述符的值,该函数返回时,结果将指示哪些描述符就绪。该函数返回后,我们使用FD_ISSET宏测试 fd_set 数据类型中的描述符。描述符集内任何与未就绪描述符对应的位返回时均清0。为此,每次重新调用select函数时,我们都得再次把所以描述符集内所关心的为均置一。
      数的返回值表示跨所有描述符集的已就绪的总位数。如果任何描述符就绪之前定时器到时,那么返回0.返回-1表示出错。
描述符就绪条件:

对于可读文件描述符集以下四种情况会导致置位:
1、socket接收缓冲区中的数据量大于或等于当前缓冲区的低水位线.此时对于read操作不会被阻塞并且返回一个正值(读取的字节数).低水位线可以通过SO_RCVLOWAT选项设定,对于Tcp和Udp来说其默认值为1.
2、socket连接的读端被关闭,如shutdown(socket, SHUT_RD)或者close(socket).对应底层此时会接到一个FIN包,read不会被阻塞但会返回0.代表读到socket末端.
3、socket是一个监听socket并且有新连接等待.此时accept操作不会被阻塞.
4、发生socket错误.此时read操作会返回SOCKET_ERROR(-1).可以通过errno来获取具体错误信息.

对于可写文件描述符集以下四种情况会导致置位:
1、socket发送缓冲区中的可用缓冲大小大于或等于发送缓冲区中的低水位线并且满足以下条件之一
    (1)、socket已连接
    (2)、socket本身不要求连接,典型如Udp
    
    低水位线可以通过SO_SNDLOWAT选项设置.对于Tcp和Udp来说一般为2048.
2、socket连接的写端被关闭,如shutdown(socket, SHUT_WR)或者close(socket).在一个已经被关闭写端的句柄上写数据会得到SIGPIPE的信号(errno).
3、一个非阻塞的connect操作连接成功 或者 connect操作失败.
4、发生socket错误.此时write操作会返回SOCKET_ERROR(-1).可以通过errno来获取具体错误信息.

对于异常文件描述符集只有一种情况(针对带外数据):
当收到带外数据(out-of-band)时或者socket的带外数据标志未被清除.

 

下面看个具体例子:

server

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <stdlib.h>  
  4. #include <unistd.h>  
  5. #include <sys/types.h>  
  6. #include <sys/socket.h>  
  7. #include <sys/select.h>  
  8. #include <netinet/in.h>  
  9. #include <arpa/inet.h>  
  10. #define PORT 8888  
  11. #define MAXSIZE 128  
  12.   
  13. int main()  
  14. {  
  15.     int i,nbyte;  
  16.     int listenfd, confd, maxfd;  
  17.     char buffer[MAXSIZE];  
  18.     fd_set global_rdfs, current_rdfs;  
  19.     struct sockaddr_in addr,clientaddr;  
  20.     int addrlen = sizeof(struct sockaddr_in);  
  21.     int caddrlen = sizeof(struct sockaddr_in);  
  22.   
  23.     if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)  
  24.     {  
  25.         perror("socket error");  
  26.         exit(-1);  
  27.     }  
  28.     else  
  29.     {  
  30.         printf("socket successfully!\n");  
  31.         printf("listenfd : %d\n",listenfd);  
  32.     }  
  33.   
  34.     memset(&addr, 0 ,addrlen);  
  35.     addr.sin_family = AF_INET;  
  36.     addr.sin_port = htons(PORT);  
  37.     addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  38.     if(bind(listenfd,(struct sockaddr *)&addr,addrlen) == -1)  
  39.     {  
  40.         perror("bind error");  
  41.         exit(-1);  
  42.     }  
  43.     else  
  44.     {  
  45.         printf("bind successfully!\n");  
  46.         printf("listen port:%d\n",PORT);  
  47.     }  
  48.   
  49.     if(listen(listenfd,5) == -1)  
  50.     {  
  51.         perror("listen error");  
  52.         exit(-1);  
  53.     }  
  54.     else  
  55.     {  
  56.         printf("listening...\n");  
  57.     }  
  58.   
  59.     maxfd = listenfd;  
  60.     FD_ZERO(&global_rdfs);  
  61.     FD_SET(listenfd,&global_rdfs);  
  62.   
  63.     while(1)  
  64.     {  
  65.         current_rdfs = global_rdfs;  
  66.         if(select(maxfd + 1,¤t_rdfs, NULL, NULL,0) < 0)  
  67.         {  
  68.             perror("select error");  
  69.             exit(-1);  
  70.         }  
  71.           
  72.         for(i = 0; i <= listenfd + 1; i++)  
  73.         {  
  74.             if(FD_ISSET(i, ¤t_rdfs))  
  75.             {  
  76.                 if(i == listenfd)  
  77.                 {  
  78.                     if((confd = accept(listenfd,(struct sockaddr *)&clientaddr,&caddrlen)) == -1)  
  79.                     {  
  80.                         perror("accept error");  
  81.                         exit(-1);  
  82.                     }  
  83.                     else  
  84.                     {  
  85.                         printf("Connect from [IP:%s PORT:%d]\n",  
  86.                                 inet_ntoa(clientaddr.sin_addr),clientaddr.sin_port);  
  87.                         FD_SET(confd,&global_rdfs);  
  88.                         maxfd = (maxfd > confd ? maxfd : confd);  
  89.                     }  
  90.                 }  
  91.                 else  
  92.                 {  
  93.                     if((nbyte = recv(i, buffer, sizeof(buffer),0)) < 0)  
  94.                     {  
  95.                         perror("recv error");  
  96.                         exit(-1);  
  97.                     }  
  98.                     else if(nbyte == 0)  
  99.                     {  
  100.                         close(i);  
  101.                         FD_CLR(i,&global_rdfs);  
  102.                     }  
  103.                     else  
  104.                     {  
  105.                         printf("recv:%s\n",buffer);  
  106.                         send(i, buffer, sizeof(buffer),0);  
  107.                     }  
  108.                 }  
  109.             }  
  110.         }  
  111.     }  
  112.   
  113.     return 0;  
  114. }  

执行结果如下:

[cpp] view plaincopy
  1. fs@ubuntu:~$ cd qiang/select/  
  2. fs@ubuntu:~/qiang/select$ ./select2  
  3. socket successfully!  
  4. listenfd : 3  
  5. bind successfully!  
  6. listen port:8888  
  7. listening...  
  8. Connect from [IP:192.168.3.51 PORT:1992]  
  9. recv:hello  
  10.   
  11. Connect from [IP:192.168.3.53 PORT:2248]  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 系统应用编程——网络编程(TCP/IP 数据包格式解析)

图中括号中的数字代表的是当前域所占的空间大小&#xff0c;单位是bit位。 黄色的是数据链路层的头部&#xff0c;一共14字节 绿色的部分是IP头部&#xff0c;一般是20字节 紫色部分是TCP头部&#xff0c;一般是20字节 最内部的是数据包内容 黄色部分&#xff1a;链路层 目的MA…

Linux 系统应用编程——网络编程(TCP 协议三次握手过程)

TCP(Transmission Control Protocol) 传输控制协议 TCP是主机对主机层的传输控制协议&#xff0c;提供可靠的连接服务&#xff0c;采用三次握手确认建立一个连接: 位码即tcp标志位,有6种标示: SYN ( synchronous 建立联机 ) ACK ( acknowledgement 确认 ) PSH ( push 传送…

基于ELK的简单数据分析

原文链接&#xff1a; http://www.open-open.com/lib/view/open1455673846058.html 环境 CentOS 6.5 64位JDK 1.8.0_20Elasticsearch 1.7.3LogStash 1.5.6Kibana 4.1.4介绍 ElasticSearch是有名的开源搜索引擎&#xff0c;现在很多公司使用ELK技术栈做日志分析&#xff0c;比如…

[win10] 在桌面上显示计算机、控制面板、网络

1. 右击桌面&#xff0c;选择个性化 2. 选择”主题", 点击“桌面图标设置” 3. 把想要放桌面的图标给钩上

linux下共享文件夹(windows可访问,linux也可访问)

2019独角兽企业重金招聘Python工程师标准>>> 本文是转字网上的两段&#xff0c;如果是菜鸟&#xff0c;想懂有点难度&#xff0c;我这里给点注释 在linux上共享文件夹windows下看 ******************************************* 首先给linux设一个ip&#xff0c;要和…

Linux---进程调度相关命令解析

进程相关命令 1、ps 查看系统中的进程 使用方式&#xff1a;ps [options] [--help] 说明&#xff1a;显示瞬间进程 (process) 的动态 参数&#xff1a;ps的参数非常多, 在此仅列出几个常用的参数并大略介绍含义 ps命令常用用法&#xff08;方便查看系统进程&#xff09; 1&a…

Linux 系统应用编程——多线程经典问题(生产者-消费者)

“生产者——消费者”问题是Linux多线程编程中的经典问题&#xff0c;主要是利用信号量处理线程间的同步和互斥问题。 “生产者——消费者”问题描述如下&#xff1a; 有一个有限缓冲区&#xff08;这里用有名管道实现 FIFO 式缓冲区&#xff09;和两个线程&#xff1a;生产者和…

Linux 系统应用编程——进程间通信(下)

在前面&#xff0c;我们学习了传统的进程间通信方式——无名管道&#xff08;pipe&#xff09;、有名管道&#xff08;fifo&#xff09;和信号&#xff08;signal&#xff09;。 下面我们来学习 System V IPC 对象&#xff1a; 1、共享内存&#xff08;share memory&#xff0…

.balignl 16,0xdeadbeef浅析

http://zqwt.012.blog.163.com/blog/static/12044684201031102956976/ 最近在分析u-boot的源代码&#xff0c;看到这一行&#xff1a; .balignl 16, 0xdeadbeef不知道为什么要这样写&#xff0c;0xdeadbeef&#xff0c;明显是个单词组&#xff0c;写在这里有何意义呢&am…

使用maven导入任意jar包

http://mvnrepository.com/ 我这里&#xff0c;因为是spark1.5.2版本。 保存&#xff0c;maven会自动下载jar包到本地仓库。 转载于:https://www.cnblogs.com/lchzls/p/6281764.html

Linux下静态IP地址的设置及TFTP服务的搭建

TFTP&#xff08;Trivial File Transfer Protocol,简单文件传输协议&#xff09;是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。TFTP承载在UDP上&#xff0c;提供不可靠的数据流传输服务&#xff0c;…

bzoj 3924 幻想乡战略游戏

题目大意&#xff1a; 有边权点权的树&#xff0c;动态修改点权 每次修改后求带权重心x (\(minimize\) \(S\sum_i val[i]*dist[x][i]\)) 分析&#xff1a; 从暴力找突破口&#xff1a; 对于边x,y&#xff0c;设长度为len&#xff0c;切断后x半边树权值和为\(w_1\)&#xff0c;y…

Linux 系统应用编程——网络编程(基础篇)

一、网络体系结构 1、OSI模型和TCP/IP 模型 网络体系结构指的是网络的分层结构以及每层使用的协议的集合。其中最著名的就是OSI协议参考模型&#xff0c;他是基于国际标准化组织&#xff08;OSI&#xff09;的建议发展起来的。它分为7个层次&#xff1a;应用层、表示层、会话层…

C++中函数的默认参数

使用方法&#xff1a; &#xff08;1&#xff09;在函数声明或定义时&#xff0c;直接对参数赋值&#xff0c;该参数就是默认参数。&#xff08;2&#xff09;在函数调用时&#xff0c;省略部分或全部参数&#xff0c;这时就会使用默认参数进行代替。注意事项&#xff1a; &…

Linux 系统应用编程——网络编程(socket编程)

二、网络编程基础 1、套接字概述 套接字就是网络编程的ID。网络通信&#xff0c;归根到底还是进程间的通信&#xff08;不同计算机上的进程间的通信&#xff09;。在网络中&#xff0c;每一个节点&#xff08;计算机或路由器&#xff09;都有一个网络地址&#xff0c;也就是IP地…

php curl拉取远程图片

<?php $url "图片绝对地址/thumbnail.jpg"; $filename curl.jpg; getImg($url, $filename); /**通过curl方式获取制定的图片到本地* 完整的图片地址* 要存储的文件名*/ function getImg($url "", $filename "") {if(is_dir(basename($fi…

利用indent格式化源文件的脚本

脚本一&#xff1a;格式化指定目录下的源文件(*.h, *.cpp...) #!/bin/sh# 格式化某目录下所有*.h, *.c, *.cpp, *.hh文件, 并将文件换行符转换成Linux下的格式if [ $# -lt 1 ]; thenecho "Usage: $0 <dir>"exit 1elsedir$1fi# format a source file(*.c, *.h,…

Struts入门(三)深入Struts用法讲解

访问Servlet APIAction搜索顺序动态方法调用指定多个配置文件默认ActionStruts 后缀接收参数处理结果类型1.访问Servlet API 首先我们了解什么是Servlet API httpRequest、httpResponse、servletContext  3个api对应jsp面向对象&#xff1a;request、response、application …

Linux ALSA声卡驱动之四:Control设备的创建

声明&#xff1a;本博内容均由http://blog.csdn.net/droidphone原创&#xff0c;转载请注明出处&#xff0c;谢谢&#xff01; Control接口 Control接口主要让用户空间的应用程序&#xff08;alsa-lib&#xff09;可以访问和控制音频codec芯片中的多路开关&#xff0c;滑动控件…

jQuery 入门教程(5): 显示/隐藏内容

2019独角兽企业重金招聘Python工程师标准>>> jQuery的hide()和show()可以用来显示和隐藏内容。比如下面的例子&#xff1a;jQuery的hide()和show() 可以用来显示和隐藏内容。比如下面的例子&#xff1a; [html] view plain copy print ? <!doctype html> …