ถึงผู้ที่
สั้น
บทความนี้กล่าวถึงพื้นฐานของการส่งข้อมูลที่เชื่อถือได้ ดำเนินการตามตัวอย่าง
โปรโตคอลชั้นการขนส่ง
ให้การเชื่อมต่อแบบลอจิคัลระหว่างกระบวนการแอปพลิเคชันที่ทำงานบนโฮสต์ที่แตกต่างกัน จากมุมมองของแอปพลิเคชัน การเชื่อมต่อแบบลอจิคัลดูเหมือนช่องทางที่เชื่อมต่อกระบวนการโดยตรง
ซึ่งทำได้โดยการแยก (หากจำเป็น) ข้อความในเลเยอร์แอปพลิเคชันออกเป็นส่วนๆ และเพิ่มส่วนหัวของเลเยอร์การขนส่งให้กับแต่ละข้อความ
เลเยอร์การขนส่งจะส่งผ่านส่วนไปยังเลเยอร์เครือข่ายของผู้ส่ง โดยที่ส่วนนั้นจะถูกห่อหุ้มในแพ็คเก็ตเลเยอร์เครือข่าย (ดาตาแกรม) และส่งไป ที่ส่วนรับปลายทาง เลเยอร์เครือข่ายจะแยกส่วนของเลเยอร์การขนส่งออกจากดาตาแกรมและส่งต่อไปยังเลเยอร์การขนส่ง ถัดไป เลเยอร์การขนส่งจะประมวลผลเซ็กเมนต์ที่ได้รับเพื่อให้ข้อมูลพร้อมใช้งานสำหรับแอปพลิเคชันที่รับ
หลักการส่งข้อมูลที่เชื่อถือได้
การส่งข้อมูลที่เชื่อถือได้ผ่านช่องทางที่ปลอดภัยอย่างสมบูรณ์
กรณีที่ง่ายที่สุด ฝ่ายส่งเพียงแค่รับข้อมูลจากชั้นบน สร้างแพ็กเก็ตที่บรรจุข้อมูลนั้น และส่งไปยังช่องสัญญาณ
เซิร์ฟเวอร์
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)
}
}
การส่งข้อมูลที่เชื่อถือได้ผ่านช่องทางที่มีข้อผิดพลาดที่อาจเกิดขึ้น
ขั้นตอนต่อไปคือการสมมติว่าได้รับแพ็กเก็ตที่ส่งทั้งหมดตามลำดับที่ถูกส่ง แต่บิตในนั้นอาจเสียหายเนื่องจากบางครั้งช่องสัญญาณส่งข้อมูลด้วยการบิดเบือน
ในกรณีนี้จะใช้กลไกต่อไปนี้:
- การตรวจจับข้อผิดพลาด
- ข้อเสนอแนะ;
- การส่งสัญญาณซ้ำ
โปรโตคอลการถ่ายโอนข้อมูลที่เชื่อถือได้ซึ่งมีกลไกที่คล้ายกันสำหรับการส่งซ้ำหลายครั้งเรียกว่าโปรโตคอล Automatic Repeat reQuest (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 สำหรับเครือข่าย
В
ที่มา: will.com