对于那些
简要地
本文讨论了可靠数据传输的基础知识,并实现了以下示例
传输层协议
提供不同主机上运行的应用程序进程之间的逻辑连接。 从应用程序的角度来看,逻辑连接就像直接连接进程的通道。
这是通过将应用程序层消息分割成片段并向每个片段添加传输层标头来完成的。
然后,传输层将数据段传递到发送方的网络层,在发送方的网络层中,数据段被封装在网络层数据包(数据报)中并发送。 在接收端,网络层从数据报中提取传输层段并将其传递到传输层。 接下来,传输层处理接收到的段,以便其数据可供接收应用程序使用。
可靠数据传输原理
通过完全安全的通道进行可靠的数据传输
最简单的情况。 发送端简单地接收来自上层的数据,创建包含该数据的数据包,并将其发送到通道。
服务器
package main
import (
"log"
"net"
)
func main() {
// IP-адрес сервера и порт
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// создаем сокет с портом
serverConn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
// отложенное закрытие соединения
defer serverConn.Close()
// создаем буфер для данных
buf := make([]byte, 1024)
// ждем соединение
for {
// читаем запрос
n, addr, err := serverConn.ReadFromUDP(buf)
// передаем данные в ВЕРХНИЙ уровень: в нашем случае stdout
println(string(buf[0:n]), " form ", addr.IP.String())
if err != nil {
log.Fatal(err)
}
// ответа нет, т.к. это UDP + надежный канал
}
}
客户
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
// IP-адрес сервера и порт
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:12000")
if err != nil {
log.Fatal(err)
}
// локальный IP-адрес и порт
localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
log.Fatal(err)
}
// установка соединения
conn, err := net.DialUDP("udp", localAddr, serverAddr)
if err != nil {
log.Fatal(err)
}
// отложенное закрытие соединения
defer conn.Close()
for {
// получение данных от ВЕРХНЕГО уровня
fmt.Print("Введите строчное предложение > ")
var msg string
_, err := fmt.Scanf("%s", &msg)
if err != nil {
log.Fatal(err)
}
// передается поток байт, а не строка
buf := []byte(msg)
// запись (передача) в соединение
_, err = conn.Write(buf)
if err != nil {
log.Fatal(err)
}
// 1 секундочку
time.Sleep(time.Second * 1)
}
}
通过可能出现错误的通道进行可靠的数据传输
下一步是假设所有传输的数据包都按照发送的顺序接收,但由于信道有时会传输失真的数据,因此其中的位可能会被损坏。
在这种情况下,使用以下机制:
- 错误检测;
- 反馈;
- 重传。
具有类似的多次重复传输机制的可靠数据传输协议称为自动重复请求(ARQ)协议。
此外,当接收方不会收到有关最后一个数据包传输结果的任何信息时,值得考虑接收错误的可能性。
解决这个问题的方法也用在 TCP 中,就是在数据包中添加一个新字段,其中包含数据包的序列号。
在不可靠的通道上进行可靠的数据传输,容易出现数据包失真和丢失的情况
不幸的是,除了失真之外,网络中还存在数据包丢失。
而要解决这个问题,需要机制:
- 确定数据包丢失的事实;
- 将丢失的数据包重新传送给接收方。
此外,除了包裹丢失之外,还需要考虑收据丢失的可能性,或者如果没有丢失,则其交付可能会严重延迟。 在所有情况下,都会执行相同的操作:重新传输数据包。 为了控制时间,此机制使用倒计时器,它允许您确定等待间隔的结束时间。 所以在包里
// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
defaultTCPKeepAlive = 15 * time.Second
)
每次发送数据包时(第一次和第二次),发送方都需要启动一个计时器,处理计时器的中断并停止计时器。
因此,我们已经熟悉了可靠数据传输协议的关键概念:
- 校验和;
- 包裹的序列号;
- 定时器;
- 正收据和负收据。
但这还不是全部!
具有流水线功能的可靠数据传输协议
在我们已经考虑过的变体中,可靠的交付协议效率非常低。 随着 RTT 的增加,它开始“减慢”通信通道提供的传输速度。 为了提高其效率并更好地利用通信信道容量,使用了流水线。
流水线的使用导致:
- 增加序列号的范围,因为所有发送的数据包(重传除外)必须被唯一标识;
- 需要增加发送端和接收端的缓冲区。
序列号范围和缓冲区大小要求取决于协议为响应数据包损坏、丢失和延迟而采取的操作。 在流水线的情况下,有两种纠正错误的方法:
- 返回N个数据包;
- 选择性重复。
返回N个数据包——滑动窗口协议
发送者必须支持三种类型的事件:
- 由更高层协议调用。 当“从上面”调用数据发送函数时,发送方首先检查窗口的填充程度(即是否存在 N 个已发送消息等待收到回执)。 如果窗口为空,则生成并传输新的数据包,并更新变量值。 否则,发送方向上层返回数据,这是窗口已满的隐式指示。 通常上层会在一段时间后尝试再次传输数据。 在实际应用中,发送方可能会缓冲数据(而不是立即发送)或具有同步机制(例如信号量或标志),该机制允许上层仅在窗口为空时调用发送函数。
- 收到确认。 在该协议中,对于序列号为N的数据包,发出一般确认,表明序列号在N之前的所有数据包均已成功接收。
- 等待间隔已过。 为了确定数据包和接收的丢失和延迟的事实,该协议使用计时器。 如果超时时间到期,发送端将重新发送所有已发送的未确认数据包。
选择性重复
当窗口大小和吞吐量-传播延迟乘积很大时,管道中可能有大量数据包。 在这种情况下,单个数据包错误可能会导致大量数据包重传,而其中大部分是不需要的。
例子
顶部
服务器
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
)
func main() {
// создаем сокет с портом
ln, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln(err)
}
// ожидание вызова
conn, _ := ln.Accept()
for {
// считывание данных
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// вывод сообщения в stdout
fmt.Print("Message Received:", string(msg))
// перевод строки в верхний регистр
newMsg := strings.ToUpper(msg)
// отправка данных
conn.Write([]byte(newMsg + "n"))
}
}
客户
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
func main() {
// установка соединения
conn, err := net.Dial("tcp", "127.0.0.1:8081")
if err != nil {
log.Fatalln(err)
}
for {
// считывание данных с stdin
reader := bufio.NewReader(os.Stdin)
fmt.Print("Text to send: ")
// построчно
text, err := reader.ReadString('n')
if err != nil {
log.Fatalln(err)
}
// отправка
fmt.Fprintf(conn, text+"n")
// прием
msg, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
log.Fatalln(err)
}
// вывод полученного ответа
fmt.Print("Msg from Server: " + msg)
}
}
结论
确保可靠数据传输和使用的机制
Механизм
申请、评论
校验和
用于检测传输数据包中的位错误
计时器
倒计时超时间隔并指示超时时间。 后者意味着数据包或其接收在传输过程中丢失的可能性很高。 如果数据包延迟传送,但没有丢失(超时间隔过早到期),或者回执丢失,则重传会导致接收方出现重复的数据包
序号
用于对从发送方传输到接收方的数据包进行顺序编号。 接收到的数据包序列号中的间隙允许接收器检测数据包丢失。 相同的数据包序列号意味着数据包彼此重复
确认
由接收端生成,向发送端指示相应的数据包或数据包组已成功接收。 通常,确认包含成功接收的数据包的序列号。 根据协议的不同,区分个人确认和团体确认
否定确认
接收方用来通知发送方数据包接收不正确。 否定确认通常包括未正确接收的数据包的序列号
窗户、传送带
限制可用于传输数据包的序列号范围。 与等待确认相比,多播和握手可以显着提高协议吞吐量。 正如我们将看到的,窗口大小可以根据接收端的接收和缓冲能力以及网络负载水平来计算
使用 Go 进行网络的更多示例
В
来源: habr.com