文档章节

网络编程学习——非阻塞式I/O

thanatos_y
 thanatos_y
发布于 2016/04/20 18:19
字数 2343
阅读 14
收藏 0
点赞 1
评论 0

1 概述

  套接字的默认状态是阻塞的,这意味着当发出一个不能立即完成的套接字调用时,其进程将会投入睡眠,等待相应操作完成,可能阻塞的套接字调用可分为以下四类。

  1. 输入操作,包括read、readv、recv、recvfrom和recvmsg共五个函数。对于非阻塞的套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误。

  2. 输出操作,包括write、writev、send、sendto和sendmsg共5个函数。对于一个TCP套接字,内核将从应用进程的缓冲区到该套接字的发送缓冲区复制数据。对于阻塞的套接字,如果其发送缓冲区没有空间,进程将被投入睡眠,直到有空间为止。

    对于非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即返回一个EWOULDBLOCK错误。如果其发送缓冲区中没有空间,返回值将是内核能够复制到该缓冲区中的字节数。这个字节数也称为不足计数(short count)。

    UDP套接字不存在真正的发送缓冲区,内核只是复制应用进程数据并把它沿协议栈向下传送,渐次冠以UDP首部和IP首部。因此对一个阻塞的UDP套接字(默认设置),输出函数调用将不会因与TCP套接字一样的原因而阻塞,不过可能因其他原因而阻塞。

  3. 接受外来连接,即accept函数。如果对一个阻塞的套接字调用accept函数,并且尚无新的连接到达,调用进程将被投入睡眠。

    如果对一个非阻塞的套接字调用accept函数,并且尚无新的连接到达,accept调用将立即返回一个EWOULDBLOCK错误。

  4. 发起外出连接,即用于TCP的connect函数。(connect函数同样可用于UDP,不过它不能使一个“真正”的连接建立起来,它只是使内核保存对端的IP地址和端口号)。

    对于一个非阻塞的TCP套接字调用connect,并且连接不能立即建立,那么连接的建立能照样发起(譬如送出TCP三路握手的第一个分组),不过会返回一个EINPROGRESS错误。

2 非阻塞读和写:str_cli函数(修订版)

  我们维护着两个缓冲区:to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出来的数据。图1-1展示了to缓冲区的组织和指向该缓冲区中的指针。

图1-1 容纳从标准输入到套接字的数据的缓冲区

  其中toiptr指针指向从标准输入读入的数据可以存放的下一个字节,tooptr指向下一个必须写到套接字的字节。有(toiptr-tooptr)个字节需要写到套接字。可从标准输入读入的字节数是(&to[MAXLINE]-toiptr)。一旦tooptr移动到toiptr,这两个指针就一起恢复到缓冲区开始处。

  图1-2展示了fr缓冲区相应的组织。

图1-2 容纳从套接字到标准输出的数据的缓冲区

  下面给出了本函数的一部分。

