#c_sharp #java #сеть
public class NetCon extends Thread { String address = "127.0.0.1"; int port = 3458; private DataInputStream in; private DataOutputStream out; public ArrayListqueue; 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 раза. Велосипедизм. Возможны ложные срабатывания детектора конца пакета. - Только тут надо обозначить не конец, а начало строки.
Комментариев нет:
Отправить комментарий