對於那些
簡要地
本文討論了可靠數據傳輸的基礎知識,並實現了以下示例
傳輸層協議
提供不同主機上運行的應用程序進程之間的邏輯連接。 從應用程序的角度來看,邏輯連接就像是直接連接進程的通道。
這是通過將應用程序層消息分割成片段並向每個片段添加傳輸層標頭來完成的。
然後傳輸層將數據段傳遞到發送方的網絡層,在網絡層中該數據段被封裝在網絡層數據包(數據報)中並發送出去。 在接收端,網絡層從數據報中提取傳輸層段並將其傳遞到傳輸層。 接下來,傳輸層處理接收到的段,以便其數據可供接收應用程序使用。
可靠數據傳輸原理
通過完全可靠的通道進行可靠的數據傳輸
最簡單的情況。 發送方只需從頂層接收數據,創建包含該數據的數據包,然後將其發送到通道。
服務器
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 進行網絡的其他示例
В
來源: www.habr.com