void str_cli( FILE *fp, int sockfd )
{
  int maxfdp1, val, stdineof;
  ssize_t n, nwritten;
  fd_set rset, wset;
  char to[ MAX_MESG_SIZE ], fr[ MAX_MESG_SIZE ];
  char *toiptr, *tooptr, *friptr, *froptr;
  
  // 使用fcntl把所有3个描述符都设置为非阻塞,包括连接到服务器的套接字、标准输入和标准输出
  val = fcntl( sockfd, F_GETFL, 0 );
  fcntl( sockfd, F_SETFL, val | O_NONBLOCK );
  
  val = fcntl( STDIN_FILENO, F_GETFL, 0 );
  fcntl( STDIN_FILENO, F_SETFL, val | O_NONBLOCK );
  
  val = fcntl( STDOUT_FILENO, F_GETFL, 0 );
  fcntl( STDIN_FILENO, F_SETFL, val | O_NONBLOCK );
  
  // 初始化指向两个缓冲区的指针,并把最大的描述符号加1,以用做select的第一个参数
  toiptr = tooptr = to; // initialize buffer pointers
  friptr = froptr = fr;
  stdineof = 0;
  
  maxfdp1 = max( max( STDIN_FILENO, STDOUT_FILENO ), sockfd ) + 1;
  
  //这个版本的主循环也是一个select调用后跟对所关注各个条件所进行的单独测试
  for( ; ; )
  {
    // 两个描述符集都先清零再打开最多2位。如果在标准输入上尚未读到EOF,而且在to缓冲区中有至少一个字节
    // 的可用空间,那就打开描述符集中对应标准输入的位。如果在fr缓冲区中至少一个字节的可用空间,那就打
    // 开描述符集中对应套接字的位。最后,如果在fr缓冲区中有要写到标准输出的数据,那就打开写描述符集中
    // 对应标准输出的位
    FD_ZERO( &rset );
    FD_ZERO( &wset );
    if( stdineof == 0 && toiptr < &to[ MAX_MESG_SIZE ] )
      FD_SET( STDIN_FILENO, &rset ); // read from stdin
    if( fripter < &fr[ MAX_MESG_SIZE ] )
      FD_SET( sockfd, &rset ); // read from socket
    if( tooptr != toiptr )
      FD_SET( sockfd, &wset ); // data to write to sockfd
    if( froptr != friptr )
      FD_SET( STDOUT_FILENO, &wset ); // data to write to stdout
      
    //  调用select,等待4个可能条件中任何一个变为真。我们没有为本select设置超时。
    select( maxfdp1, &rset, &wset, NULL, NULL );
    
    // 如果标准输入可读,那就调用read。指定的第三个参数是to缓冲区中的可用空间量
    if( FD_ISSET( STDIN_FILENO, &rset ) )
    {
      if( ( n = read( STDIN_FILENO, toiptr, &to[ MAX_MESG_SIZE ] - toiptr ) ) < 0 )
      {
        // 如果发生一个EWOULDBLOCK错误,我们就忽略它。通常情况下这种条件“不应该发生”,因为这种条件意味着,
        // select告知我们相应描述符可读,然而read该描述符却返回EWOULDBLOCK错误,不过我们无论如何还是处
        // 理这种条件。
        if( errno != EWOULDBLOCK )
        { 
          printf( " read error on stdin\n " );
          exit( 1 );
        }
      }
      // 如果read返回0,那么标准输入处理就此结束,我们还设置stdineof标志。如果在to缓冲区中不再有数据要发送
      // (即tooptr等于toiptr),那就调用shutdown发送FIN到服务器。如果在to缓冲区仍有数据要发送,FIN的发送
      // 就得推迟到缓冲区中数据已写到套接字之后。
      else if( n == 0 )
      {
        fprintf( stderr, " %s: EOF on stdin\n ", gf_time() );
        stdineof = 1; // all done with stdin
        if( tooptr == toiptr )
          shutdown( sockfd, SHUT_WR ); // send FIN
      }
      // 当read返回数据时,我们相应地增加toiptr。我们还打开写描述符集中与套接字对应的位,使得以后在本循环
      // 对应该位的测试为真,从而导致调用write写到套接字。
      else
      {
        fprintf( srderr, " %s: read %d bytes from stdin\n ", gf_time(), n );
        toiptr += n; // just read
        FD_SET( sockfd, &wset ); // try and write to sockfd below
      }
    }
    
    // 这段代码类似刚才讲解的处理标准输入可读条件的if语句。如果read返回EWOULDBLOCK错误,那么不做任何处理。
    // 如果遇到来自服务器的EOF,那么若我们已经在标准输入上遇到EOF则没有问题,否则来自服务器的EOF并非预期。
    // 如果read返回一些数据,我们就相应地增加friptr,并把写描述符集中与标准输出对应的位打开,以尝试在本函
    // 数第三部分中将这些数据写到标准输出
    if( FD_ISSET( sockfd, &rset ) )
    {
      if( ( n = read( sockfd, friptr, &fr[ MAX_MESG_SIZE ] - friptr ) ) < 0 )
      {
        if( errno != EWOULDBLOCK )
        { 
          printf( " read error on socket\n " );
          exit( 1 );
        }
      }
      else if( n == 0 )
      {
        fprintf( stderr, " %s: EOF on stdin\n ", gf_time() );
        if( stdineof )
          return 0; // normal termination
        else
        {
          printf( " str_cli: server terminated prematurely " );
          exit( 1 );
        }
      }
      else
      {
        fprintf( srderr, " %s: read %d bytes from socket\n ", gf_time(), n );
        toiptr += n; // just read
        FD_SET( sockfd, &wset ); // try and write to below
      }
    }
    
    // 如果标准输出可写而且要写的字节数大于0,那就调用write。如果返回EWOULDBLCOK错误。那么不做任何处理。
    // 注意这种条件完全可能发生,因为本函数第二部分末尾的代码在不清楚write是否会成功的前提下就打开了写描述
    // 符集中与标准输出对应的位
    if( FD_ISSET( STDOUT_FILENO, &wset ) && ( ( n = friptr - froptr ) > 0 ) )
    {
      if( ( nwritten = write( STDOUT_FILENO, froptr, n ) ) < 0 )
      {
        if( errno != EWOULDBLOCK )
        {
          printf( " write error to stdout\n " );
          exit( 1 );
        }
      }
      // 如果write成功,froptr就增加写处的字节数。如果输出指针(froptr)追上输入指针(friptr),这两个指针
      // 就同时恢复为指向缓冲区开始
      else
      {
        fprintf( stderr, " %s: wrote %d bytes to stdout\n ", gf_time(), nwritten );
        froptr += nwritten; // just written
        if( froptr == friptr )
          froptr = friptr = fr; // back to beginning of buffer
      }
    }
    
    // 这段代码类似刚才讲解的处理标准输出可写条件的if语句。唯一的差别是当输出指针追上输入指针时,不仅这两
    // 个指针同时恢复到缓冲区开始处,而且如果已经在标准输入上遇到EOF就要发送FIN到服务器
    if( FD_ISSET( sockfd, &wset ) && ( ( n = toiptr - tooptr ) > 0 ) )
    {
      if( ( nwritten = write( sockfd, tooptr, n ) ) < 0 )
      {
        if( errno != EWOULDBLOCK )
        {
          printf( " write error to socket\n " );
          exit( 1 );
        }
      }
      else
      {
        fprintf( stderr, " %s: wrote %d bytes to socket\n ", gf_time(), nwritten );
        tooptr += nwritten; // just written
        if( tooptr == toiptr )
          toiptr = tooptr = to; // back to beginning of buffer
          if( stdineof )
            shutdown( sockfd, SHUT_WR ); // send FIN
      }
    }
  }
  
}

  下面给出本函数调用的gf_time函数。

