Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Cho những người tìm kiếm Dành riêng cho việc hiểu mạng và giao thức.

Ngắn gọn

Bài viết thảo luận những vấn đề cơ bản về truyền dữ liệu đáng tin cậy, triển khai các ví dụ về Go, bao gồm UDP và TCP. Dựa trên thời gian, два, ba và cuốn sách "Mạng máy tính. Cách tiếp cận từ trên xuống", nếu không thì mọi người chỉ thảo luận về Tannenbaum và Oliferov.

Giao thức lớp vận chuyển

Cung cấp kết nối logic giữa các tiến trình ứng dụng chạy trên các máy chủ khác nhau. Từ góc độ ứng dụng, kết nối logic trông giống như một kênh kết nối trực tiếp các quy trình.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Giao thức lớp vận chuyển được hỗ trợ bởi các hệ thống đầu cuối, nhưng không được hỗ trợ bởi các bộ định tuyến mạng (ngoại trừ - Sở KH & ĐT). Về phía người gửi, lớp vận chuyển chuyển đổi dữ liệu của lớp ứng dụng mà nó nhận được từ quá trình ứng dụng gửi thành các gói của lớp vận chuyển được gọi là các phân đoạn.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Điều này được thực hiện bằng cách chia (nếu cần) các thông báo của lớp ứng dụng thành các đoạn và thêm tiêu đề của lớp vận chuyển cho mỗi đoạn đó.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Sau đó, lớp vận chuyển sẽ chuyển phân đoạn này đến lớp mạng của người gửi, tại đó phân đoạn này được gói gọn trong một gói lớp mạng (datagram) và được gửi đi. Ở đầu nhận, lớp mạng trích xuất phân đoạn lớp vận chuyển từ datagram và chuyển nó lên lớp vận chuyển. Tiếp theo, lớp vận chuyển xử lý phân đoạn nhận được để dữ liệu của nó có sẵn cho ứng dụng nhận.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Nguyên tắc truyền dữ liệu đáng tin cậy

Truyền dữ liệu đáng tin cậy qua kênh hoàn toàn an toàn

Trường hợp đơn giản nhất. Bên gửi chỉ cần nhận dữ liệu từ lớp trên, tạo gói chứa dữ liệu đó và gửi đến kênh.

Máy chủ

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 + надежный канал
    }
}

Khách hàng

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)
    }
}

Truyền dữ liệu đáng tin cậy qua một kênh có thể xảy ra lỗi

Bước tiếp theo là giả định rằng tất cả các gói được truyền đều được nhận theo thứ tự chúng được gửi, nhưng các bit trong chúng có thể bị hỏng do kênh đôi khi truyền dữ liệu bị biến dạng.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Trong trường hợp này, các cơ chế sau được sử dụng:

  • phát hiện lỗi;
  • nhận xét;
  • truyền lại.

Các giao thức truyền dữ liệu đáng tin cậy có cơ chế lặp lại truyền nhiều lần tương tự được gọi là giao thức Yêu cầu lặp lại tự động (ARQ).
Ngoài ra, cần xem xét khả năng xảy ra lỗi trong biên nhận khi bên nhận sẽ không nhận được bất kỳ thông tin nào về kết quả chuyển gói cuối cùng.
Giải pháp cho vấn đề này, cũng được sử dụng trong TCP, là thêm một trường mới vào gói dữ liệu chứa số thứ tự của gói.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Truyền dữ liệu đáng tin cậy qua kênh không đáng tin cậy có thể bị biến dạng và mất gói

Cùng với sự biến dạng, thật không may, có hiện tượng mất gói trong mạng.
Và để giải quyết vấn đề này cần có những cơ chế:

  • xác định thực tế mất gói;
  • giao lại các gói tin bị mất cho bên nhận.

Ngoài ra, ngoài việc mất gói hàng, cần phải tính đến khả năng mất biên lai hoặc nếu không có gì bị mất thì việc giao hàng sẽ bị chậm trễ đáng kể. Trong mọi trường hợp, điều tương tự cũng được thực hiện: gói được truyền lại. Để kiểm soát thời gian, cơ chế này sử dụng đồng hồ đếm ngược, cho phép bạn xác định thời điểm kết thúc khoảng thời gian chờ. Vì vậy trong gói net TCPKeepAlive được đặt thành 15 giây theo mặc định:

// defaultTCPKeepAlive is a default constant value for TCPKeepAlive times
// See golang.org/issue/31510
const (
    defaultTCPKeepAlive = 15 * time.Second
)

Bên gửi cần khởi động bộ hẹn giờ mỗi khi gói được truyền (cả lần đầu tiên và lần thứ hai), xử lý các gián đoạn từ bộ hẹn giờ và dừng nó.

Vì vậy, chúng ta đã làm quen với các khái niệm chính của các giao thức truyền dữ liệu đáng tin cậy:

  • tổng kiểm tra;
  • số thứ tự của gói hàng;
  • bộ tính giờ;
  • thu nhập tích cực và tiêu cực.

Nhưng đó không phải là tất cả!

Giao thức truyền dữ liệu đáng tin cậy với đường ống

