UNIX(网络编程-IO操作):14---阻塞IO与非阻塞IO概念、非阻塞读写案例

一、阻塞IO分类

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

可能阻塞的套接字调用可以分为以下4类型

  • (1) 输入操作,包括read、readv、recv、recvfrom和recvmsg共5个函数。如果某个进程 对一个阻塞的TCP套接字(默认设置)调用这些输入函数之一,而且该套接字的接收缓冲区中 没有数据可读,该进程将被投入睡眠,直到有一些数据到达。既然TCP是字节流协议,该进程 的唤醒就是只要有一些数据到达,这些数据既可能是单个字节,也可以是一个完整的TCP分节 中的数据。如果想等到某个固定数目的数据可读为止,那么可以调用我们的readn函数(图3-15), 或者指定MSG_WAITALL标志(图14-6)
    • 既然UDP是数据报协议,如果一个阻塞的UDP套接字的接收缓冲区为空,对它调用输入函 数的进程将被投入睡眠,直到有UDP数据报到达
    • 对于非阻塞的套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数 据可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK 错误
  • (2) 输出操作,包括write、writev、send、sendto和sendmsg共5个函数。对于一个TCP 套接字我们已在2.11节说过,内核将从应用进程的缓冲区到该套接字的发送缓冲区复制数据。 对于阻塞的套接字,如果其发送缓冲区中没有空间,进程将被投入睡眠,直到有空间为止。
    • 对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即 返回一个EWOULDBLOCK错误。如果其发送缓冲区中有一些空间,返回值将是内核能够复制到该 缓冲区中的字节数。这个字节数也称为不足计数(short count)。
    • 我们还在2.11节说过,UDP套接字不存在真正的发送缓冲区。内核只是复制应用进程数据 并把它沿协议栈向下传送,渐次冠以UDP首部和IP首部。因此对一个阻塞的UDP套接字(默认 设置),输出函数调用将不会因与TCP套接字一样的原因而阻塞,不过有可能会因其他的原因而 阻塞
  • (3) 接受外来连接,即accept函数。如果对一个阻塞的套接字调用accept函数,并且尚无 新的连接到达,调用进程将被投入睡眠。
    • 如果对一个非阻塞的套接字调用accept函数,并且尚无新的连接到达,accept调用将立即 返回一个EWOULDBLOCK错误。
  • (4) 发起外出连接,即用于TCP的connect函数。(回顾一下,我们知道connect同样可用于 UDP,不过它不能使一个“真正”的连接建立起来,它只是使内核保存对端的IP地址和端口号。) 我们已在2.6节展示过,TCP连接的建立涉及一个三路握手过程,而且connect函数一直要等到 客户收到对于自己的SYN的ACK为止才返回。这意味着TCP的每个connect总会阻塞其调用进程至少一个到服务器的RTT时间
    • 如果对一个非阻塞的TCP套接字调用connect,并且连接不能立即建立,那么连接的建立 能照样发起(譬如送出TCP三路握手的第一个分组),不过会返回一个EINPROGRESS错误。注意 这个错误不同于上述三个情形中返回的错误。另请注意有些连接可以立即建立,通常发生在服 务器和客户处于同一个主机的情况下。因此即使对于一个非阻塞的connect,我们也得预备 connect成功返回的情况发生。我们将在16.3节展示一个非阻塞connect的例子
  • 注意事项:按照传统,对于不能被满足的非阻塞式I/O操作,System V会返回EAGAIN错误,而源自 Berkeley的实现则返回EWOULDBLOCK错误。顾及历史原因,POSIX规范声称这种情况下这两 个错误码都可以返回。幸运的是,大多数当前的系统把这两个错误码定义成相同的值(检查 一下你自己的系统中的头文件),因此具体使用哪一个并无多大关系。我们 在本书中使用EWOULDBLOCK

二、非阻塞读写案例

案例简介

  • 我们再次回到前面讨论过的str_cli函数。前面使用的select的版本仍使用阻塞式I/O。举例来说,如果在标准输入有一行文本可读,我们就调用read读入它,再调用 writen把它发送给服务器。然而如果套接字发送缓冲区已满,writen调用将会阻塞。在进程阻 塞于writen调用期间,可能有来自套接字接收缓冲区的数据可供读取。类似地,如果从套接字 中有一行输入文本可读,那么一旦标准输出比网络还要慢,进程照样可能阻塞于后续的write 调用。本节的目标是开发这个函数的一个使用非阻塞式I/O的版本。这样可以防止进程在可做任 何有效工作期间发生阻塞。
  • 不幸的是,非阻塞式I/O的加入让本函数的缓冲区管理显著地复杂化了,因此我们将分片介 绍这个函数。我们已在第6章和第14章中讨论过在套接字上使用标准I/O的潜在问题和困难,它 们在非阻塞式I/O操作中显得尤为突出。本例子中继续避免使用标准I/O
  • 我们维护着两个缓冲区:to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输 出来的数据。下图展示了to缓冲区的组织和指向该缓冲区中的指针

UNIX(网络编程-IO操作):14---阻塞IO与非阻塞IO概念、非阻塞读写案例_第1张图片

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

UNIX(网络编程-IO操作):14---阻塞IO与非阻塞IO概念、非阻塞读写案例_第2张图片

