1. Обзор
Для отправки и получения данных по сети мы часто используем сокеты. Сокеты — это не что иное, как комбинация IP-адреса и номера порта, которая может однозначно идентифицировать программу, работающую на любом данном компьютере.
В этом уроке мы покажем, как мы можем читать данные, которые отправляются нам через сокет.
2. Чтение данных из сокета
Предположим, у нас есть базовое понимание программирования сокетов .
Теперь мы углубимся в чтение данных на порту, который прослушивает наш сервер.
Во-первых, нам нужно объявить и инициализировать переменные ServerSocket, Socket
и DataInputStream
:
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
Обратите внимание, что мы решили обернуть InputStream
сокета в DataInputStream.
Это позволяет нам читать строки текстовых и примитивных типов данных Java переносимым способом.
Это хорошо, поскольку теперь, если мы знаем тип данных, которые мы получим, мы можем использовать специализированные методы, такие как readChar(), readInt(), readDouble()
и readLine().
Однако это может быть затруднительно, если тип и длина данных не известны заранее.
В этом случае мы получим поток байтов из сокета, используя функцию read() более низкого уровня.
Но в этом подходе есть небольшая проблема: как узнать длину и тип данных, которые мы получим?
Давайте рассмотрим этот сценарий в следующем разделе.
3. Чтение двоичных данных из сокета
При чтении данных в байтах нам нужно определить собственный протокол для связи между сервером и клиентом. Самый простой протокол, который мы можем определить, называется TLV (Type Length Value). Это означает, что каждое сообщение, записываемое в сокет, имеет форму значения длины типа.
Итак, мы определяем каждое отправленное сообщение как:
- 1 -
байтовый
символ, представляющий тип данных, напримерs
длястроки .
- 4 -
байтовое
целое число, указывающее длину данных. - А дальше собственно данные, длина которых как раз и была указана
Как только клиент и сервер установят соединение, каждое сообщение будет соответствовать этому формату. Затем мы можем написать наш код для разбора каждого сообщения и чтения n
байтов данных определенного типа.
Давайте посмотрим, как мы можем реализовать это, используя простой пример со строковым
сообщением.
Во-первых, нам нужно вызвать функцию readChar()
, чтобы прочитать тип данных, а затем вызвать функцию readInt()
, чтобы прочитать их длину:
char dataType = in.readChar();
int length = in.readInt();
После этого нам нужно прочитать данные, которые мы получаем. Здесь важно отметить, что функция read()
может быть не в состоянии прочитать все данные за один вызов. Итак, нам нужно вызвать read()
в цикле while:
if(dataType == 's') {
byte[] messageByte = new byte[length];
boolean end = false;
StringBuilder dataString = new StringBuilder(length);
int totalBytesRead = 0;
while(!end) {
int currentBytesRead = in.read(messageByte);
totalBytesRead = currentBytesRead + totalBytesRead;
if(totalBytesRead <= length) {
dataString
.append(new String(messageByte, 0, currentBytesRead, StandardCharsets.UTF_8));
} else {
dataString
.append(new String(messageByte, 0, length - totalBytesRead + currentBytesRead,
StandardCharsets.UTF_8));
}
if(dataString.length()>=length) {
end = true;
}
}
}
4. Клиентский код для отправки данных
А как насчет клиентского кода? На самом деле, это довольно просто:
char type = 's'; // s for string
String data = "This is a string of length 29";
byte[] dataInBytes = data.getBytes(StandardCharsets.UTF_8);
out.writeChar(type);
out.writeInt(dataInBytes.length);
out.write(dataInBytes);
Это все, что делает наш клиент!
5. Вывод
В этой статье мы обсудили, как читать данные из сокета. Мы рассмотрели различные функции, которые помогают нам читать данные определенного типа. Кроме того, мы увидели, как читать двоичные данные.
Полную реализацию этого туториала можно найти на GitHub .