UNP 学习笔记 2:协议概述与传输层对应 socket 函数行为

OS ,应用以及他们用的协议

  • 首先明确所有的 IP 层及以上都需要操作系统内核网络栈处理。所以底层 OS 实际接触到的层次是数据链路层。tcpdump 能实现的原理即字节访问数据链路层, 这里涉及 BSD packet filter 和 datalink provider interface 两种方法直接访问底层。
  • ICMP 是独立于 TCP,UDP,SCTP 的一个基于 IP 上的网际控制报文协议,ping 和 traceroute 都会用。这里 traceroute 实际是用发送没用的 UDP 报文配合篡改 IP 包的 TTL 接受 ICMP 包来实现 trace。
  • ARP 也是操作系统要实现的,这个是直接在数据链路和IP之间的。RARP 是反向地址解析。

各协议的 RFC 参考

     (由于具体协议在计网和 TCP/IPv1详细学习了,这里不记录了)

  • 首先是读 RFC 的好处,他解释了很多东西为什么要这样规定,比书本或者网络上的n道信息贩子的魔改更精确详细。可以去这里搜索 RFC:https://datatracker.ietf.org/doc/,知道 RFC 号可以通过这个查看:https://www.rfc-editor.org/rfc/rfc#### (替换为 RFC 号)
  • UDP:RFC 768
  • TCP:RFC 793RFC 1323RFC 2581, RFC 2988,RFC 3390.
  • UDP 没有流控,很容易编写一个超速的发包者导致包不会被接收。
  • SCTP (Stream Control,RFC2960)是面向消息的,主要是支持多宿主连接和不 block 的 tcp 优点,这两点都能帮助即时通信因而被用到 WebRTC 里,
  • 实际 SCTP 没有直接 over IP 实现,如各种 linux kernel,browser 都用 SCTP over UDP 来实现(所以像我们之前说到谷歌搞了QUIC也能用在 WebRTC 上,因为都是定制,不如直接重做了属于是) ,这个我们在 RFC 6951 里面可以看到这个解释,因为我们现代网络是基于 NAT 的!笑死,很多 NAT 只考虑了 TCP、UDP。

TCP 连接建立 与 socket 函数

  • passive open:socket,bind,listen 函数会引发一个允许外界连接的端口,否则 TCP 栈会丢弃入站 TCP 包.
  • active open: 使用 connect 的时候实际是发送第一个 SYN 请求连接, 主要是客户端启用.
  • Accept: 服务端调用 accept 时, 阻塞进程, 如果 listen fd 收到了 SYN, kernel 会在底层队列拿一个建立好的连接创建 fd 之后传上来, 但是 accept 调用与否, 连接都会默认建立好的了, 这是在 bind 和 listen 之后默认会建立连接, 所以 accept 实际是发生在三次握手之后.
  • 所以我们可以理解 listen 干的事情才是准许回应 SYN, 这是因为 listen 的参数就是指示 kernel 建立 queue. 如果没有 queue, 自然不会响应 SYN, 这种情况, 这种情况实际是 RST 情况.
  • RST 的几种情况, 前面学习 TCP/IPv1 的笔记里面讲过了为什么要做 RST (尝试与一个已经关闭的连接通信) , 而实际 RST 也会被用到其他地方, 比如一些操作系统可以对没有 bind 的入站 SYN 响应 RST, 对没有 listen 的端口响应 RST, 提前关闭连接(传输中途突然 close)也可以发 RST.
  • 由于 RST 的存在, 就不能通过 raw socket 直接访问 IP 包来上层做没有 listen 的 TCP 端口的 SYN+ACK 建立连接了, 因为 TCP 入站报文全部会经过 kernel 网络栈, 这时候 kernel 很有可能直接 RST, 如果要用 raw 的话, 需要禁止内核处理数据包.
  • 腾讯面试题(ref): 如果没有 listen 可以建立 TCP 连接吗? 首先第一个方法, 之前那片笔记我们说过标准规定 TCP 栈需要支持双向 同时 connect 然后建立一条连接的情况来方便打洞. 所以双方同时 connect 就不用 listen 也能建立连接. 这个其实也可以通过思想实验实现: server 发 SYN+ACK 的时候, client 也没有 listen , 这是因为 connect 本身也要委托给 kernel 去做入站映射表的建立, 而不会响应 RST. 套接字本身也可以 connect 一个已经 bind 没有 listen 的地址端口, 这是因为 loopback 的存在吗? 这一点我暂时没找到解释, 还是得看 os 具体实现才行.
  • 如果不 listen 就发 rst, 那么 bind 和 listen 分开的意义何在呢? 首先 bind 是明显地声明应用占用了一个端口, 但是允许不允许连接是另一回事.

TCP 选项

  • MTU 与 数据链路层:我们看 netstat -if  显示的 lo 的 MTU 是 65535, while eth0 是 1500, 1500 这个我们在计网笔记里面已经讲过了, 是因为早期的 Ethernet CSMA 的保证能持续监听 5.12km 的限制, 谁是被约定好了在所有以太网设备上,这个实际是数据链路层的限制。而 IP 包最大支持 65535(由长度字段 bytes 数 uint 范围决定),所以 IP 到数据链路可能被分片。lo 的话直接内存复制或者映射就行了,赢!
  • 分段最大选项: MSS tcp 上用 segment 讲, 所以是 MSS
  • 窗口规模选项(RFC1323):窗口由于用 16位字段负责通告大小,限制了 65535 大小(因为uint 无法表示65536)。为了适应,可以让一个选项(比如 syn 或者ack的时候payload里双方指定?)让窗口大小字段实际<<1。
  • 时间戳选项(RFC1323),这个我们之前的笔记讲过了测量 RTT 的时候可以想办法让报文带上时间戳,这样方便测量每个报文的 RTT instead of 每个窗口只有一个计时器。而且时间戳的存在(4.2提到就是复用之前测量 RTT 的时间戳来实现这个目的)可以避免失而复得包(第三版中文翻译说这个不是重复的包而是失去的包,我认为失去的包延迟到达,此时不会复用新一轮序号,这一点暂时存疑,RFC里面主要讲的是 lost 的情况?)可能和 32 位序列号重复。作者说网络编程实际我们不用管他😓