源码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define MAXLINE 1024
int max(int f1,int f2);
void str_cli(FILE *fp,int sockfd);
char *gf_time(void);

int main(int argc,char *argv[])
{
    if(argc!=3){
        perror("please enter [IP] [PORT]");
        exit(EXIT_FAILURE);
    }

    int sockFd=socket(AF_INET,SOCK_STREAM,0);
    if(sockFd<0){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons(atoi(argv[2]));
    if((inet_aton(argv[1],(struct in_addr*)&serverAddr.sin_addr))==0){
        perror("inet_aton");
        exit(EXIT_FAILURE);
    }

    if(connect(sockFd,(struct sockaddr*)&serverAddr,sizeof(serverAddr))<0){
        printf("Connect failed,reasons:%s\n",strerror(errno));
        close(sockFd);
        exit(EXIT_FAILURE);
    }
 
    printf("Connect success\n");

    str_cli(stdin,sockFd);

    exit(0);
}

void str_cli(FILE *fp,int sockfd)
{
    int maxfdp1,val,stdineof,retval;
    ssize_t n,nwritten;
    fd_set rset,wset;
    char to[MAXLINE],fr[MAXLINE]; //to存放标准输入,fr存放标准输出
    char *toiptr,*tooptr,*friptr,*froptr;

    val=fcntl(sockfd,F_GETFL,0);
    fcntl(sockfd,F_SETFL,val | O_NONBLOCK);//把sockfd设为非阻塞

    val=fcntl(STDIN_FILENO,F_GETFL,0);
    fcntl(STDIN_FILENO,F_SETFL,val | O_NONBLOCK);//把标准输入设为非阻塞

    val=fcntl(STDOUT_FILENO,F_GETFL,0);
    fcntl(STDOUT_FILENO,F_SETFL,val | O_NONBLOCK);//把标准输出设为非阻塞

    toiptr=tooptr=to;
    friptr=froptr=fr;
    stdineof=0;

    maxfdp1=max(max(STDIN_FILENO,STDOUT_FILENO),sockfd)+1;
    for(;;){
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        if(stdineof==0 && toiptr<&to[MAXLINE])//如果标准输入还可以存放数据
            FD_SET(STDIN_FILENO,&rset);
        if(friptr<&fr[MAXLINE])
            FD_SET(sockfd,&rset);
        if(tooptr!=toiptr)
            FD_SET(sockfd,&wset);
        if(froptr!=friptr)
            FD_SET(STDOUT_FILENO,&wset);
        switch(retval=select(maxfdp1,&rset,&wset,NULL,NULL))
        {
            case -1:
                perror("select");
                continue;
            case 0:
                printf("timeout\n");
                continue;
            default:
                if(FD_ISSET(STDIN_FILENO,&rset))//从标准输入读取数据
                {
                    if((n=read(STDIN_FILENO,toiptr,&to[MAXLINE]-toiptr))<0){
                        if(errno!=EWOULDBLOCK)
                            perror("read error on stdin");
                    }else if(n==0){
                        fprintf(stderr,"%s:EOF on stdin\n",gf_time());
                        stdineof=-1;
                        if(tooptr==toiptr)//关闭写端
                            shutdown(sockfd,SHUT_WR);
                    }else{
                        fprintf(stderr,"%s:read %d bytes from stdin\n",gf_time(),n);
                        toiptr+=n;
                        FD_SET(sockfd,&wset);
                    }
                }

                if(FD_ISSET(sockfd,&rset))
                {
                    if((n=read(sockfd,friptr,&fr[MAXLINE]-friptr))<0){
                        if(errno!=EWOULDBLOCK)
                            perror("read error on socket");
                    }else if(n==0){
                        fprintf(stderr,"%s:EOF on socket\n",gf_time());
                        if(stdineof)
                            return;
                        else
                            perror("str_cli:server terinated prematurely");
                    }else{
                        fprintf(stderr,"%s:read %d bytes from socket\n",gf_time(),n);
                        friptr+=n;
                        FD_SET(STDOUT_FILENO,&wset);
                    }
                }

                if(FD_ISSET(STDOUT_FILENO,&wset)&&((n=friptr-froptr)>0))
                {
                    if((nwritten=write(STDOUT_FILENO,froptr,n))<0){
                        if(errno!=EWOULDBLOCK)
                            perror("write error to stdout");
                    }else{
                        fprintf(stderr,"%s:wrote %d bytes to stdout\n",gf_time(),nwritten);
                        froptr+=nwritten;
                        if(froptr==friptr)
                            froptr=friptr=fr;
                    }
                }

                if(FD_ISSET(sockfd,&wset)&&((n=toiptr-tooptr)>0))
                {
                    if((nwritten=write(sockfd,tooptr,n))<0){
                        if(errno!=EWOULDBLOCK)
                            perror("write error to socket");
                    }else{
                        fprintf(stderr,"%s:wrote %d bytes to socket\n",gf_time(),nwritten);
                        froptr+=nwritten;
                        if(tooptr==toiptr)
                            toiptr=tooptr=to;
                        if(stdineof)
                            shutdozwn(sockfd,SHUT_WR);
                    }
                }
                break;
        }
        
    }
}

int max(int f1,int f2)
{
    if(f1>f2)
        return f1;
    else if(f1

你可能感兴趣的:(UNIX(网络编程-IO操作))