char * gf_time( void )
{
  struct timeval tv;
  static char str[ 30 ];
  char *ptr;
  
  if( gettimeofday( &tv, NULL ) < 0 )
  {
    printf( " gettimeofday error\n " );
    exit( 1 );
  }  
  
  ptr = ctime( &tv.tv_sec );
  strcpy( str, &ptr[ 11 ] );
  // Fri Sep 13 00:00:00 1986\n\0
  // 0123456789012345678901234 5
  snprintf( str + 8, sizeof( str ) - 8, " .%06ld ", tv.tv_usec );
  
  return( str );
}

  gf_time函数返回一个含有当前时间的字符串,包括微秒,格式如下。

  12:34:56.123456

  这里特意采用与tcpdump的时间戳输出一致的格式。

 

 

 

 

 

 

© 著作权归作者所有

共有 人打赏支持
thanatos_y
粉丝 7
博文 90
码字总数 315059
作品 0
成都
程序员
我的网络开发之旅——socket编程

上一篇文章《TCP/IP协议分析》讲述了自己是如何和网络领域的开发扯上关系的。正如从招聘网站上抽出的几个关键词“TCP/IP, Socket, 多线程”可见,协议分析并不是网络开发的主流,通常我们所说...

yaocoder ⋅ 2014/09/21 ⋅ 0

0-Linux 网络编程学习笔记导航

学习交流群: Linux 学习交流群 610441700 说明:本系列文章并不能取代 《UNP》这本旷世之作,文章中难免有错误与不足之处,希望读者们遇到有疑问的地方可以加群互相交流,共同进步。写这一系...

q1007729991 ⋅ 2017/04/04 ⋅ 0

服务器模型——从单线程阻塞到多线程非阻塞(上)

前言的前言 服务器模型涉及到线程模式和IO模式,搞清楚这些就能针对各种场景有的放矢。该系列分成三部分: 单线程/多线程阻塞I/O模型 单线程非阻塞I/O模型 多线程非阻塞I/O模型,Reactor及其...

⋅ 2017/12/21 ⋅ 0

Java的IO演进

