深入理解TCP/IP协议( 二 )


(192.168.1.100) 位于 08:00:27:1a:7a:0a [ether] 在 eth0
了解了上述情况,我们分析一下刚刚宕机的情况,此时客户端是可以向服务端发送数据包的 。但是由于服务器宕机,因此不会给客户端发送任何回复 。
深入理解TCP/IP协议

文章插图
图6 数据包截图
由于客户端并不知道服务端宕机,因此会重复发送SYNC数据包,如图6所示,可以看到客户端每隔几秒会向服务端发送一个SYNC数据包 。这里面具体的时间是跟TCP协议相关的,具体时间不同的操作系统实现可能稍有不同 。
3. 建立连接时,服务器应用被阻塞(或者僵死)
还有一种情况是在客户端建立连接的过程中服务端应用处于僵死状态,这种情况在实际中也会经常出现(我们假设仅仅应用程序僵死,而内核没有僵死) 。此时会出现什么状态?TCP的三次是否可以完成?客户端是否可以收发数据?
在用户层面我们知道,服务端通过accept接口返回一个新的套接字,这时就可以和客户端进行数据往来了 。也就是在用户层面来说,accept返回结果说明3次握手完成了,否则accept会被阻塞 。在我们假设的情况下,其实就相当于应用程序无法进行accept操作了 。
如果想彻底理解上面我们假设的问题,需要理解两点,一点是accept函数具体做了什么,另外一点是TCP三次握手的本质 。
我们先试着理解第一点,accept会通过软中断陷入内核中,最终会调用tcp协议的inet_csk_accept函数,该函数会从队列中查找是否有处于ESTABLISHED状态的套接字 。如果有则返回该套接字,否则阻塞当前进程 。也就是说这里只是一个查询的过程,并不参与三次握手的任何逻辑 。
三次握手的本质是什么呢?实际上就是客户端与服务端一个不断交流的过程,而这个交流过程就是通过3个数据包完成的 。而这个数据包的发送和处理实际上都是在内核中完成的 。对于TCP的服务端来说,当它收到SYNC数据包时,就会创建一个套接字的数据结构并给客户端回复ACK,再次收到客户端的ACK时会将套接字数据结构的状态转换为ESTABLISHED,并将其发送就绪队列中 。而这整个过程跟应用程序没有半毛钱的关系 。
当上面套接字加入就绪队列时,accept函数就被唤醒了,然后就可以获得新的套接字并返回 。但我们回过头来看一下,在accept返回之前,其实三次握手已经完成,也就是连接已经建立了 。
深入理解TCP/IP协议

文章插图
【深入理解TCP/IP协议】 
另外一个是如果accept没有返回,客户端是否可以发送数据?答案是可以的 。因为数据的发送和接受都是在内核态进行的 。客户端发送数据后,服务端的网卡会先接收,然后通过中断通知IP层,再上传到TCP层 。TCP层根据目的端口和地址将数据存入关联的缓冲区 。如果此时应用程序有读操作(例如read或recv),那么数据会从内核态的缓冲区拷贝到用户态的缓存 。否则,数据会一直在内核态的缓冲区中 。总的来说,TCP的客户端是否可以发送数据与服务端程序是否工作没有任何关系 。
当然,如果是整个机器都卡死了,那就是另外一种情况了 。这种情况就我们之前分析的第2种情况一直了 。因为,由于机器完全卡死,TCP服务端无法接受任何消息,自然也无法给客户端发送任何应答报文 。
总结今天我们主要介绍了连接建立过程中的各种异常情况,还有另外一种情况是在数据的传输过程中 。比如传输过程中服务器突然掉电,或者程序crash等,后续我们将详细这些异常情况下在协议层的表现 。




推荐阅读