在tcp协议中, 我们考虑下面几个问题:

  • 消息可以发送
  • 消息能解析出正确的发送数据
  • 消息确认到达
  • 尽量的压榨性能

按照惯例, 先放总结

总结

  1. ack机制

    • 这个类似的有kafka的消息确认机制, rabbitmq的确认机制.

    • 变种的有心跳监测机制

  2. 本地buffer批量发送

    • kafka本地先buffer然后批量发送

    • mysql设置buffer区域, 先写入buffer然后批量刷盘, 刷盘的触发机制有buffer满或者达到超时时间.

  3. 异常处理

    • tcp设置urg=1告诉server这条消息优先级比较高, 先处理.
  4. 数据校验

    • 将数据和校验位一起发送, 然后server确认数据是否正确, 类似于https的连接
  5. 序列号

    • 用有序数组来确认顺序还是添加序号确认顺序, 这是个问题.
  6. 随机序列号

    • 初始给出一个随机序列号防止被黑, 跟https中建立连接的过程有点关系

三次握手和四次挥手

三次握手

在tcp中client和server是平等的, 都接收和发送消息, 所以必须要确认都能收都能接

1
2
3
c: 我能发, 你能收么, 你看我打算从x这个初始序列号给你发消息
s: 我能收, 你看我也能发, 你看我知道你下次发的序列号开始是x+1, 我给你发消息从y开始
c: 我也能收, 我知道你打算从y+1开始给我发消息

感觉这里之所以x+1是为了编码实现的方便, 跟虚拟内存一样.

四次挥手

1
2
3
4
c: 我不打算发了, 我发的最后一个是$u$
s :哦, 我收到了, 给你确认是u+1
s: 我最后给你确认你发的是u+1, 我最后自己发的是v
c: 哦, 我收到你最后发的v+1了, 告辞

差错控制机制

分段

  • 由于受到网络最大传输单元的限制, tcp不可能一次就将所有的消息发送完成, 而需要将消息分段发出, tcp发送数据的基本单位是段.

  • tcp每段数据开头都带有一个标示该段数据的32位序号, 这里类似于数组的地址就是数组开头第一个元素的地址一样

确认应答

  • 确认应答采用滑动窗口的方式

    • 发送端的发送窗口大小 = min[拥塞窗口, 接收端公告的接收窗口]

    • client在发送完窗口大小的数据后, 等待接收端的确认消息.

  • 确认过程

    • server收到消息后可以逐个确认也可以批量确认, 然后给出确认序号是最后一个发送序号+1, 也就是告诉client下次接收消息的开始序号
    • 一般来说, 确认消息会捎带在server发送的数据中, 这样可以提高网络传输的效率.
    • ack返回有两个条件, 一个是跟着server的数据一起返回, 另一个是超时, 这个超时时间不能大于client重传定时器的超时时间
    • todo
      • 这里类似于本地一个buffer, 然后buffer满了就发送, 或者等待超时就发送
      • kafka先将消息buffer到本地,然后一起发送,这样可以提高效率.// 需不需要考虑本地消息丢失的问题
  • 错序确认

    • 如果因为网络抖动或者什么原因, 4001消息的确认在2001的前面, 那么server会理科向client发送确认应答, ack的value是正确的确认序号+1, 也就是2001. 这样就会产生很多2001的ack, client根据这个ack确认是否需要重发. server会将这些错序的消息buffer到本地, 直到收到正确的
  • 消息丢失

    • 消息丢失的话, 后面的消息会一直重复确认, 逐渐将重传定时器溢出, 然后重发

所以乱序和丢失的区别在于先是乱序的那个序号到达还是client的重传定时器溢出????

重传机制

  • client发送一个tcp报文, 会将这个报文暂存到缓冲区, 并设置一个buffer超时时间, 过了这个时间如果没有收到ack的话就会重传.
    • 问题的关键在于如何设置这个超时时间
  • client收到了server的多个重复的ack, 默认是3个, 那么就会重新发送消息
  • 重传的话到底发送哪条消息?
    • 2的话只发送丢的那条就ok
    • 1是因为超时或者网络不稳定什么的, 发送丢的那条和后面的全部消息

tcp的拥塞机制

慢启动

重传定时器溢出的时候向下调整

连续接收到多个重复确认应答的时候向下调整