Анализ проблемы слипшихся пакетов TCP и ее решение

интервью Java исходный код Netty TCP/IP

В последнее время я работаю над вещами, связанными с промежуточным программным обеспечением, поэтому я столкнулся с различными протоколами.Вообще говоря, существуют различные протоколы сетевой передачи, такие как TCP, UDP и HTTP.Поэтому арендодатель хочет начать с самого базовая проблема прилипания протокола TCP.Приступайте к работе и укрепляйте основу этой части компьютерной сети.

Краткое введение в протокол TCP

TCP — это протокол транспортного уровня, ориентированный на соединение.

Проще говоря, перед использованием протокола TCP необходимо установить TCP-соединение, которое мы часто называем трехсторонним рукопожатием. После завершения передачи данных установленное TCP-соединение необходимо разорвать, иначе возникнут непредсказуемые проблемы, приводящие к недоступности услуги.

Каждое соединение TCP является надежным соединением только с двумя конечными точками.

Соединение TCP является двухточечным со стороны сервера на сторону клиента, данные передаются по TCP без ошибок, повторов и потерь.

Связь по протоколу TCP является полнодуплексной.

Протокол TCP позволяет приложениям на обеих сторонах связи отправлять данные в любое время. Оба конца соединения TCP снабжены буферами отправки и буферами приема, которые используются для временного хранения данных для двунаправленной связи. При отправке данных, после того как приложение передает данные в буфер TCP, оно может заниматься своими делами, и TCP отправляет данные в нужное время. При приеме TCP помещает принятые данные в приемный буфер, и приложение верхнего уровня считывает данные в соответствующее время.

Протокол TCP ориентирован на поток байтов.

Поток в TCP относится к последовательности байтов, поступающих в процесс или исходящих из него. Поэтому при общении с языками высокого уровня, такими как Java и golang, необходимо сериализовать соответствующую сущность для передачи. Кроме того, когда мы используем Redis в качестве кеша, нам необходимо сериализовать данные, помещаемые в Redis, потому что нижний уровень Redis — это реализованный протокол TCP.

TCP не знает значения передаваемого потока байтов, и TCP не может гарантировать, что блоки данных, отправленные приложением-получателем и приложением-отправителем, имеют соответствующее соотношение размеров (это проблема залипания пакетов во время передачи TCP).Но поток байтов, окончательно полученный получателем приложения, и поток байтов, отправленный отправителем, должны быть одинаковыми. Следовательно, мы должны сформулировать разумную стратегию закрепления и распаковки при использовании протокола TCP.

На следующем рисунке показан весь процесс передачи протокола TCP:

TCP面向字节流TCP ориентирован на байты

Картинка ниже взята изстарые деньгиЯ взял это из своего блога, это очень ярко
TCP传输动图Анимация передачи TCP

Повторение проблемы с закрепленными пакетами TCP

Теоретическая проверка

Как показано на рисунке ниже, существует три ситуации, в которых возникает проблема с липким пакетом.

TCP粘包问题Проблема с липким пакетом TCP

Первый случай:
Первый корень на картинке вышеbarКак показано, сервер считывает в общей сложности два пакета данных, каждый из которых завершен, и проблем с застреванием пакетов нет. С этой ситуацией проще справиться. Серверу нужно только просто прочитать из сетевого буфера. сообщение, прочитанное сервером, каждый раз завершается, и некорректных данных не будет.

Второй случай:
Сервер получает только один пакет данных, который содержит полную информацию о двух сообщениях, отправленных клиентом.В это время сервер, основанный на логике первого случая, ослеплен, потому что сервер не может его хорошо обработать.Этот пакет данных даже не может быть обработан.Эта ситуация на самом деле является проблемой липких пакетов TCP.

Третий случай:
Сервер получает два пакета, первый пакет содержит только часть первого сообщения, вторая половина первого сообщения и второе сообщение находятся во втором пакете, или первый первый пакет данных содержит полную информацию первого сообщения и часть информации второго сообщения, а второй пакет данных содержит оставшуюся часть второго сообщения.Эта ситуация на самом деле проблема распаковки TCP, т.к. логика не так проста.

Почему происходит залипание и распаковка TCP?

  1. Данные, записанные приложением, превышают размер буфера сокета, что приведет к распаковке.

  2. Данные, записанные приложением, меньше размера буфера сокета, и сетевая карта несколько раз отправляет данные, записанные приложением, в сеть, что приведет к залипанию пакетов.

  3. Выполнить сегментацию TCP по размеру MSS (максимальной длины сообщения).Если длина сообщения TCP - длина заголовка TCP > MSS, произойдет распаковка пакета.

  4. Метод получения не считывает данные буфера сокета вовремя, что приводит к залипанию пакетов.

Как бороться с заклеиванием и распаковкой

