• UNIX网络编程——非阻塞式I/O(套接字)


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

    (1)输入操作,包括read,readv,recv,recvfrom和recvmsg共5个函数。如果某个进程对一个阻塞的TCP套接字(默认设置)调用这些输入函数之一,而且该套接字的接收缓冲区中没有数据可读,该进程将被投入睡眠,直到有一些数据到达。既然TCP是字节流协议,该进程的唤醒就是只要有一些数据到达,这些数据既可能是单个字节,也可能是一个完整的TCP分节中的数据。

          既然UDP是数据报协议,如果一个阻塞的UDP套接字的接收缓冲区为空,对它调用输入函数的进程将被投入睡眠,直到有UDP数据报到达。

          对于非阻塞的套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据报可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误


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

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

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


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

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


    (4)发起外出连接,即用于TCP的connect函数。TCP连接的建立涉及一个三次握手过程,而且connect函数一直等到客户收到自己的SYN的ACK为止才返回。这意味着TCP的每个connect总是阻塞其调用进程至少一个到服务器的RTT时间。

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

     

     

    下面该函数使用fork把当前进程划分成两个进程。

         这个函数一开始就调用fork把当前进程划分成一个父进程和一个子进程。子进程把来自服务器的文本行复制到标准输出,父进程把来自标准输入的文本行复制到服务器。

                            

    void
    str_cli(FILE *fp, int sockfd)
    {
    	pid_t	pid;
    	char	sendline[MAXLINE], recvline[MAXLINE];
    
    	if ( (pid = fork()) == 0) {		/* child: server -> stdout */
    		while (read(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;
    }

         我们知道TCP连接是全双工的,而且父子进程共享同一个套接字:父进程往该套接字中写,子进程从该套接字中读。尽管套接字只有一个,其接收缓冲区和发送缓冲区也分别只有一个,然而这个套接字却有两个描述符在引用它:一个在父进程中,另一个在子进程中。

         我们同样需要考虑进程终止序列。正常的终止序列从标准输入上遇到EOF之时开始发生。父进程读入来自标准输入的EOF后调用shutdown发送FIN。但当这发生之后,子进程需继续从服务器到标准输出执行数据复制,直到在套接字上读到EOF。

         服务器进程过早终止也有可能发生。要是发生这种情况,子进程将在套接字上读到EOF。这样的子进程必须告知父进程停止从标准输入到套接字复制数据。子进程向父进程发送一个SIGTERM信号,以防止父进程仍在运行。如此处理的另一个手段是子进程无为的终止,使得父进程(如果仍在运行的话)捕获一个SIGCHLD信号。

         父进程完成数据复制后调用pause让自己进入睡眠状态,直到捕获一个信号(子进程来的SIGTERM信号),尽管它不主动捕获任何信号。SIGTERM信号的默认行为是终止进程。




  • 相关阅读:
    Android 网络优化,使用 HTTPDNS 优化 DNS,从原理到 OkHttp 集成
    WebView,我已经长大了,知道自己区分是否安全了!
    “崩溃了?不可能,我全 Catch 住了” | Java 异常处理
    Google 的 QUIC 华丽转身成为下一代网络协议: HTTP/3.0
    图解:HTTP 范围请求,助力断点续传、多线程下载的核心原理
    c/c++ 读入一行不确定个数的整数
    LeetCode:Length of Last Word
    LeetCode:Permutation Sequence
    机器学习:判别模型与生成模型
    LeetCode:Jump Game I II
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6172556.html
Copyright © 2020-2023  润新知