非堵塞IO的读与写的回射客户端对比分析

来源:互联网 发布:夏老师c语言视频教学 编辑:程序博客网 时间:2024/04/29 20:34

一、非堵塞读与写分析

1、老版本1分析--- 基于tcp的堵塞io模型

是read和write,结合起来处理的io模型,因为如果服务器没有数据过来,那么read将会一直堵塞下去。因为堵塞将会导致程序的效率不够高效。

伪代码如下如实:

while(fgets(buf, maxline,stdin) != NULL){write(sockfd, buf, strlen(buf));read(sockfd, buf, maxline);}

分析堵塞条件:

1).write当中如果套接字发送缓冲区已满,write调用将会堵塞

2).read当中如果套接字接受缓冲区没有数据, 那么read将会一直堵塞下去,直到收到了来之服务器的数据。


2、老版本2分析---基于tcp的select中堵塞的io复用模型

在select来判断, 是否能读取数据, 可以排除老版本一当中的读取数据时候而造成的程序堵塞情况, 很大程度的提高了程序的执行效率。

伪代码如下所示:

for(;;){FD_SET(fileno(stdin), &rset);FD_SET(sockfd, &rset);select(maxfd + 1, &rset, NULL, NULL, NULL);if(FD_ISSET(fileno(stdin), &rset)){相关处理过程}if(FD_ISSET(sockfd, &rset)){相关处理过程 }}
分析堵塞条件:
1).然而当调用write函数的时候,也会应为套接字发送缓冲区已满,而导致堵塞。

2).如果标准输出比网络慢, 那么进程也有可能堵塞与后续的write调用。


3、非堵塞IO的读与写在select下的复用模型

我们需要在应用层维护两个缓冲区:to缓冲区是标准输入到服务器去的数据, fr缓冲区是容纳来自服务器到标准输出的数据

to缓冲区:tooptr指向缓冲区未发送到服务器的数据的头子节。toiptr指向标准输入读入的数据可以存放的下一个字节。有(toiptr - tooptr)个数据可以被发送到套接字。

可以从标准输入读入的字节数&to[MAXLINE] - toiptr。当tooptr = toiptr的时候, 这两个指针就恢复到缓冲区开始的位置。

fr缓冲区与to缓冲区有相同的道理。


模型图如下所示:


详细实现代码如下所示:

dg_cli_nonblockio.c