TCP 连接关闭与 close

  • 这里应该有一张四次挥手的图。(不过已经脑内栩栩如生了属于是)。
  • active close,主动方发一个 FIN 就行了。
  • passive close,接收的 TCP 栈收到 FIN, 此时必须通知 high level,我们沿用虚拟文件,所以就传一个 EOF 上去就行了(当然得先把缓冲区的东西都处理完)。(背诵内容请不要忘记)此时主动方处于 FIN-WAIT-2 的状态(ACK接收后),被动方处于 CLOSE-WAIT 的状态,这时被动还能 write 写东西(不能!什么否定之否定)(half-close,但是其实主动方还要发ACK,还是双向连接属于是)。
  • 注意 half-close,由于对应文件的 close,主动方如果调用了 close,fd 都没了!所以 half-close 就不存在了。
  • 实际上这种情况是之前笔记说的(好像没说,那就之前我看书看到的)禁止 half-close 即双向关闭了,这种情况发 FIN 没用的,因为 FIN 是正常 half-close 支持的,所以要发 RST。所以我们还要做一个函数叫 shutdown 真实贯彻落实 TCP 的 half-close 规定,这个之后章节再讲。
  • FIN2,被动方调用 close 后,发送 FIN,导致主动方的 FIN-WAIT-2 结束,进入 2MaxSegementLifetime 状态了(TIME-WAIT),而被动也要 LAST-ACK 等待,不过已经是委托给 kernel 了。
  • 复习 time-wait 的两个要素,一个是持续响应丢包的 last-ACK,第二个是避免新连接接受 lost duplicate 引发错误组装。

SCTP 过程

  • 见 UNPv13e P38
  • 标准: RFC2960
  • over UDP: RFC 6951
  • 或者之后看 KCP,QUIC 那些的时候专门针对定制 UDP 做一个专题看看。

TCP 连接绑定不同进程的 fd

  • 主要是理解标记一个 TCP 连接必须是四元组才能标识,而不是仅仅用 服务器的 dstip:port 二元 pair,
  • 这一点前一篇笔记的 listen 小节也讲过了。

OS 缓冲区与 syscall 行为

  • 我们之前 os 学习已经讲过一些缓冲区的印象,基本是使用 copyin和 copyout函数实现 user process 和 kernel 的通信。
  • 这里讲解 write fd syscall 会发生什么,write 返回仅意味着 os 的缓冲区是 available for more bytes,仅此而已,不能说对方已经收到了。
  • 而 ACK 又是针对字节流控制的,所以要实现已读功能,必须编写应用层的 ACK,是否收到即发送成功也是需要应用层 ACK 的。
  • 应用层可靠传输可以参考这系列IM技术文章学习,深入理解 TCP 是字节流协议,所以必须需要应用层 ACK 的。(RFC2960讲为什么要 SCTP 的时候也分析了 TCP 的不足)。
  • 有争议的起名“粘包”问题也是发生在这里,就是因为没有认识到 TCP 是字节流协议而不是包的协议才能想到归类为问题。
  • 不过当然,write 返回起码说明有一部分上次在缓冲区的某些字节们(free 缓冲区达到返回的阈值也许存在,那么就由操作系统决定)已经被对方接受了,不然没有 ACK 的话也不可能腾出缓冲区,但是 again,这不意味着应用层收到了,只能说操作系统收到了,操作系统也可能没有成功传到应用层,或者可能被用户终端某些系统级程序拦截(如代理)等,所以这一点还是点个没有帮助吧。

IP 分片问题

  • 首先复习 IP 的分片,明确知道某些 Ethernet MTU 是1500,然后 IP 最大 65536, 而 UDP 本身是必须 fit in IP 包的。这里我们需要知道分片完全是为了 数据链路层,因为这里他是完全乱发的。
  • 所以 IP 包有可能会被拆分成 1500 的小包!这一点也是为了匹配数据链路层的 MTU。所以其实 IP 层是有序号这种东西的,这些内容是路由器和计算机 IP 层负责的,他必须重组 IP 包,所以 IP 头是有 identifier 和 分片 offset 的。
  • 但是IP层 No control 这个点也意味着,IP层是没有任何的重发的,一旦超时没有组装成功包的话,就会干脆不要的,而后续如果来了一个迟到的,id 已经没有 match 的了,直接丢了。
  • UDP 的 write 返回意味着数据链路层准备好发下一个包,这个速度一般会比 TCP 快很多,但是如果 write 失败的话某些底层实现有可能也不会抛出错误 since UDP 本来就不可靠,让他更不可靠也不差什么了。所以发 UDP 大包会出错的概率会比小包大很多,这是因为多了一个分装和重组的过程,而且这个过程是不可知的比TCP来讲,因为链路层的支援 MTU 并不是固定的,有的设备可能沿用 1500,有的可能支援 65535.
  • 同样的,我们这里也不看 SCTP。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值