«1. Обзор
В этой статье мы рассмотрим сетевое взаимодействие с Java через протокол пользовательских дейтаграмм (UDP).
UDP — это коммуникационный протокол, который передает по сети независимые пакеты без гарантии прибытия и порядка доставки.
Большая часть связи в Интернете осуществляется по протоколу управления передачей (TCP), однако и UDP имеет свое место, которое мы рассмотрим в следующем разделе.
2. Зачем использовать UDP?
UDP сильно отличается от более распространенного TCP. Но прежде чем рассматривать поверхностные недостатки UDP, важно понять, что отсутствие накладных расходов может сделать его значительно быстрее, чем TCP.
Помимо скорости, мы также должны помнить, что некоторые виды связи не требуют надежности TCP, а вместо этого ценят низкую задержку. Видео является хорошим примером приложения, которое может выиграть от работы по протоколу UDP вместо TCP.
3. Создание приложений UDP
Создание приложений UDP очень похоже на создание системы TCP; единственная разница в том, что мы не устанавливаем соединение точка-точка между клиентом и сервером.
Настройка тоже очень проста. Java поставляется со встроенной сетевой поддержкой UDP, которая является частью пакета java.net. Поэтому для выполнения сетевых операций через UDP нам нужно только импортировать классы из пакета java.net: java.net.DatagramSocket и java.net.DatagramPacket.
В следующих разделах мы узнаем, как проектировать приложения, взаимодействующие по протоколу UDP; мы будем использовать популярный эхо-протокол для этого приложения.
Сначала мы создадим эхо-сервер, который отправляет обратно любое отправленное ему сообщение, затем эхо-клиент, который просто отправляет любое произвольное сообщение на сервер, и, наконец, мы протестируем приложение, чтобы убедиться, что все работает нормально.
4. Сервер
При обмене по протоколу UDP одно сообщение инкапсулируется в DatagramPacket, который отправляется через DatagramSocket.
Давайте начнем с настройки простого сервера:
public class EchoServer extends Thread {
private DatagramSocket socket;
private boolean running;
private byte[] buf = new byte[256];
public EchoServer() {
socket = new DatagramSocket(4445);
}
public void run() {
running = true;
while (running) {
DatagramPacket packet
= new DatagramPacket(buf, buf.length);
socket.receive(packet);
InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buf, buf.length, address, port);
String received
= new String(packet.getData(), 0, packet.getLength());
if (received.equals("end")) {
running = false;
continue;
}
socket.send(packet);
}
socket.close();
}
}
Мы создаем глобальный DatagramSocket, который мы будем использовать для отправки пакетов, массив байтов для переноса наших сообщений и переменную состояния с именем running.
Для простоты сервер расширяет Thread, поэтому мы можем реализовать все внутри метода run.
Внутри run мы создаем цикл while, который просто выполняется до тех пор, пока run не изменится на false из-за какой-либо ошибки или сообщения о завершении от клиента.
В начале цикла мы создаем экземпляр DatagramPacket для получения входящих сообщений.
Далее мы вызываем метод получения для сокета. Этот метод блокируется до тех пор, пока не прибудет сообщение, и он сохраняет сообщение в массиве байтов переданного ему DatagramPacket.
Получив сообщение, мы получаем адрес и порт клиента, так как собираемся отправить ответ обратно.
Далее мы создаем DatagramPacket для отправки сообщения клиенту. Обратите внимание на разницу в подписи с принимающим пакетом. Для этого также требуется адрес и порт клиента, которому мы отправляем сообщение.
5. Клиент
Теперь давайте развернем простой клиент для этого нового сервера:
public class EchoClient {
private DatagramSocket socket;
private InetAddress address;
private byte[] buf;
public EchoClient() {
socket = new DatagramSocket();
address = InetAddress.getByName("localhost");
}
public String sendEcho(String msg) {
buf = msg.getBytes();
DatagramPacket packet
= new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(
packet.getData(), 0, packet.getLength());
return received;
}
public void close() {
socket.close();
}
}
Код не сильно отличается от кода сервера. У нас есть глобальный DatagramSocket и адрес сервера. Мы создаем их внутри конструктора.
У нас есть отдельный метод, который отправляет сообщения на сервер и возвращает ответ.
Сначала мы преобразуем строковое сообщение в массив байтов, а затем создаем DatagramPacket для отправки сообщений.
Далее – отправляем сообщение. Мы сразу конвертируем DatagramPacket в принимающий.
Когда приходит эхо, мы конвертируем байты в строку и возвращаем строку.
6. Тест
В классе UDPTest.java мы просто создаем один тест для проверки эхо-способности двух наших приложений:
public class UDPTest {
EchoClient client;
@Before
public void setup(){
new EchoServer().start();
client = new EchoClient();
}
@Test
public void whenCanSendAndReceivePacket_thenCorrect() {
String echo = client.sendEcho("hello server");
assertEquals("hello server", echo);
echo = client.sendEcho("server is working");
assertFalse(echo.equals("hello server"));
}
@After
public void tearDown() {
client.sendEcho("end");
client.close();
}
}
«
«В настройке мы запускаем сервер, а также создаем клиент. Находясь в методе tearDown, мы отправляем сообщение о завершении на сервер, чтобы он мог закрыться, и в то же время мы закрываем клиент.
7. Заключение
В этой статье мы узнали о протоколе пользовательских дейтаграмм и успешно создали наши собственные клиент-серверные приложения, взаимодействующие по протоколу UDP.