พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

ถึงผู้ที่ แสวงหา ทุ่มเทเพื่อทำความเข้าใจเครือข่ายและโปรโตคอล

สั้น

บทความนี้กล่าวถึงพื้นฐานของการส่งข้อมูลที่เชื่อถือได้ ดำเนินการตามตัวอย่าง Goรวมถึง UDP และ TCP ขึ้นอยู่กับ เวลา, สอง, สาม และหนังสือ "เครือข่ายคอมพิวเตอร์ วิธีจากบนลงล่าง" มิฉะนั้นทุกคนจะพูดถึงเฉพาะ Tannenbaum และ Oliferov เท่านั้น

โปรโตคอลชั้นการขนส่ง

ให้การเชื่อมต่อแบบลอจิคัลระหว่างกระบวนการแอปพลิเคชันที่ทำงานบนโฮสต์ที่แตกต่างกัน จากมุมมองของแอปพลิเคชัน การเชื่อมต่อแบบลอจิคัลดูเหมือนช่องทางที่เชื่อมต่อกระบวนการโดยตรง

พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

โปรโตคอลชั้นการขนส่ง ได้รับการสนับสนุนจากระบบปลายทาง แต่ไม่ใช่โดยเราเตอร์เครือข่าย (ยกเว้น - DPI). ในด้านผู้ส่ง เลเยอร์การขนส่งจะแปลงข้อมูลเลเยอร์แอปพลิเคชันที่ได้รับจากกระบวนการแอปพลิเคชันที่ส่งไปเป็นแพ็กเก็ตเลเยอร์การขนส่งที่เรียกว่าเซ็กเมนต์

พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

ซึ่งทำได้โดยการแยก (หากจำเป็น) ข้อความในเลเยอร์แอปพลิเคชันออกเป็นส่วนๆ และเพิ่มส่วนหัวของเลเยอร์การขนส่งให้กับแต่ละข้อความ

พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

เลเยอร์การขนส่งจะส่งผ่านส่วนไปยังเลเยอร์เครือข่ายของผู้ส่ง โดยที่ส่วนนั้นจะถูกห่อหุ้มในแพ็คเก็ตเลเยอร์เครือข่าย (ดาตาแกรม) และส่งไป ที่ส่วนรับปลายทาง เลเยอร์เครือข่ายจะแยกส่วนของเลเยอร์การขนส่งออกจากดาตาแกรมและส่งต่อไปยังเลเยอร์การขนส่ง ถัดไป เลเยอร์การขนส่งจะประมวลผลเซ็กเมนต์ที่ได้รับเพื่อให้ข้อมูลพร้อมใช้งานสำหรับแอปพลิเคชันที่รับ

พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

หลักการส่งข้อมูลที่เชื่อถือได้

การส่งข้อมูลที่เชื่อถือได้ผ่านช่องทางที่ปลอดภัยอย่างสมบูรณ์

กรณีที่ง่ายที่สุด ฝ่ายส่งเพียงแค่รับข้อมูลจากชั้นบน สร้างแพ็กเก็ตที่บรรจุข้อมูลนั้น และส่งไปยังช่องสัญญาณ

เซิร์ฟเวอร์

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 เช่นกัน คือการเพิ่มฟิลด์ใหม่ให้กับแพ็กเก็ตข้อมูลที่มีหมายเลขลำดับของแพ็กเก็ต

พื้นฐานของการถ่ายโอนข้อมูลที่เชื่อถือได้

การส่งข้อมูลที่เชื่อถือได้ผ่านช่องทางที่ไม่น่าเชื่อถืออาจมีการบิดเบือนและสูญหายของแพ็กเก็ต

นอกจากความบิดเบี้ยวแล้ว โชคไม่ดีที่แพ็กเก็ตสูญหายในเครือข่ายด้วย
และเพื่อแก้ไขปัญหานี้ จำเป็นต้องมีกลไก:

  • การพิจารณาข้อเท็จจริงของการสูญเสียแพ็กเก็ต
  • การส่งแพ็กเก็ตที่สูญหายอีกครั้งไปยังฝ่ายรับ

นอกจากนี้ นอกเหนือจากการสูญหายของบรรจุภัณฑ์แล้ว ยังจำเป็นต้องจัดเตรียมความเป็นไปได้ที่จะสูญเสียใบเสร็จรับเงิน หรือหากไม่มีสิ่งใดสูญหาย การส่งมอบจะมีความล่าช้าอย่างมาก ในทุกกรณี สิ่งเดียวกันจะเสร็จสิ้น: แพ็กเก็ตจะถูกส่งอีกครั้ง เพื่อควบคุมเวลา กลไกนี้ใช้ตัวจับเวลาถอยหลัง ซึ่งช่วยให้คุณกำหนดจุดสิ้นสุดของช่วงเวลารอคอยได้ ดังนั้นในแพ็คเกจ สุทธิ TCPKeepAlive ถูกตั้งค่าเป็น 15 วินาทีตามค่าเริ่มต้น:

// 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 สำเร็จแล้ว
  • ช่วงเวลารอคอยสิ้นสุดลงแล้ว เพื่อระบุข้อเท็จจริงของการสูญเสียและความล่าช้าของแพ็กเก็ตและการรับ โปรโตคอลจะใช้ตัวจับเวลา หากช่วงเวลาการหมดเวลาหมดลง ฝ่ายส่งจะส่งแพ็กเก็ตที่ไม่ได้รับการตอบรับที่ส่งทั้งหมดอีกครั้ง

การทำซ้ำแบบเลือกสรร

เมื่อขนาดหน้าต่างและผลิตภัณฑ์ล่าช้าในการแพร่กระจายปริมาณงานมีขนาดใหญ่ แพ็กเก็ตจำนวนมากอาจอยู่ในไปป์ไลน์ ในกรณีเช่นนี้ ข้อผิดพลาดของแพ็กเก็ตเดียวอาจทำให้มีการส่งแพ็กเก็ตจำนวนมากอีกครั้ง ซึ่งส่วนใหญ่ไม่จำเป็น

ตัวอย่าง

ด้านบน ตามทฤษฎี รวบรวมวิธีปฏิบัติในการนำไปปฏิบัติจริง TCP. และถ้าใครรู้วิธีที่ดีที่สุด - ยินดีต้อนรับ.

เซิร์ฟเวอร์

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

เพิ่มความคิดเห็น