泛读《Effective TCP/IP》

TL;DR

《Effective TCP/IP》一书进行了浏览,记录了印象较为深刻的一些技巧。

有趣的是,《Unix网络编程》系列书籍的作者 W. Richard Stevens 对这本书的编写提供了一些帮助,而作者也认为阅读 Stevens 的书籍也是一个提高网络编程水平的44个技巧之一 :-)

笔记

send/recv不是一一对应关系

初级网络程序员常犯的错误之一就是无法理解TCP传送的是一个没有记录边界概念的字节流。这一点很重要,可以总结为TCP中没有用户可见的“分组”概念,它只是传送了一个字节流,我们无法准确的预测在一个特定的读操作中会返回多少字节。

TCP通信中虽然能读取的数据报文是按seq有序的,但是send与recv因为网络以及其他种种原因,并不会按照意想之中调用多少次send,就可以调用多少次recv。例如发送两个数据包,由于网络最大传输单元的限制,可能会出现数据各种到达的排列组合结果,如:

  • A和B两个数据包分别可以读取
  • A和B的一部分先可以被读取,B的剩余部分一段时间之后能被读取
  • A和B一起能被读取
  • A的一部分先被读取,A的剩余部分和B能被读取

那么解决自身数据传输的的范围界定问题可以考虑什么样的方式呢?书中提到了几种方式:

  • 定长数据包(最简单也最实用)
  • 界定分隔符(有转义和解转义的成本)
  • 定长首部声明数据报文长度,再读取对应长度数据的数据

TCP的连接的存活与否判断最好由应用程序处理

TCP/IP由于设计的初衷是维持一个逻辑上的连接,例如中间路由部分的链路变化是不会认定是一个问题,即允许临时的连接丢失。

TCP同时也有Keepalive机制可以用来检测连接是否中断,原理是空闲一定时间之后发送一个特殊的段,但是问题在于一般设计上的空闲时长过长(RFC1122要求默认2小时),如果在这一个时间范围内客户端崩溃,那么服务端是无法得知的。

如果要敏感的,及时的得知客户端的状态,需要应用程序主动处理,定时的给对端发送心跳数据包,心跳到达重置超时,通过比如多次无应答等条件判定对端已关闭连接。

对于报文中本身就声明了报文类型的情况下,直接增加报文类型即可。如果没有的,可能需要创建单独的连接,进行心跳的传递。

TCP的写操作成功不代表数据已被发出

常规情况下,TCP的写操作在缓冲区已满时会阻塞。在未满时写操作实际上应该看做是将数据已经复制到缓冲区队列,在实际发送数据之前,就已经会返回。

发送数据的错误一般会在写操作返回之后发生,而一般跟随写操作的是一个读操作,所以有一个说法,写操作的错误是由读操作返回的。

TIME-WAIT的目的是为了牢靠的拆除连接

TCP四次挥手过程中,主动断开连接的一方,首先发送FIN(状态由ESTABLISHED变为FIN-WAIT1);被断开的一方收到FIN之后,返回ACK(状态由ESTABLISHED变为CLOSE-WAIT),同时发送FIN(状态由CLOSE-WAIT变为LAST-ACK);主动断开放收到ACK后状态由FIN-WAIT1变为FIN-WAIT2,在接收到被断开方发送的FIN包之后会发送一个ACK,同时状态变为TIME-WAIT,之后在2MSL(一般为2min)之后套接字彻底关闭连接并释放资源;而远端在FIN包对应的ACK之后,连接变为CLOSED状态。

TIME-WAIT的目的在于保证数据在网络中丢失之前,套接字不会被重用,能合理的,完整的完成一次连接拆出的过程。

检测主机连接状态可以通过高层次向低层次的方向进行

首先可以通过应用层操作进行检测(如curl),之后可以通过telnet等方式在传输层进行检验,如果传输层无法检测到通路的存在,则可以尝试通过ICMP等更低层次的检测(ping)。

UDP也可以先连接

UDP虽然是面向无连接的协议,但是一样也可以先调用connect系统调用。

调用之后的变化在于,可以使用send与recv系统调用,而不是sendto与recvfrom。

UDP Socket使用connect并不会实际产生网络流量,只是一个本地行为,只会将远端的IP与端口绑定到本地套接字之中,看似没有什么特别的作用,然而如果在发送数据包之后出现了错误,那么远端返回的错误因为已经完成了绑定,可以被当前套接字感知,起到通知应用层错误控制的作用。

缓冲区大小设定的经验值

至少将缓冲区设置为MSS的3倍大小。

traceroute原理

traceroute首先对下一个目标值设定一个TTL长度为1的ICMP数据包,发送到下一条之后,因为TTL减小为0,路由器会返回一个ICMP传输超时的报文回到发送端,同时带有下一跳的地址。

发送端接收之后向下一跳发送一个TTL长度增加1的ICMP数据包,重复上述过程,最终绘制出到达远端的路径。

路由表中标记位G和H不是对立的

H表示目标是一个主机,如果没有则说明是一个网络号。

G表示没有直连的通路,需要经过一个网关。

想象一下如下的case,主机A与B在一个子网,B为网关,同时与主机C相连,那么A的路由表里面就会出现如下的记录:

Destination Gateway Flags Refs Use Netif
C B UG 0 0 eth0