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();
}
}
В setup
мы запускаем сервер, а также создаем клиент. Находясь в методе tearDown
, мы отправляем сообщение о завершении на сервер, чтобы он мог закрыться, и в то же время мы закрываем клиент.
7. Заключение
В этой статье мы узнали о протоколе пользовательских дейтаграмм и успешно создали наши собственные клиент-серверные приложения, которые взаимодействуют по протоколу UDP.
Чтобы получить полный исходный код примеров, использованных в этой статье, вы можете ознакомиться с проектом GitHub .