在JDK1.4推出Java NIO之前,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),这种——请求——应答的通信模型简化了上层的应用开发,但是在性能和可靠性方面却存在着巨大的瓶颈。因此...

柳哥 ⋅ 2015/01/18 ⋅ 0

同步异步 ——Nginx与Apache

同步异步,阻塞非阻塞 和nginx的IO模型 同步与异步 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。所谓同步,就是在发出一个调用时,在没有得到...

wujunqi1996 ⋅ 06/02 ⋅ 0

网易游戏入职作业总结(2)I/O复用

参考文献:《Unix网络编程》 一个输入操作通常包含两个阶段: 等待数据准备好。 从内核向进程复制数据。 Unix下有5种可用的I/O模型: 阻塞式I/O; 非阻塞式I/O; I/O复用; 信号驱动式I/O; ...

fatever ⋅ 02/20 ⋅ 0

并发服务器(三):事件驱动

这是并发服务器系列的第三节。第一节 介绍了阻塞式编程,第二节:线程 探讨了多线程,将其作为一种可行的方法来实现服务器并发编程。 另一种常见的实现并发的方法叫做 事件驱动编程,也可以叫...

作者: Eli Bendersky ⋅ 2017/12/08 ⋅ 0

解读I/O多路复用技术

前言 当我们要编写一个echo服务器程序的时候,需要对用户从标准输入键入的交互命令做出响应。在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起网络连接请求,2)用户在...

新栋BOOK ⋅ 2017/11/19 ⋅ 0

(转)Twisted :第一部分:初步认识Twisted

前言: 最近有人在Twisted邮件列表中提出诸如”为任务紧急的人提供一份Twisted介绍”的的需求。值得提前透露的是,这个序列并不会如他们所愿.尤其是介绍Twisted框架和基于Python 的异步编程而...

水果糖 ⋅ 2016/01/27 ⋅ 0

网络-BIO-同步阻塞模型

网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务器监听的地址发起连接请求,通过三...

xyh12344 ⋅ 2015/12/21 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

win10怎么彻底关闭自动更新

win10自带的更新每天都很多,每一次下载都要占用大量网络,而且安装要等得时间也蛮久的。 工具/原料 Win10 方法/步骤 单击左下角开始菜单点击设置图标进入设置界面 在设置窗口中输入“服务”...

阿K1225 ⋅ 今天 ⋅ 0

Elasticsearch 6.3.0 SQL功能使用案例分享

The best elasticsearch highlevel java rest api-----bboss Elasticsearch 6.3.0 官方新推出的SQL检索插件非常不错,本文一个实际案例来介绍其使用方法。 1.代码中的sql检索 @Testpu...

bboss ⋅ 今天 ⋅ 0

informix数据库在linux中的安装以及用java/c/c++访问

一、安装前准备 安装JDK(略) 到IBM官网上下载informix软件:iif.12.10.FC9DE.linux-x86_64.tar放在某个大家都可以访问的目录比如:/mypkg,并解压到该目录下。 我也放到了百度云和天翼云上...

wangxuwei ⋅ 今天 ⋅ 0

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 今天 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 今天 ⋅ 0

浅谈设计模式之工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻...

佛系程序猿灬 ⋅ 今天 ⋅ 0

Dockerfile基础命令总结

FROM 指定使用的基础base image FROM scratch # 制作base image ,不使用任何基础imageFROM centos # 使用base imageFROM ubuntu:14.04 尽量使用官方的base image,为了安全 LABEL 描述作...

ExtreU ⋅ 昨天 ⋅ 0

存储,对比私有云和公有云的不同

导读 说起公共存储,很难不与后网络公司时代的选择性外包联系起来,但尽管如此,它还是具备着简单和固有的可用性。公共存储的名字听起来也缺乏专有性,很像是把东西直接堆放在那里而不会得到...

问题终结者 ⋅ 昨天 ⋅ 0

C++难点解析之const修饰符

C++难点解析之const修饰符 c++ 相比于其他编程语言,可能是最为难掌握,概念最为复杂的。结合自己平时的C++使用经验,这里将会列举出一些常见的难点并给出相应的解释。 const修饰符 const在c...

jackie8tao ⋅ 昨天 ⋅ 0

聊聊spring cloud netflix的HystrixCommands

序 本文主要研究一下spring cloud netflix的HystrixCommands。 maven <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-clo......

go4it ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部