type
status
date
slug
summary
tags
category
icon
password

任务目标:sender

TCP sender,负责读取byte stream中由上层应用写入的东西,并且将stream转变为TCP segments
TCP receiver会把这些segments转换回stream,并且将ack和window size发送回sender

TCP sender的任务

  • Keep track of the receiver’s window (receiving incoming TCPReceiverMessages with their acknos and window sizes)
  • 每次尽量填满window,除非stream里面没有更多内容了
    • Fill the window when possible, by reading from the ByteStream, creating new TCP segments (including SYN and FIN flags if needed), and sending them. The sender should keep sending segments until either the window is full or the outbound ByteStream has nothing more to send.
  • 记录发送了但没有ack的segment
    • Keep track of which segments have been sent but not yet acknowledged by the receiver—we call these “outstanding” segments
  • overtime之后重新发送segment
    • Re-send outstanding segments if enough time passes since they were sent, and they haven’t been acknowledged yet
🌊
“automatic repeat request” (ARQ)
The basic principle is to send whatever the receiver will allow us to send (filling the window), and keep retransmitting until the receiver acknowledges each segment.

How does the TCPSender know if a segment was lost?

调用sender的tick方法来计算时间。Periodically, the owner of the TCPSender will call the TCPSender’s tick method, indicating the passage of time.
TCP sender为每个outstanding segment计时,如果经过的时间太长,就把最老的一个重传

具体细节:

  1. 每过几毫秒,tick方法就会被调用,有参数会告诉它距离上次调用过了多少毫秒。不能调用任何系统函数,比如time或者clock来实现
  1. TCPsender的构造函数需要一个参数:初始retransmission timeout(RTO),用于告知重传segment之前需要等待的时间,RTO会改变(根据采样每次计算time interval),但是初始RTO并不会变,The starter code saves the “initial value” of the RTO in a member variable called initial_RTO_ms_ .
  1. retransmission timer:一个警报,一旦RTO过期,警报就会响起,但是时间过去的概念一定只来自于tick方法
  1. 每发送一个包含非空data的segment,都要让timer开始运行
  1. 当所有的outstanding segment都被ack后,停止retransmission timer
    1. notion image
  1. 如果tick被调用并且retransmission timer过期
    1. 重传最早的一个没被ack的data
    2. 如果window size非空
      1. 记录连续传输的retransmissions,并且实时更新,TCPconnection会根据这个来判断是否需要丢弃掉一些。Your TCPConnection will use this information to decide if the connection is hopeless (too many consecutive retransmissions in a row) and needs to be aborted
      2. 把RTO加倍,This is called “exponential backoff”—it slows down retransmissions on lousy networks to avoid further gumming up the works.
        1. 超时间隔加倍:用于拥塞控制,防止要重传的segment太多
    3. 重启timer
  1. 当receiver给sender一个新ackno时(absolute seqno,比之前都大),
    1. 将RTO变回initial RTO
    2. 如果sender还有outstanding data,就重启定时器,以便在RTO毫秒后重新发送数据
    3. 将连续重传数重置为0
notion image

Implementing the TCP sender

We’ve discussed the basic idea of what the TCP sender does (given an outgoing ByteStream, split it up into segments, send them to the receiver, and if they don’t get acknowledged soon enough, keep resending them). And we’ve discussed when to conclude that an outstanding segment was lost and needs to be resend.

接口理解

  1. void push( const TransmitFunction& transmit );
    1. 根据window size的大小,尽可能地传输更多的byte,as long as there are new bytes to be read and space available in the window. 直接调用transmit()函数来传输这些byte
      最大可传输的payload长度:TCPConfig::MAX_PAYLOAD_SIZE (1452 bytes)
      TCPSenderMessage::sequence_length():一个segment占用的总sequence number,SYN和FIN flag也要占一个seqno,也要算进window的空间
      🪟
      当window size为0时 sender要假装window size为1然后发送数据,以激起receiver发送回来一个ackno和当前window size,不然sender永远都不知道receiver的window size什么时候>0了,也就永远不会继续发信息
  1. void receive( const TCPReceiverMessage& msg );
    1. 接收从receiver发送回来的信息msg,传达最新的ackno和window size,TCPsender需要查看所有outstanding segment,移除所有已经ack了的(也就是所有seqno比ackno小的)
  1. void tick( uint64_t ms_since_last_tick, const TransmitFunction& transmit );
    1. 根据ms_since_last_tick来判断是否需要超时重传,调用transmit()函数来实现重传
  1. TCPSenderMessage make_empty_message() const;
    1. 发送一个长度为0的带seqno的TCPsenderMessage,用于当sender想从peer那里得到一些信息的时候。seqno应该是LastByteSent+1,如果接收方收到了,会返回一个ackno也就是这个下一个本来应该传输的byte的seqno
      接收方那边的代码:所以传空串,push num+=0,ackno就跟之前不变
      Note: a segment like this one, which occupies no sequence numbers, doesn’t need to be kept track of as “outstanding” and won’t ever be retransmitted
      notion image

