tcp
tcp和udp的不同
这是两种连接协议,直接说我的看法吧。(能多说的地方尽量多说别说一句就停)
tcp是面向连接的,udp是面向无连接的。(区别是传输数据是否要建立连接)
tcp是可靠传输,传输数据有序可靠不丢不重。udp是不可靠传输,只能尽力交付。
tcp传输效率比较慢,因为需要保证可靠要做重试之类的,udp传输效率比较快。
文件传输这种可靠性需求高的就可以用tcp连接,追求实时性高速传输就用udp。
(有无粘包应该也算,可以当扩展来吟唱)
粘包/拆包
首先tcp是面向字节流的,udp是面向报文的。字节流就是数据像流水一样,报文是独立完整有界限的数据包。
对于字节流发送了3次:"ABC", "DEF", "GHI",可能 Recv 1 次就全拿到了("ABCDEFGHI")------粘包了 ,也可能 Recv 2 次("ABCDE", "FGHI")被拆包了 。
而对于报文,send几次就要recv几次,次数要严格对应几发几收。
造成粘包/拆包的原因:
tcp发送缓冲区剩余空间不足以发送完整的数据包会导致拆包。
要发送的数据超过了限定的最大报文长度也会导致拆包。(不管发送缓冲区够不够都会触发)
要发送的数据包小于tcp缓冲区剩余空间,tcp会将多个数据包写满缓冲区一起发送导致粘包。
接收端没有及时消费tcp发送缓冲区的数据包会导致粘包。
这么看好像tcp的发送缓冲区很严格啊,实际上只是tcp想把效率(这里能提高的是带宽)拉满,所以放弃了消息边界这个概念。
在tcp来看拆包,粘包反而算优化。网线有规定一次最大字节数如果他不帮你拆就只能你自己拆了,他帮你拆反而便利。对于粘包,他采用了把操作放到缓存池等到数量够了一起搞这种常见的提高效率但是降低实时性的选择,因为本来就有为了实时性特化的udp存在了,所以这里为了效率做出这样的选择也很合理。
那么为什么udp不为了效率也选择这种呢?
(这也是技术选型啊,好爽,这种分析对比的感觉)
udp很强调实时连接,比如打游戏的一个操作按下一次空格,如果在采用缓冲池等别的操作够了一起发过来,会导致体验很差玩家直呼:"土豆服务器"。所以为了实时性只要有操作就发车尽管会有带宽(一次发送的数量)的浪费。
怎么解决拆包/粘包?
这里也要谈一下拆包/粘包的很关键的一个点,tcp(传输层)本来负责的就是把字节流安全、有序、完整地送到对面,不考虑里面装了什么。要解决就要在应用层下手(加工处理).
这里的解决就是对被拆分/粘合的数据流处理变回原样,因为tcp是规范不能从他那里下手,只能对那个数据下手(可以是从发送方),或者接收方下手。
解决方法:
发送方给数据包添加首部,首部里面有长度属性,这样就能直接处理了。
对于数据包小于自己规定的一个长度的可以考虑末尾补0,再根据这个长度来读取。(实现很无脑)
发送端给不同的数据包添加边界,接收端通过这个来区分不同的数据包。
注意点:这三种方法是互斥的选了一种就不能选择另一种。(也是三种技术选型)
像是jvm的g1垃圾回收器的分代一样,不同的问题使用不同的办法结合使用。
tcp怎么保证可靠性?
校验和:tcp报文头有检验字段,可以判断报文是否损坏。
序列号和确认号:发送端发送的时候会选择一个序列号,接收端会检测数据包的完整性与顺序性(校验和的作用),可以根据序列号进行排序去重(很重要的一个功能),如果检测通过会响应一个 ack 确 认号表示收到了数据包。
确认和超时重传机制 :接收方收到报文后就会返回一个确认报文, 发送方发送一段时间后没收到确认报文就会进行超时重传.。
流量控制:当接收方来不及消费发送方的信息,通过滑动窗口里的剩余的能消费的长度来提醒发送的频率,防止包丢失。
拥塞控制:当网络拥塞时,通过拥塞窗口,减少数据的发送,防止包丢失。(这里感觉讲的很浅,下面会细讲)
快速重传:
超时重传是简单好用的手段,但是痛点是时间任何定时的时间都不是很好设置呢,所以出现了快速重传。超时重传是保底机制,这个是优化机制。
正常发送 :发送方发了 1, 2, 3, 4, 5。=
发生丢包 :包 #2 丢了 ,但 1, 3, 4, 5 都到了。
接收方(疯狂催单):
- 收到包 #1 -> 回复 ACK 2(意思是:我收到1了,我要 2)。
- 收到包 #3(2没来,3来了,乱序了) -> 立刻 再回复一个 ACK 2(意思是:虽然收到3了,但我还在等 2 )。------ 这是第 1 个重复 ACK。
- 收到包 #4 -> 立刻 再回复一个 ACK 2(意思是:收到4了,还是没等到 2 )。------ 这是第 2 个重复 ACK。
- 收到包 #5 -> 立刻 再回复一个 ACK 2(意思是:收到5了,你快把 2 给我啊 )。------ 这是第 3 个重复 ACK。
发送方得到了3个ACK立马重传2,不需要等待那个保守设计的很长的时间。(设置三个ACK才重传也是为了防止误判)
tcp拥塞控制
这个很像流量控制,但是存在一个本质区别就是不知道窗口里的剩余长度,只能去靠自己试探。
tcp通过一个拥护窗口来猜。发送方真正的发送窗口 = min ( 接收方通告的窗口大小, 发送方自己估算的 窗口大小)。
拥塞控制有四个阶段:
第一阶段:慢启动
(哪里都有我们鸡煲慢启动,但是这个指数增长很快的)
刚开始建立连接开始慢慢的试探从1,2,...2^n只要不丢包就一直指数爆炸增长,直到阈值。(很像我们二分/滑动窗口呢)
第二阶段:拥塞避免
当窗口大到一定程度(比如到了 16设置的阈值),不能再翻倍了,太危险。改成线性增长比如每次加1来试探。
第三阶段:拥塞发生
突然发现丢包了,只要丢包,tcp就认为网络堵了。
接下来就会触发之前说过的超时重传或者快速重传。(看快速重传的ack够不够)
触发了超时重传就认为阻塞严重,直接全部重来,从慢启动开始。
触发了快速重传就认为只是轻微阻塞还有得救,接下来会把拥塞窗口减半进入快速恢复阶段。
第四阶段:快速恢复
在减半后就只采用线性增长慢慢尝试,比从零开始好。