Files
netstack/tcpip/transport/tcp/snd.go

151 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package tcp
import (
"netstack/logger"
"netstack/tcpip"
"netstack/tcpip/buffer"
"netstack/tcpip/seqnum"
"time"
)
// NOTE 这里实现了tcp的拥塞控制 很重要
// congestionControl is an interface that must be implemented by any supported
// congestion control algorithm.
// tcp拥塞控制拥塞控制算法的接口
type congestionControl interface {
// HandleNDupAcks is invoked when sender.dupAckCount >= nDupAckThreshold
// just before entering fast retransmit.
// 在进入快速重新传输之前,当 sender.dupAckCount> = nDupAckThreshold 时调用HandleNDupAcks。
HandleNDupAcks()
// HandleRTOExpired is invoked when the retransmit timer expires.
// 当重新传输计时器到期时调用HandleRTOExpired。
HandleRTOExpired()
// Update is invoked when processing inbound acks. It's passed the
// number of packet's that were acked by the most recent cumulative
// acknowledgement.
// 已经有数据包被确认时调用 Update。它传递了最近累积确认所确认的数据包数。
Update(packetsAcked int)
// PostRecovery is invoked when the sender is exiting a fast retransmit/
// recovery phase. This provides congestion control algorithms a way
// to adjust their state when exiting recovery.
// 当发送方退出快速重新传输/恢复阶段时将调用PostRecovery。
// 这为拥塞控制算法提供了一种在退出恢复时调整其状态的方法。
PostRecovery()
}
// tcp发送器它维护了tcp必要的状态
type sender struct {
ep *endpoint
// lastSendTime is the timestamp when the last packet was sent.
// lastSendTime 是发送最后一个数据包的时间戳。
lastSendTime time.Time
// sndNxt 是要发送的下一个段的序列号。
sndNxt seqnum.Value
// sndNxtList is the sequence number of the next segment to be added to
// the send list.
// sndNxtList 是要添加到发送列表的下一个段的序列号。
sndNxtList seqnum.Value
// maxSentAck is the maxium acknowledgement actually sent.
maxSentAck seqnum.Value
closed bool
writeNext *segment
// 发送链表
writeList segmentList
// cc is the congestion control algorithm in use for this sender.
// cc 是实现拥塞控制算法的接口
cc congestionControl
}
// 新建并初始化发送器 irs是cookies
func newSender(ep *endpoint, iss, irs seqnum.Value, sndWnd seqnum.Size, mss uint16, sndWndScale int) *sender {
s := &sender{
ep: ep,
sndNxt: iss + 1,
maxSentAck: irs + 1,
}
return s
}
func (s *sender) sendAck() {
s.sendSegment(buffer.VectorisedView{}, flagAck, s.sndNxt) // seq = cookies+1 ack ack|fin.seq+1
logger.TODO("发送字节序")
}
// sendSegment sends a new segment containing the given payload, flags and
// sequence number.
// 根据给定的参数负载数据、flags标记和序列号来发送数据
func (s *sender) sendSegment(data buffer.VectorisedView, flags byte, seq seqnum.Value) *tcpip.Error {
s.lastSendTime = time.Now()
//if seq == s.rttMeasureSeqNum {
// s.rttMeasureTime = s.lastSendTime
//}
rcvNxt, rcvWnd := s.ep.rcv.getSendParams()
// Remember the max sent ack.
s.maxSentAck = rcvNxt
return s.ep.sendRaw(data, flags, seq, rcvNxt, rcvWnd)
}
// 收到段时调用 handleRcvdSegment 它负责更新与发送相关的状态
func (s *sender) handleRcvdSegment(seg *segment) {
// 现在某些待处理数据已被确认或者窗口打开或者由于快速恢复期间出现重复的ack而导致拥塞窗口膨胀
// 因此发送更多数据。如果需要,这也将重新启用重传计时器。
s.sendData()
}
// 发送数据段,最终调用 sendSegment 来发送
func (s *sender) sendData() {
//log.Println(unsafe.Pointer(s.ep), "怎么又调用了一次")
var seg *segment
// 遍历发送链表,发送数据
// tcp拥塞控制s.outstanding < s.sndCwnd 判断正在发送的数据量不能超过拥塞窗口。
for seg = s.writeNext; seg != nil; /*&& s.outstanding < s.sndCwnd*/ seg = seg.Next() {
// 如果seg的flags是0将flags改为psh|ack
if seg.flags == 0 {
seg.sequenceNumber = s.sndNxt
seg.flags = flagAck | flagPsh
}
var segEnd seqnum.Value
if seg.data.Size() == 0 { // 数据段没有负载,表示要结束连接
if s.writeList.Back() != seg {
panic("FIN segments must be the final segment in the write list.")
}
// 发送 fin 报文
seg.flags = flagAck | flagFin
// fin 报文需要确认,且消耗一个字节序列号
segEnd = seg.sequenceNumber.Add(1)
} else {
// We're sending a non-FIN segment.
if seg.flags&flagFin != 0 {
panic("Netstack queues FIN segments without data.")
}
logger.TODO("发送正常的数据, 需要流量控制")
}
s.sendSegment(seg.data, seg.flags, seg.sequenceNumber)
// 发送一个数据段后更新sndNxt
if s.sndNxt.LessThan(segEnd) {
s.sndNxt = segEnd
}
}
// Remember the next segment we'll write.
s.writeNext = seg
// TODO 启动定时器
}