FAQs and special cases

  • What should my TCPSender assume as the receiver’s window size before the receive method informs it otherwise? One.
  • If I send three individual segments containing “a,” “b,” and “c,” and they never get acknow-ledged, can I later retransmit them in one big segment that contains “abc”? Or do I have to retransmit each segment individually?
    • 只需要单纯重传
  • Should I store empty segments in my “outstanding” data structure and retransmit them when necessary? No—the only segments that should be tracked as outstanding, and possibly retransmit ted, are those that convey some data—i.e. that consume some length in sequence space. A segment that occupies no sequence numbers (no SYN, payload, or FIN) doesn’t need to be remembered or retransmitted

代码和思路

思路

push怎么写?

首先要记住这个图:
notion image
  • 关于何时发送数据
      1. 缓存区里面有数据要发送的时候
      1. 缓存区没有数据,但是想要单独发送一个SYN的时候
      1. 缓存区没有数据,但是想要单独发送一个FIN的时候,且这个时候writer必须已经关闭,不然一直等待缓存区读入数据
  • 关于要发送多少数据
    • notion image
      结合这个图就可以看出来,要构成一个segment的最大的数据量,就是这个rwnd减去还没到达发送方的数据数量
       
      也就是说,必须从缓存区里面读取数据直到缓存区空了或者rwnd被填满了
      注意:SYN和FIN也要占一个空间,因此在计算payload的大小的时候,如果这段信息有SYN,那么SYN是必须占一个位置的,如果信息有FIN,那么就要看缓存区里面的数据是不是读完了,如果读完了还有空余,就把FIN加入segment,如果数据都还没读完,就选择抛弃掉FIN,多加一位数据进segment
      也就是这段代码:当缓存区还有数据的时候,哪怕sequence_length刚好等于spare_room,也不能立马就把数据发出去,而是要考虑是不是因为FIN占了一个位置。当有机会进入这个判断的时候,就说明肯定没有FIN了
  • 还要考虑一个特殊情况,也就是当rwnd=0的时候
    • 本来一开始写的是,只要rwnd=0,就按check3的文档里面写的一样,传一个1byte的数据过去,如果整个reader已经is finish了,说明数据已经全部发送,就单独发送一个FIN;如果不是,就单独发送一个缓存区里面的字符,然后把它存在“发送了但是没有ack”的一个队列里面
      但实际上,如果一个已经因为rwnd=0发送过一个segment了,那这个segment的ack传回来之前,就不用再发这么一个含1byte的数据了,不然进入循环,缓存区里面所有的字符都会被因为这个pop掉
      应该采取超时重传的机制,如果这个数据丢了或者被抛弃了,就肯定会引发超时重传,到时候再重传一次这个数据,而不是从缓存区里面再pop一个发送
      因此引入has_trans_win0,用于记录在收到最新的ACK之后,是否已经发送过1byte的数据,发送了一个1byte数据之后将它置为true,收到更新的ack后再将它的值置为false
      并且当rwnd=0的时候丢包的话,不用将RTO加倍
  • 关于计算spare_room的一个雷点
    • 一开始我简单的用书上的写法采用LastSentByte和LastAckedByte来记录上一个已经发送的字符和上一个已经确定的字符
      然而,因为两个数都用的无符号整型,且初始值为0,导致没办法确定当LastAckedByte=0的时候,SYN到底有没有被ACK,这样会导致很多额外的代码和问题
      因此还是用NextByte2Sent和LastByteAcked吧,这里的LastByteAcked就是上次传过来的最新ack的值了,这样一来如果这个数是0,就说明SYN也没有被确认

tick到底是什么

实际上根本就没有时钟,这个程序的时间是测试样例自己定的,因此使用时钟只需要直到经过的时间=经过的时间+距离上次的时间就行了。然后经过时间>RTO了就自动重传
所以实际上tick只用处理超时重传、RTO加倍

代码

.hh
.cc
 
数组Computer Networking Notes
  • Giscus