面试题答案
一键面试设计思路
- 数据结构:
- 定义一个结构体来封装要发送的数据,例如包含数据内容字段以及用于确认的数据编号字段。
type Packet struct { DataID int Content []byte }
- 为了处理网络延迟和丢包,需要维护发送和接收的状态。在发送端,可使用一个 map 来记录已发送但未确认的数据包,键为 DataID,值为 Packet 结构体。在接收端,可使用一个切片来存储接收到但顺序不正确的数据包,等待正确顺序的数据到来后再一起处理。
- 发送逻辑:
- 发送端按顺序发送数据包,并启动一个定时器,每个数据包发送后开始计时。如果在规定时间内没有收到确认消息,重发该数据包。
- 发送函数如下:
func sendData(conn net.Conn, data []byte, sendChan chan struct{}) { var dataID int unacknowledged := make(map[int]Packet) for { select { case <-sendChan: packet := Packet{DataID: dataID, Content: data} _, err := conn.Write(encodePacket(packet)) if err != nil { log.Println("Send error:", err) return } unacknowledged[dataID] = packet dataID++ go func(id int) { timer := time.NewTimer(time.Second) defer timer.Stop() select { case <-timer.C: if _, ok := unacknowledged[id]; ok { log.Println("Resending packet:", id) _, err := conn.Write(encodePacket(unacknowledged[id])) if err != nil { log.Println("Resend error:", err) } } } }(dataID - 1) } } }
- 接收逻辑:
- 接收端持续读取数据,解析接收到的数据包。如果数据包编号正确,将数据传递给上层应用处理,并发送确认消息;如果编号不正确,将数据包暂存到切片中。当接收到正确编号的数据包时,检查暂存切片中是否有后续顺序的数据包,如有则一起处理。
- 接收函数如下:
func receiveData(conn net.Conn, receiveChan chan []byte) { expectedID := 0 outOfOrder := make([]Packet, 0) for { buffer := make([]byte, 1024) n, err := conn.Read(buffer) if err != nil { log.Println("Receive error:", err) return } packet := decodePacket(buffer[:n]) if packet.DataID == expectedID { receiveChan <- packet.Content expectedID++ for len(outOfOrder) > 0 && outOfOrder[0].DataID == expectedID { receiveChan <- outOfOrder[0].Content outOfOrder = outOfOrder[1:] expectedID++ } _, err := conn.Write(encodeAck(packet.DataID)) if err != nil { log.Println("Ack send error:", err) } } else { outOfOrder = append(outOfOrder, packet) } } }
- 优化策略:
- 批量发送:可以将多个小数据包合并成一个大数据包发送,减少网络传输次数,降低延迟。
- 滑动窗口:在发送端维护一个窗口,窗口内的数据包可以无需等待确认就发送,提高发送效率。同时,接收端也相应调整处理逻辑。
- 拥塞控制:根据网络拥塞情况动态调整发送速率,避免网络拥塞导致更多丢包。
核心示例代码
以下是完整的示例代码,包括数据包编码解码、确认消息处理等:
package main
import (
"encoding/binary"
"fmt"
"log"
"net"
"time"
)
type Packet struct {
DataID int
Content []byte
}
func encodePacket(packet Packet) []byte {
data := make([]byte, 4+len(packet.Content))
binary.BigEndian.PutUint32(data[:4], uint32(packet.DataID))
copy(data[4:], packet.Content)
return data
}
func decodePacket(data []byte) Packet {
dataID := int(binary.BigEndian.Uint32(data[:4]))
content := make([]byte, len(data)-4)
copy(content, data[4:])
return Packet{DataID: dataID, Content: content}
}
func encodeAck(dataID int) []byte {
ack := make([]byte, 4)
binary.BigEndian.PutUint32(ack, uint32(dataID))
return ack
}
func sendData(conn net.Conn, data []byte, sendChan chan struct{}) {
var dataID int
unacknowledged := make(map[int]Packet)
for {
select {
case <-sendChan:
packet := Packet{DataID: dataID, Content: data}
_, err := conn.Write(encodePacket(packet))
if err != nil {
log.Println("Send error:", err)
return
}
unacknowledged[dataID] = packet
dataID++
go func(id int) {
timer := time.NewTimer(time.Second)
defer timer.Stop()
select {
case <-timer.C:
if _, ok := unacknowledged[id]; ok {
log.Println("Resending packet:", id)
_, err := conn.Write(encodePacket(unacknowledged[id]))
if err != nil {
log.Println("Resend error:", err)
}
}
}
}(dataID - 1)
}
}
}
func receiveData(conn net.Conn, receiveChan chan []byte) {
expectedID := 0
outOfOrder := make([]Packet, 0)
for {
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
log.Println("Receive error:", err)
return
}
packet := decodePacket(buffer[:n])
if packet.DataID == expectedID {
receiveChan <- packet.Content
expectedID++
for len(outOfOrder) > 0 && outOfOrder[0].DataID == expectedID {
receiveChan <- outOfOrder[0].Content
outOfOrder = outOfOrder[1:]
expectedID++
}
_, err := conn.Write(encodeAck(packet.DataID))
if err != nil {
log.Println("Ack send error:", err)
}
} else {
outOfOrder = append(outOfOrder, packet)
}
}
}
func main() {
// 模拟客户端和服务器通信
go func() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer lis.Close()
conn, err := lis.Accept()
if err != nil {
log.Fatal(err)
}
defer conn.Close()
receiveChan := make(chan []byte)
go receiveData(conn, receiveChan)
for content := range receiveChan {
fmt.Println("Received:", string(content))
}
}()
time.Sleep(time.Second)
conn, err := net.Dial("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
sendChan := make(chan struct{})
go sendData(conn, []byte("Hello, World!"), sendChan)
for i := 0; i < 5; i++ {
sendChan <- struct{}{}
time.Sleep(time.Second)
}
close(sendChan)
time.Sleep(2 * time.Second)
}