UNP第十六章 非阻塞式I/O

来源:互联网 发布:沃尔玛数据流程图 编辑:程序博客网 时间:2024/05/17 13:09

概述

套接字的默认状态是阻塞的,可能被阻塞的套接字调用分为以下四类:

  • 输入操作:包括read、readv、recv、recvfrom和recvmsg共5个函数。(1)如果某个进程对一个阻塞的TCP套接字调用这些输入函数之一,而且该套接字的接受缓冲区中没有数据可读,该进程将被投入睡眠,直到有一些数据到达。(2)对于非阻塞的套接字,如果输入操作不能被满足,相应调用将立即返回一个EWOULDBLOCK错误。
  • 输出操作:包括write、writev、send、sendto和sendmsg共5个函数。(1)对于阻塞套接字,如果其发送缓冲区中没有空间,进程将被投入睡眠,直到有空间为止。(2)对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,返回将是内核能够复制到该缓冲区中的字节数。
  • 接受外来连接:例如connfd = Accept();(1)如果迟迟没有客户端请求连入,则服务器进程将投入睡眠。(2)如果对一个非阻塞的套接字调用accept函数,并且尚无新的连接到达,accept调用将立即返回一个EWOULDBLOCK错误。
  • 发起外出连接:即用于TCP的connect函数

非阻塞式I/O与阻塞式I/O的比喻

  • 阻塞式I/O:假设我要到菜鸟驿站(取快递的地方就是内核缓冲区)去快递,但是我不知道快递什么时候过来,这时候我只能死等着(睡眠),直到菜鸟驿站给我发送短信通知,我才被唤醒,然后去取快递。
  • 非阻塞式I/O:假设我要到菜鸟驿站取快递,这次我采取每20分钟去菜鸟驿站看快递到了没:如果快递没到,我就立即回寝室(返回EWOULDBLOCK);如果在每20分钟的轮询中查到快递已到达,那么我就去菜鸟驿站(内核缓冲区)取快递。

非阻塞读和写:str_cli函数

在以往的基础上添加两个缓冲区:to和from。
to缓冲区中包含从标准输入读取的空间、要发往服务器的数据(正在排队)和已发送成功的数据。
这里写图片描述

from缓冲区中包含从TCP套接字读取的空间,要发往标准输出的数据(正在排队)和已发送成功的数据。
这里写图片描述
操作to和from的主要代码如下:

//缓冲区的创建以及初始化char to[MAXLINE], fr[MAXLINE];char *toiptr, *tooptr, *friptr, *froptr;toiptr = tooptr = to;friptr = froptr = fr;  //如果文件未结束且to缓冲区中尚有可从标准输入读入的空闲空间if (stdineof == 0 && toiptr < &to[MAXLINE]) FD_SET(STDIN_FILENO, &rset);//设置Select轮询描述符状态 Select(maxfdp1, &rset, &wset, NULL, NULL);//完成从标准输入读入缓冲区的操作if (FD_ISSET(STDIN_FILENO, &rset)) {//如果标准输入准备好    //从标准输入读入检查    if((n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {        if(errno != EWOULDBLOCK)            err_sys("read error on stdin");    } else if (n == 0) {        fprintf(stderr, "%s:EOF on stdin\n", gf_time());        stdineof = 1; //设置读入结束标志        if (tooptr == toiptr)//要发往服务器数据空间为0,则发送操作完成        Shutdown(sockfd, SHUT_WR); //置TCP为半关闭,传输后续未完成数据      } else {    fprintf(stderr, "%s:read %d bytes from stdin\n", gf_time(), n);    toiptr += n; //读入成功,移动指针,使得要发往服务器数据空间增加:增加一组排队发往服务器的数据    FD_SET(sockfd, &wset);}}

总结:使用非阻塞I/O使程序能够发挥动态性的优势,只要I/O操作有可能发生,就执行合适的读操作或写操作。通过Select函数,我们可以让内核告知我们何时某个I/O操作可以发生。

str_cli的简单版本

利用fork把当前进程划分为一个父进程和一个子进程。子进程把来自服务器的文本行复制到标准输出,父进程把来自标准输入的文本行复制到服务器,如图。
这里写图片描述

代码如下,从中可以看到如何在一个函数中分别操纵父子进程。

#include "unp.h"void str_cli1(FILE *fp, int sockfd){    pid_t pid;    char  sendline[MAXLINE], recvline[MAXLINE];    if( (pid = Fork()) == 0) {//if包裹范围内为子进程        while(Readline(sockfd, recvline, MAXLINE) > 0)            Fputs(recvline, stdout);        kill(getppid(), SIGTERM);    }    while (Fgets(sendline, MAXLINE, stdin) != NULL)        Writen(sockfd, sendline, strlen(sendline));    Shutdown(sockfd, SHUT_WR);    pause();    return;}

非阻塞connect

原创粉丝点击