UNP-UNIX网络编程 第十六章:非阻塞式I/O

(一) 概述

套接字默认状态是阻塞的。也就是说当发出一个不能立即完成的套接字调用时,进程将被投入睡眠,等待相应操作的完成 ,一般分为四类:
1. 输入操作:包括read、readv、recv、recvfrom、recvmsg这5个函数。当套接字缓冲区没数据可读时,进程将被投入睡眠状态,直到有数据可达。
2. 输出操作:包括write、writev、send、snedto和sendmsg这5个函数。
如果发送缓冲区没有空间,则进程将被投入睡眠,因为这些操作需要将应用进程缓冲区的数据拷贝到套接字的发送缓冲区的数据
(UDP不存在真正的发送缓冲,内核只是复制数据并把他们沿着协议栈向下传送并依次包装UDP首部和IP首部)。
3. 接收连接:accept函数,这个就很熟悉了,当服务器等待客户端外来连接的到来时将被投入睡眠。
4. 建立连接:根据一个TCP三路握手的过程,connect函数至少阻塞一个RTT的时间(自己的SYN发送和服务器的ACK的接收)。

(二)非阻塞str_cli函数

主要复杂之处在于缓冲区的管理,设想一个场景:
如果标准输入可读,read返回,接着调用write,然而,如果这个时候套接字发送缓冲区已经满了,则write会阻塞,当write阻塞的时间里面,很有可能有来自接收缓冲区的数据可读;

1.复杂版本,使用select+非阻塞I/O
(1).fcntl将标准输入、标准输出和网络套接字描述符都设置为非阻塞模式。
(2)调用select之前对4中情况进行判断。
(3).四种套接字可读或者可写后的操作,细节先忽略。

2.简单版本,使用fork多线程
函数一开始就把进程划分为父进程和子进程,父进程用来把标准输入的文本复制到服务器,子进程用来将回射的文本复制到标准输出。
从图中可以看到,尽管只有一个套接字(父子进程共享),但由于TCP是全双工的,并且这个套接字有两个描述符(fork子进程时完成拷贝),所以父进程可以往里写,而子进程可以从中读。
这里需要注意一些边界条件,例如服务器关闭,客户子进程将收到EOF,此时子进程需要给父进程一个SIGTERM以防止父进程仍在执行。
另外,父进程中要用shutdown而不是close,因为此时是多进程共享描述符,引用计数大于1。

#include    "unp.h"
void str_cli(FILE *fp, int sockfd)
{
    pid_t   pid;
    char    sendline[MAXLINE], recvline[MAXLINE];

    if ( (pid = Fork()) == 0) {     /* child: server -> stdout */
        while (Readline(sockfd, recvline, MAXLINE) > 0)
            Fputs(recvline, stdout);

        kill(getppid(), SIGTERM);   /* in case parent still running */
        exit(0);
    }

        /* parent: stdin -> server */
    while (Fgets(sendline, MAXLINE, fp) != NULL)
        Writen(sockfd, sendline, strlen(sendline));

    Shutdown(sockfd, SHUT_WR);  /* EOF on stdin, send FIN */
    pause();
    return;
}

注:客户向服务器复制两千行文本所用时间:
select+阻塞I/O:12.3 s;
select+非阻塞I/O:6.9 s;
fork多线程:8.7 s;
多线程:8.5 s。

(三)非阻塞connect

当在一个非阻塞的套接字上调用connect的时候,会立刻返回一个EINPROGRESS错误,不过TCP三路握手连接将继续进行。当调用connect后,我们可以使用select检测这个连接成功、失败。非阻塞connect主要有三个用途:
1. 三路握手的同时进行其他处理;
2. 同时建立多个连接,例如web服务器;
3. 减少connect的超时等待时间;
利用非阻塞的connect可以构建一个web客户端,例如我们在与一个web服务器建立一个http连接的时候,我们会获取一个homepage,随后这个主页往往还有对其他网页的引用,客户可以通过非阻塞的connect并行地获取多个网页,而不是阻塞地串行获取,这样就提高了效率。(考虑到TCP的拥塞避免机制,他对网络是不利的)

你可能感兴趣的:(后端(基础))