Страницы

Поиск по вопросам

суббота, 1 февраля 2020 г.

Пакеты склеиваются

#c_sharp #java #сеть


public class NetCon extends Thread {

    String address = "127.0.0.1";
    int port = 3458;

    private DataInputStream in;
    private DataOutputStream out;

    public ArrayList queue;

    public Boolean Connect()
    {
        try {
            InetAddress ipAddress = InetAddress.getByName(address);
            Socket socket = new Socket(ipAddress, port);

            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();

            in = new DataInputStream(sin);
            out = new DataOutputStream(sout);

            queue = new ArrayList();
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public void SendText(String s) {
        try {
            out.write(s.getBytes("UTF-8"));
            out.flush();
        } catch(Exception e) {
            System.out.println(e);
        }
    }

    @Override
    public void run() {
        for(int i = 0; i < queue.size(); i++) {
            //System.out.println(queue.get(i)); //если убрать коммент. то ок
            SendText(queue.get(i));
        }
    }

}

В queue находится две строки "test1", "test2". Запускаю поток, идёт отправка на сервер.
Проблема в том, что иногда получаю 2 пакета как и положено, первый пакет со строкой
"test1", второй с "test2", а иногда проскакивает что получаю пакет "test1test2". Если
перед отправкой на сервер выводить на экран, то получается маленькая задержка и работает
тогда всегда правильно. Почему между пакетами нужна эта пауза? Как сделать нормально?
Вот фрагмент кода сервера C# (TcpClient)
NetworkStream ns = client.GetStream();
byte[] buffer = new byte[1024];

int count;
while ((count = ns.Read(buffer, 0, buffer.Length)) > 0)
{
   Console.WriteLine(BitConverter.ToString(buffer, 0, count));
   Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, count));
   ns.Flush();
}
    


Ответы

Ответ 1



Скорость сокета - непредсказуемая штука. Бывает, что пакет пришел ещё не весь, а программа уже читает буфер - пакет получен не весь, а следующий искажен. Бывает, что программа не дернула буфер сокета вовремя, и пришел ещё один или больше пакетов. Программа дергает буфер - и получает склеившиеся пакеты. Всё дело в устройстве TCP/IP, и вытекающем из него устройстве сокета и его буфера. Сетевые пакеты ни как не обозначают свою длину, если програмист не позаботился об этом. В сокет приходит байт, и он сразу помещается в конец своего буфера. Не имеет значения, пришел один байт или миллион. Когда программа вызывает метод чтения, если не указана читаемая длина - возвращается весь буфер в обратном порядке, а если указана - то байты от нулевого до указанного в обратном порядке. Таким образом программист сам обязан обозначивать размер или диапазон байтов в пакете. Решений несколько. От самых неправильных к наиболее подходящему: 1) Фиксировать размер пакета. Встречал несколько раз в чужом коде. Наличествует ещё и свой буфер, в который до нужного заполнения сливают данные буфера сокета, и читают нужными порциями. Стоит ли говорить, что вещь ужасно хардкодная? 2) В пакете означают его конец, к примеру каким-нибудь сочетанием 00-FF-00-FF. Встречал 2 раза. Велосипедизм. Возможны ложные срабатывания детектора конца пакета. 3) Первыми двумя (или четырьмя, если пакет планируется длинным) байтам обозначают либо размер всего пакета, либо размер "полезной части. Программа в цикле ждет 2 байта в буфере сокета, и читает их. Далее программа ждет появления N байтов или больше в сокете, где N - цифра, прочтенная из первых двух байтов. Далее программа читает N байт в свой буфер, и обрабатывает их. Цикл входит в новую итерацию. Помогать конкретным кодом не стану, если автор не попросит этого прямо. Я пропагандирую самостоятельное развитие ума с возможными подсказками. Добавлено: Вспомнил, что встречал ещё одну разновидность велосипедизма, похожую на ответ ниже. В первых 4 байтах передавался идентификатор пакета (обычный int), он передавался в обработчик пакетов, где свитчем выбирался нужный, создавался экземпляр этого пакета, в конструкторе читалось нужное количество байтов, и пакет обрабатывался. Самое смешное, что такой велосипед присутствует в одном, довольно известном и, пожалуй, знаменитом, приложении. Сообщить название продукта и производителя не могу по причине NDA. В принципе, такой метод вполне жизнеспособен, и, возможно, полностью пропитан духом ООП, но мне он кажется через чур странным... Ведь одио из основных негласных принципов ООП является отсутствие хардкода, а в таком решении размер пакета захардкожен.

Ответ 2



Нужен протокол, собственно софтверная его часть, скажем в самом примитивном исполнении так: public class MyProtocol { final String beginner="$BEGIN$"; String data; final String terminator="$END$" } Тогда вы точно будете знать где начало и где конец ваших данных и не будете зависеть от капризов сокетов.

Ответ 3



Я сталкивался с взаимодействием сервер-клиент по сокету. Там была четко разработана структура пакета, он имел строго определенный размер и разметку, первые 12 символов отвечали за назначение пакета(##-так пакет начинался, далее шла служебная информация о id пользователя и тп), далее шла информация которая предназначалась пользователю, а на конце последние два значения отвечали за целостность строки (CRC, если строка не проходила проверку, запрос повторялся 3 раза) Я к тому что нужно объединить несколько методов контроля данных Фиксировать размер пакета. Встречал несколько раз в чужом коде. Наличествует ещё и свой буфер, в который до нужного заполнения сливают данные буфера сокета, и читают нужными порциями. Стоит ли говорить, что вещь ужасно хардкодная? В пакете означают его конец, к примеру каким-нибудь сочетанием 00-FF-00-FF. Встречал 2 раза. Велосипедизм. Возможны ложные срабатывания детектора конца пакета. - Только тут надо обозначить не конец, а начало строки.

Комментариев нет:

Отправить комментарий