《UNIX网络编程》读书笔记2---2-5章,套接字断开2,3事

时间:2021-09-26 10:22:39

Stevens的书连着读真是酣畅淋漓,爽。总结真是改了又改,不过一切都是开心的。

这里一定要结合第二章的那若干种状态来看。可以通过netstat -a | grep port 来查看。


1.关于TCP的几种关闭的边界条件

讨论的前提都是没有使用SO_KEEPALIVE套接字选项,即client探测到server的状况需要write发送数据。

①正常终止

(所谓正常终止,就是说终止连接是client这边发起的。包括A。client调用了shutdown或者close B。client进程终止,不管是exit(0)退出终止,还是被信号终止)

②服务器进程终止(被信号杀死)

③服务器主机崩溃

④服务器主机崩溃后重启

⑤服务器主机关机。


其中①为正常,其它都为异常,究其原因是因为,server的第一个行为就是读,并对读到的内容进行判断。

只要client那边终止了,不管是什么原因的终止,只要没有client主机崩溃,而是client进程的终止。

server这边就能读到FIN消息,然后完成正常的四步,退出。所以从client那边终止,总是正常的。


补充一下,read函数有关套接字返回值的行为。

(1)内核收到对端TCP数据,套接字变为可读,read返回大于0的值

(2)内核收到对端FIN(对端进程终止,对方调用close,shutdown),套接字变为可读,read返回0(EOF)

(3)内核收到对端RST(对端主机崩溃并重新启动),套接字变为可读,read返回-1,errno中含有确切的错误码


所以就算你这边什么也不输入,只是回车,也会有一个换行符传送到对端,那么read是不会返回0表示输入结束的,

只有这边进程终止或者应用程序调用了close或者shutdown,那么内核会发送FIN,对端read才会返回0,表示另一边输入EOF了。


2.客户/服务器程序结构

一个服务器,tcpserv01.c+str_echo.c        一个客户端,tcpcli01.c+str_cli.c    书P98-100

后面的所有的改进都是在这两个程序之上,改进的,而尤其是改进两个str_函数。


服务器程序结构

①socket准备

②循环,阻塞于accept。一旦连接请求到达接受请求,然后fork子进程。

子进程使用str_echo进行弹射,父进程继续阻塞于accept。


客户端程序结构

①socket准备

②阻塞于str_cli。完成从stdin读取,向server发送,从server接收,在stdout显示的任务。


3.正常终止


进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核关闭。

所以这个关闭进程中的套接字描述符的过程,并不是一个一蹴而就的过程,还是交给内核进行,适当的操作。

联想到其实调用close关闭描述符最后肯定还是要调用内核,此种情况下依靠内核关闭,那么和调用close本质上是没有区别的。


所以一直都是内核使得client发送FIN然后收到ACK,最后对于server发过来的FIN也有一个ACK。

这里有一个概念其实就是一个套接字连接,其实有应用程序层面的,也有内核层面的。

而我们的发起连接,等待连接这些函数包括close这些都是应用程序层面的,

而像ACK这样的,是内核层面的一种对于物理层收到了信息就会有的回复,这个与应用层代码无关。它是一个内核的行为。


一个进程结束还有描述符(套接字)的回收操作,这个操作是由内核来完成的,不管你是正常返回,还是被信号杀死,都会有内核来对你进行回收,这个不影响套接字关闭的最后四步的ACK,或者FIN,因为这本来就是由内核来完成的。


注:核心就是弄清楚,套接字(文件描述符)与打开它的进程之间的关系。

套接字可以关闭,但是进程还活着。

进程也可以终止,但是套接字还开着(如果用了fork使得同一套接字被多个进程同时打开)

但是,如果只有一个进程,那么进程终止,套接字也会被关闭,但是套接字彻底关闭也许是在进程终止之后一段时间。


4.服务器主机进程终止+服务器主机关机

因为主机正常关机会先发送SIGKILL信号,所以情况和服务器主机进程终止是一样的。

进程终止,server向client发送FIN,client回复ACK。

然后client读取用户输入,向server writen,接着调用readline,然后返回0,表明EOF,然后client检测到0,进行相关处理。


注:①由于server已经发送FIN,这个时候client仍然writen,server已经没有连接进程了,所以回复一个RST。

吊轨的地方就是本来收到RST,read应该返回-1,而client的read返回了0,表示EOF呢?

因为client writen之后,下一条语句就是readline,而那边的RST发过来是走的网络,还没有到达。

也就是说readline的调用与返回,是在RST到达之前。

那么由于之前client接收到了FIN,所以这里read是返回0。

②这里client程序read返回0,接着也收到了RST。

它可以这个时候判断read返回值,然后结束进程。

也可以不管,继续运行程序,只是这个时候如果已经收到了RST,还向发来RST的套接字write的话,内核就会发送一个SIGPIPE信号。

默认就是终止进程了。


5.服务器主机崩溃

这个嘛,client这边并不知道发生了任何事情,因为那边主机都崩溃了,不会向这边发送任何的信息了。

最后阻塞于readline,那么这个调用就会返回错误。

要么就是timeout要么就是hostunreach,netunreach


注:write函数只要将字符串,写到了内核缓冲区就会返回。而至于内核缓冲区发过去以后,收没有收到ACK,不关write函数的事情。


6.服务器主机崩溃后重启

这个有点类似于4中的服务器进程终止或者服务器关机,不同的是,

这边崩溃也没有消息传到那边,所以client不会收到任何的FIN信息。

但是当client writen信息过来的时候,server并不知道这是个什么东西,所以会发送RST。

由于,server这次没有发送,FIN,所以client的readline就会阻塞,一直到通过RST返回-1。

接下来的情况就看client对于这个-1怎么处理了。

可以判断后终止连接。

也可以不管,继续writen,最终被SIGPIPE信号终止。


7.server的accept返回非致命错误的情况

①假设服务器是多进程的,然后父进程阻塞在accept,子进程在为一个client echo。

结果子进程终止了,内核会发一个SIGCHLD给父进程,虽然这个信号的默认行为是被忽略。

但是父进程的accept程序确实是被中断了。并且返回一个非致命错误EINTR

吊轨的地方在于,有的操作系统并不会自动重启,accept。所以要进行相应地处理。

所幸的是linux会自动处理的。CSAPP也有说到这个问题,如果要编写可移植程序就要多多注意这个了。

②完成三次握手连接建立后,client却发送了一个RST,这个可能出现在繁忙的web服务器上,我们可以通过在listen后睡眠一段时间accept来模拟。

这个错误既然是client RST,那么管server吊事啊,所以根本就是非致命错误,所以只需重启accpet即可。