套接字的默认状态是阻塞的。这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应的操作完成。可能阻塞的套接字调用可分为以下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信号的默认行为是终止进程。

UNIX网络编程——非阻塞式I/O(套接字)的更多相关文章

  1. UNIX网络编程——非阻塞connect:时间获取客户程序

    #include "unp.h" int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec) ...

  2. 《Unix 网络编程》08:基本UDP套接字编程

    基本UDP套接字编程 系列文章导航:<Unix 网络编程>笔记 UDP 概述 流程图 recvfrom 和 sendto #include <sys/socket.h> ssi ...

  3. unix网络编程第四章----基于TCP套接字编程

    为了执行网络I/O操作.进程必须做的第一件事情就是调用Socket函数.指定期待的通信协议 #include<sys/socket.h> int socket(int family,int ...

  4. UNIX网络编程-非阻塞connect和非阻塞accept

    1.非阻塞connect 在看了很多资料之后,我自己的理解是:在socket发起一次连接的时候,这个过程需要一段时间来将三次握手的过程走完,如果在网络状况不好或者是其他的一些情况下,这个过程需要比较长 ...

  5. UNIX网络编程——非阻塞accept

    当有一个已完成的连接准备好被accept时,select将作为可读描述符返回该连接的监听套接字.因此,如果我们使用select在某个监听套接字上等待一个外来连接,那就没有必要把监听套接字设置为非阻塞, ...

  6. UNIX网络编程——非阻塞connect: Web客户程序

    非阻塞的connect的实现例子出自Netscape的Web客户程序.客户先建立一个与某个Web服务器的HTTP连接,再获取一个主页.该主页往往含有多个对于其他网页的引用.客户可以使用非阻塞conne ...

  7. UNIX网络编程——非阻塞connect

    当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的TCP三次握手继续进行.我们接着使用select检测这个连接或成功或失败的已建 ...

  8. 【UNIX网络编程(四)】TCP套接字编程具体分析

    引言: 套接字编程事实上跟进程间通信有一定的相似性,可能也正由于此.stevens这位大神才会将套接字编程与进程间的通信都归为"网络编程",并分别写成了两本书<UNP1> ...

  9. UNIX网络编程 第8章 基本UDP套接字编程

    UDP是无连接的,不需要accept,TCP通过accept API来接受连接,并且将连接客户端的信息写入到accept将返回的新socket中,该新socket中有服务端和客户端的IP地址和端口,因 ...

随机推荐

  1. hdu 5641 BestCoder Round #75

    King's Phone  Accepts: 310  Submissions: 2980  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: ...

  2. [bzoj3668][Noi2014]起床困难综合症/[洛谷3613]睡觉困难综合症

    来自FallDream的博客,未经允许,请勿转载,谢谢. 21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳.作为一名青春阳光好少年,atm 一直坚持与起床困难综 ...

  3. Cisco 的基本配置实例之六----常排错命令--关闭提示

    TEST#terminal monitor # 排除网络故障以前,请打开这一命令以便实时的接收到交换机的提示信息. TEST# TEST#sh run #显示所有的配置清单,可将这些配置保存成文本作为 ...

  4. js登录,回车登录

    $(document).ready(function(){ $("#loginBtn").click(doLoginEvent); loadCookies(); //回车登录 do ...

  5. String 类

    一.String类String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象.java把String类声明的final类,不能有类.String类对象创建 ...

  6. RabbitMQ用户管理

    rabbitmq常用命令 add_user        <UserName> <Password> delete_user     <UserName> chan ...

  7. 罗列Linux发行版的基础目录名称,命令法则和功能

    罗列Linux发行版的基础目录名称命名法则及功用规定 目录描述 /主层次 的根,也是整个文件系统层次结构的根目录 /bin存放在单用户模式可用的必要命令二进制文件,所有用户都可用,如 cat.ls.c ...

  8. Java 程序运行过程中的内存分析

    作为 java 程序员,都应该知道 Java 程序运行在 JVM(Java Virtual Machine,Java 虚拟机)上,可以把 JVM 理解成 Java 程序和操作系统之间的桥梁,JVM 实 ...

  9. ng-book札记——路由

    路由的作用是分隔应用为不同的区块,每个区块基于匹配当前URL的规则. 路由可以分为服务端与客户端两种,服务端以Express.js为例: var express = require('express' ...

  10. Kirill And The Game CodeForces - 842A

    CodeForces - 842A 需要将除法改换成乘法进行计算 #include<bits/stdc++.h> using namespace std; int main() { lon ...