Обычно используются следующие общепринятые методы:

  1. Используется протокол с заголовком сообщения, а в заголовке сообщения хранится идентификатор начала сообщения и информация о длине сообщения.Когда сервер получает заголовок сообщения, он анализирует длину сообщения, а затем считывает содержимое длины в обратном порядке.

  2. Установите сообщение фиксированной длины. Сервер каждый раз считывает содержимое заданной длины как полное сообщение. Если сообщение недостаточно длинное, пустое место заполняется фиксированными символами.

  3. Установите границу сообщения, сервер отделяет содержимое сообщения от сетевого потока в соответствии с редактированием сообщения, обычно используйте '\n'.

  4. Более сложные протоколы, такие как протоколы Интернета транспортных средств 808 и 809, с которыми арендодатель недавно столкнулся.

Кодовая практика приклеивания и распаковки TCP

Следующий код владельца в основном демонстрирует использование указанного заголовка сообщения и тела сообщения для решения проблемы прилипания и распаковки TCP.

код на стороне сервера:Основная логика кода на стороне сервера состоит в том, чтобы получить сообщение, отправленное клиентом, собрать сообщение и распечатать его.


import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author wuhf
 * @Date 2018/7/16 15:50
 **/
public class TestSocketServer {
    public static void main(String args[]) {
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8089));
            while (true) {
                Socket socket = serverSocket.accept();
                new ReceiveThread(socket).start();

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static class ReceiveThread extends Thread {
        public static final int PACKET_HEAD_LENGTH = 2;//包头长度
        private Socket socket;
        private volatile byte[] bytes = new byte[0];

        public ReceiveThread(Socket socket) {
            this.socket = socket;
        }

        public byte[] mergebyte(byte[] a, byte[] b, int begin, int end) {
            byte[] add = new byte[a.length + end - begin];
            int i = 0;
            for (i = 0; i < a.length; i++) {
                add[i] = a[i];
            }
            for (int k = begin; k < end; k++, i++) {
                add[i] = b[k];
            }
            return add;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                try {
                    InputStream reader = socket.getInputStream();
                    if (bytes.length < PACKET_HEAD_LENGTH) {
                        byte[] head = new byte[PACKET_HEAD_LENGTH - bytes.length];
                        int couter = reader.read(head);
                        if (couter < 0) {
                            continue;
                        }
                        bytes = mergebyte(bytes, head, 0, couter);
                        if (couter < PACKET_HEAD_LENGTH) {
                            continue;
                        }
                    }
                    // 下面这个值请注意,一定要取2长度的字节子数组作为报文长度,你懂得
                    byte[] temp = new byte[0];
                    temp = mergebyte(temp, bytes, 0, PACKET_HEAD_LENGTH);
                    String templength = new String(temp);
                    int bodylength = Integer.parseInt(templength);//包体长度
                    if (bytes.length - PACKET_HEAD_LENGTH < bodylength) {//不够一个包
                        byte[] body = new byte[bodylength + PACKET_HEAD_LENGTH - bytes.length];//剩下应该读的字节(凑一个包)
                        int couter = reader.read(body);
                        if (couter < 0) {
                            continue;
                        }
                        bytes = mergebyte(bytes, body, 0, couter);
                        if (couter < body.length) {
                            continue;
                        }
                    }
                    byte[] body = new byte[0];
                    body = mergebyte(body, bytes, PACKET_HEAD_LENGTH, bytes.length);
                    count++;
                    System.out.println("server receive body:  " + count + new String(body));
                    bytes = new byte[0];
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

Код на стороне клиента:Основная логика клиентского кода состоит в том, чтобы собрать сообщение для отправки, определить заголовок сообщения, тело сообщения, а затем отправить его на сервер.


import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * @author wuhf
 * @Date 2018/7/16 15:45
 **/
public class TestSocketClient {
    public static void main(String args[]) throws IOException {
        Socket clientSocket = new Socket();
        clientSocket.connect(new InetSocketAddress(8089));
        new SendThread(clientSocket).start();

    }

    static class SendThread extends Thread {
        Socket socket;
        PrintWriter printWriter = null;

        public SendThread(Socket socket) {
            this.socket = socket;
            try {
                printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            String reqMessage = "HelloWorld! from clientsocket this is test half packages!";
            for (int i = 0; i < 100; i++) {
                sendPacket(reqMessage);
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }

        public void sendPacket(String message) {
            try {
                OutputStream writer = socket.getOutputStream();
                writer.write(message.getBytes());
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

резюме

В последнее время я веду несколько фреймворковых блогов, и блогов, посвященных теоретическим техническим дискуссиям по тем или иным вопросам, относительно немного, поэтому арендодатель думает о том, как научиться чему-то самому, а также дать дикую дорогу технологиям. борющиеся друзья - это направление, в котором работает домовладелец.

Справочная статья

Добавить Автора
Оригинальная ссылка:Уууу. Пересаживайтесь на студию. Можете/статья/201…
Заявление об авторских правах: Неспециальное заявление является оригинальной работой данного сайта.При перепечатке указывайте автора и оригинальную ссылку.