Trong biến thể mà chúng tôi đã xem xét, giao thức phân phối đáng tin cậy rất kém hiệu quả. Nó bắt đầu “làm chậm” quá trình truyền do kênh liên lạc cung cấp khi RTT tăng lên. Để tăng hiệu quả và tận dụng tốt hơn dung lượng kênh liên lạc, đường ống được sử dụng.

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Việc sử dụng đường ống dẫn đến:

  • tăng phạm vi số thứ tự, vì tất cả các gói được gửi (ngoại trừ các gói truyền lại) phải được xác định duy nhất;
  • sự cần thiết phải tăng bộ đệm ở phía truyền và phía nhận.

Các yêu cầu về phạm vi số thứ tự và kích thước bộ đệm phụ thuộc vào các hành động mà giao thức thực hiện để ứng phó với sự cố, mất gói và độ trễ gói. Trong trường hợp pipeline, có hai phương pháp sửa lỗi:

  • trả lại N gói;
  • sự lặp lại có chọn lọc.

Quay lại N gói - giao thức cửa sổ trượt

Nguyên tắc cơ bản của việc truyền dữ liệu đáng tin cậy

Người gửi phải hỗ trợ ba loại sự kiện:

  • gọi bằng giao thức cấp cao hơn. Khi chức năng gửi dữ liệu được gọi là “từ phía trên”, trước tiên bên gửi sẽ kiểm tra mức độ lấp đầy của cửa sổ (nghĩa là có N tin nhắn đã gửi đang chờ nhận). Nếu cửa sổ trống, một gói mới sẽ được tạo và truyền đi, đồng thời các giá trị biến sẽ được cập nhật. Ngược lại, phía gửi sẽ trả dữ liệu về lớp trên và đây là dấu hiệu ngầm cho thấy cửa sổ đã đầy. Thông thường, lớp trên sẽ cố gắng truyền lại dữ liệu sau một thời gian. Trong một ứng dụng thực tế, người gửi có thể sẽ đệm dữ liệu (thay vì gửi ngay lập tức) hoặc có cơ chế đồng bộ hóa (chẳng hạn như đèn hiệu hoặc cờ) cho phép lớp trên chỉ gọi chức năng gửi khi cửa sổ trống .
  • nhận được xác nhận. Trong giao thức, đối với gói có số thứ tự N, một xác nhận chung được đưa ra cho biết rằng tất cả các gói có số thứ tự trước N đã được nhận thành công.
  • khoảng thời gian chờ đợi đã hết. Để xác định thực tế về sự mất mát và độ trễ của các gói và biên nhận, giao thức sử dụng bộ đếm thời gian. Nếu hết thời gian chờ, bên gửi sẽ gửi lại tất cả các gói đã gửi chưa được xác nhận.

Sự lặp lại có chọn lọc

Khi kích thước cửa sổ và tích độ trễ truyền thông lượng lớn, một số lượng lớn các gói có thể được đưa vào đường ống. Trong trường hợp như vậy, một lỗi gói đơn lẻ có thể khiến một số lượng lớn các gói được truyền lại, hầu hết trong số đó là không cần thiết.

Ví dụ

Lên trên lý thuyết thực tiễn được thu thập trong việc thực hiện thực tế TCP. Và nếu ai đó biết cách tốt nhất - chào mừng.

Máy chủ

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"))
    }
}

Khách hàng

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)
    }
}

Đầu ra

Cơ chế đảm bảo truyền và sử dụng dữ liệu đáng tin cậy

Механизм
Ứng dụng, bình luận

Kiểm tra tổng
Được sử dụng để phát hiện lỗi bit trong gói được truyền

Bộ hẹn giờ
Đếm ngược khoảng thời gian chờ và cho biết khi nào nó đã hết hạn. Điều sau có nghĩa là gói tin hoặc gói nhận được có khả năng cao bị mất trong quá trình truyền. Nếu một gói được gửi bị trễ nhưng không bị mất (hết hạn sớm trước khoảng thời gian chờ) hoặc biên nhận bị mất, việc truyền lại sẽ dẫn đến gói trùng lặp ở bên nhận

Số seri
Được sử dụng để đánh số thứ tự các gói dữ liệu được truyền từ người gửi đến người nhận. Khoảng trống trong số thứ tự của các gói nhận được cho phép người nhận phát hiện mất gói. Số thứ tự gói giống nhau có nghĩa là các gói trùng lặp với nhau

Xác nhận
Được tạo bởi đầu nhận và thông báo cho đầu gửi rằng gói hoặc nhóm gói tương ứng đã được nhận thành công. Thông thường, xác nhận chứa số thứ tự của các gói được nhận thành công. Tùy thuộc vào giao thức, xác nhận cá nhân và xác nhận nhóm được phân biệt

Xác nhận tiêu cực
Được người nhận sử dụng để thông báo cho người gửi rằng gói được nhận không chính xác. Một xác nhận phủ định thường bao gồm số thứ tự của gói không được nhận chính xác

Cửa sổ, băng tải
Giới hạn phạm vi số thứ tự có thể được sử dụng để truyền gói. Multicast và bắt tay có thể tăng đáng kể thông lượng giao thức so với việc chờ xác nhận. Như chúng ta sẽ thấy, kích thước cửa sổ có thể được tính toán dựa trên khả năng tiếp nhận và lưu vào bộ đệm của đầu nhận cũng như mức tải mạng.

Các ví dụ khác về việc sử dụng Go để kết nối mạng

В kho lưu trữ.

Nguồn: www.habr.com

Thêm một lời nhận xét