#include "unp.h"void str_cli(FILE *fp, int sockfd){    int maxfdp1, flag, stdineof;    ssize_t n, nwritten;    fd_set wset, rset;    char to[MAXLINE], fr[MAXLINE];    char *toiptr, *tooptr, *friptr, *froptr;    //重点一:把标准输入、标准输出、连接服务器的套接字都设置成非堵塞条件     flag = Fcntl(sockfd, F_GETFL, 0);    Fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);    flag = Fcntl(STDIN_FILENO, F_GETFL, 0);    Fcntl(STDIN_FILENO, F_GETFL, flag | O_NONBLOCK);    flag = Fcntl(STDOUT_FILENO, F_GETFL, 0);    Fcntl(STDOUT_FILENO, F_SETFL, flag | O_NONBLOCK);        toiptr = tooptr = to;    friptr = froptr = fr;    stdineof = 0;    maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd);    for(;;){        FD_ZERO(&rset);        FD_ZERO(&wset);        //标准输出未读到eof的时候, 并且to的空闲缓冲区至少在一个字节字节以上, 打开读描述符集中的标准输入位         if(stdineof == 0 && toiptr < &to[MAXLINE]){             FD_SET(STDIN_FILENO, &rset);    /* read from stdin*/        }        //当fr缓冲区有空闲时候, 则设置描述符集中的标准输出位         if(friptr < &fr[MAXLINE]){            FD_SET(sockfd, &rset);  /*read from socket*/        }        //tooptr一直小于等于toiptr的值,当不等于的时候,则表示to缓冲区有数据可以输出到socket         if(tooptr != toiptr){            FD_SET(sockfd, &wset);  /*write to socket*/        }// froptr一直小于等于friptr的值,当不等于的时候,则表示fr缓冲区有数据可以标准输出         if(froptr != friptr){           FD_SET(STDOUT_FILENO, &wset);   /* write to stdout*/        }        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){  //当读取失败的时候有情况,第一非堵塞条件下没有成功读取数据,                                            //errno置为EWOULDBLOCK,这种错误可以忽略。另一种情况就是读取错误fprintf(stdout, "read error from stdin\n");                }            }else if(n == 0){//read返回为0,标准输入结束,设置stdineof标志。当to缓冲区不在有数据发送的时候                 stdineof = 1;//那么发送FIN分节, 如果有数据发送,则推迟FIN分节的发送                 fprintf(stderr, "%s, EOF on stdin\n", gf_time());   /* gf_time() is a function that we finished by  ourselves.*/                if(toiptr == tooptr)   Shutdown(sockfd, SHUT_WR);   /* send FIN*/            }else{                toiptr += n;                fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);    /*just read*/                FD_SET(sockfd, &wset);          //try and write to sockfd below            }        }        if(FD_ISSET(sockfd, &rset)){            if((n = read(sockfd, friptr, (size_t)(&fr[MAXLINE] - friptr))) < 0){                if(errno != EWOULDBLOCK)                    fprintf(stderr, "read error from sockfd\n");/*read error from socket*/            }else if(n == 0){                fprintf(stderr, "%s:EOF on sockfd\n", gf_time());                if(stdineof == 1) return;           /*normal termination*/            }else{                fprintf(stderr, "%s: read %d bytes from sockfd\n", gf_time(), n);    /*just read*/                friptr += n;                FD_SET(STDOUT_FILENO, &wset);       /*try and write below*/            }        }        //标准输出的条件是标准输出描述符号可写,并且fr缓冲区有数据可以输出         if(FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0 )){ // 判断的同时并且也获得了fr缓冲区中为数据的bytes大小             if((nwritten = write(STDOUT_FILENO, friptr, n)) < 0){//输出失败时候,EWOULDBLOCK错误忽略                 if(errno != EWOULDBLOCK) fprintf(stderr, "write error to stdout\n");            }else{                fprintf(stderr, "%s: wrote %d bytes to stdout\n", gf_time(), nwritten);                friptr += nwritten;         /**just written*/                //当输出指针(froptr)追上输入指针(friptr)的时候,指向fr缓冲区开始位置                 if(friptr == froptr)    friptr = froptr = fr;   /*back to begining of buffer*/            }        }        if(FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)){            if((nwritten = write(sockfd, friptr, n)) < 0){                if(errno != EWOULDBLOCK) fprintf(stderr, "write error to socket\n");            }else{                fprintf(stderr, "%s: wrote %d bytes to socket\n", gf_time(), nwritten);                toiptr += nwritten;         /*just written*/                if(toiptr == tooptr){    toiptr = tooptr = to;   /*back to begining of buffer*/                if(stdineof) Shutdown(sockfd, SHUT_WR);  /* send FIN */                //当stdin已经关闭, 并且to缓冲的数据都已经发送到了服务器, 那么接发送FIN分节到服务器。                 }             }        }    }}
详细分析如代码中的中文注释可得:

二、非堵塞IO问题分析

套接字默认状态是堵塞的。这也就说当我们发送出一个io请求的时候,如果不能及时的完成,那么进程将会进入睡眠状态,等待操作的进行。

(1)、输入操作,包括read, readv,recv,recvmsg,recvfrom五个函数。非堵塞的情况下如果不能被满足(对于tcp至少有一个数据可读,对于UDP套接字即有一个完整的数据报可读),相应的调用返回-1,并且errno将会返回EWOULDBLOCK错误

(2)、输出操作,包括write,writev、send、sendmsg、sendto五个函数。对与tcp而言,在堵塞套接字中,内核将会把数据从应用层缓冲区的数据复制到套接字缓冲区,如果套接字缓冲区空间不够哪儿内核将会进入休眠的状态,在非堵塞套接字中,如果套接字缓冲区的空间没有,那么将会立刻返回EWOULDBLOCK错误,若是有部分空间,则是返回该部分空间的字节数。对于UDP而言, UDP套接字不存在正真的缓冲区,内核直接将数据从应用层缓冲区换衣UDP首部和IP首部从协议栈往下传递

阅读全文
0 0
原创粉丝点击