网络与部署课后实践(一)
Q&A
TCP/IP模型中的数据包拆包和封包过程涉及数据在不同层级的封装和解封装:
- 应用层(Application Layer) : 在应用层,数据被封装为应用层协议的消息或报文。这可以是HTTP请求、SMTP邮件、FTP文件传输等。数据被包含在应用层协议的数据字段中。
- 传输层(Transport Layer) : 在传输层,应用层的数据被封装为传输层的段(Segment)。每个段包括源端口和目标端口,以及用于控制拥塞和数据流的一些信息。传输层还为数据包提供了序列号、确认号等用于可靠传输的信息。
- 网络层(Network Layer) : 在网络层,传输层的段被封装为网络层的数据报(Datagram)。数据报包括源IP地址和目标IP地址,以及用于路由和分片(如果需要)的相关信息。网络层使用IP协议来管理数据包的传输。
- 链路层(Link Layer) : 在链路层,网络层的数据报被封装为帧(Frame)。帧包含源MAC地址和目标MAC地址,以及用于错误检测和流控制的一些字段。链路层负责在物理介质上传输数据包。
- 物理层(Physical Layer) : 物理层负责在物理媒介上传输链路层的帧。这可能涉及到电压、光信号等,具体取决于物理媒介的类型。
TCP 的拥塞算法有哪些?
- 慢启动(Slow Start) : 在开始传输数据时,TCP以指数级增加发送窗口(拥塞窗口),从而逐渐增加发送速率。它会一直持续到发送窗口大小达到某个阈值(慢启动阈值)或者遇到拥塞情况。慢启动可以快速利用可用带宽,但在网络拥塞时可能会引发拥塞。
- 拥塞避免(Congestion Avoidance) : 一旦发送窗口达到慢启动阈值,TCP切换到拥塞避免模式。在拥塞避免中,发送窗口线性增长,而不是指数增长,从而减缓增长速率,以防止过度拥塞。拥塞避免算法通过线性增加发送窗口,逐渐逼近网络的承载能力。
- 快重传(Fast Retransmit) : 当接收端收到一个失序的数据段(不是当前期望的数据段),它会立即发送一个重复ACK,告诉发送端已经收到了失序的数据。发送端在收到一定数量的重复ACK后(通常是3个),会认为某个数据段丢失,直接重传该数据段,而不需要等待超时。
- 快恢复(Fast Recovery) : 在快重传的基础上,TCP还会进行快恢复操作,该操作通过减半慢启动阈值和发送窗口,然后继续线性增加发送窗口,而不是退回到慢启动。这样可以保持发送速率在一定水平,而不会因为单个丢失的数据段导致大幅度的减速。
- 超时重传(Timeout-based Retransmission) : 如果发送端未收到确认的数据包,并且超过了超时时间,那么它会假设该数据包已经丢失,然后进行重传。这是一种基于时间的拥塞控制方法,用于恢复丢失的数据包。
课后作业
UDP socket 实现 ack,感知丢包重传
服务器端代码:
package main
import (
"fmt"
"net"
)
func main() {
serverAddr, _ := net.ResolveUDPAddr("udp", "localhost:12345")
conn, _ := net.ListenUDP("udp", serverAddr)
defer conn.Close()
fmt.Println("Server started. Listening for packets...")
for {
buf := make([]byte, 1024)
n, addr, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("Error reading packet:", err)
continue
}
message := string(buf[:n])
fmt.Println("Received:", message)
ack := fmt.Sprintf("ACK %s", message)
conn.WriteToUDP([]byte(ack), addr)
}
}
客户端代码:
package main
import (
"fmt"
"net"
)
func main() {
serverAddr, _ := net.ResolveUDPAddr("udp", "localhost:12345")
conn, _ := net.DialUDP("udp", nil, serverAddr)
defer conn.Close()
seqNumber := 0
buf := make([]byte, 1024)
for {
message := fmt.Sprintf("Packet %d", seqNumber)
conn.Write([]byte(message))
n, _, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("Error reading ACK:", err)
continue
}
receivedAck := string(buf[:n])
fmt.Println("Received ACK:", receivedAck)
seqNumber++
}
}
客户端使用UDP套接字向服务器发送数据包,并等待服务器的ACK确认。
如果在一定的超时时间内未收到ACK,客户端会重新发送相同的数据包。
这样的机制可以模拟丢包重传,从而实现可靠性传输。
什么时候客户端认为是丢包?
在UDP通信中,客户端可以通过等待超时来判断是否发生了丢包。如果客户端在一定时间内未收到服务器端的ACK确认,就可以假设数据包丢失,然后触发重传机制。
conn.SetReadDeadline(time.Now().Add(time.Second * 2))
将超时时间设置为2秒,即客户端等待2秒来接收服务器的ACK确认。如果在2秒内没有收到ACK,客户端会认为数据包丢失,然后触发重传。
重传怎么考虑效率?
- 自适应超时时间: 使用自适应的超时时间,根据网络延迟和拥塞情况来调整超时时间。如果网络延迟较大,超时时间可以相应延长,以免过早触发不必要的重传。一种常见的方法是使用加权平均往返时间(RTT)作为超时时间的参考。
- 选择性重传: 不一定每个丢失的数据包都需要立即重传,特别是在数据包较大或网络稳定的情况下。选择性重传只重传丢失的数据包,而不是重传整个窗口的数据。这可以减少不必要的重传开销。
- 快速重传和快速恢复: 使用快速重传和快速恢复机制可以在接收到重复的ACK时立即进行重传,而不需要等待超时。这可以有效减少重传的延迟,并减少拥塞窗口的缩小。
阻塞只穿丢掉的中间的段
- 使用超时机制: 在发送每个数据包时,设置一个适度的超时时间。如果在超时时间内未收到ACK确认,你可以触发重传。这样可以避免阻塞等待ACK,但需要在发送多个数据包后检查超时。
- 多线程或异步处理: 使用多线程或异步处理来同时发送数据包并等待ACK。这样,即使一个数据包丢失,其他数据包也可以继续发送,而不会阻塞。你可以在后台线程中检查ACK确认情况,并触发丢失数据包的重传。
- 使用非阻塞套接字: 在Go语言中,你可以使用
select语句来实现非阻塞的套接字操作。通过监听多个通道,可以同时等待数据包发送和ACK确认,从而实现非阻塞的重传机制。