Взаимодействие по протоколу TCP выполняется через канал
С точки зрения процессов взаимодействие по протоколу TCP выполняется через канал, который существует до тех пор, пока один из процессов не закроет его. Передача процессом информации сводится к тому, что процесс «сбрасывает» байты непосредственно в канал, при этом нет необходимости снабжать байты адресом назначения, поскольку канал логически связан с адресатом. Кроме того, передача по каналу является надежной, то есть принимаемая последовательность байтов в точности соответствует передаваемой последовательности.
Протокол UDP, как и TCP, реализует взаимодействие между двумя процессами, выполняющимися на разных хостах; тем не менее UDP-взаимодействие принципиально отличается от TCP-взаимодействия. Во-первых, логическое соединение между хостами отсутствует, поскольку не существует процедуры рукопожатия, в ходе которой устанавливался бы канал. Во-вторых, любая передаваемая последовательность байтов должна снабжаться адресом назначения, представляющим собой совокупность IP-адреса хоста и номера порта процесса.
IP-адрес, номер порта и последовательность информационных байтов мы будем называть пакетом. Передачу по протоколу U DP можно сравнить с поездкой на такси: первым делом нужно сообщить водителю адрес дома, к которому мы хотим добраться.
После того как пакет создан, процесс отправляет его адресату через свой сокет.
Транспортный уровень (протокол UDP) предпринимает действия для доставки пакета по адресу назначения, однако не дает относительно доставки никаких гарантий. Другими словами, протокол UDP обеспечивает ненадежную передачу данных между процессами.
В этом разделе в качестве примера мы модифицируем предыдущее приложение для протокола UDP. Как вы увидите, коды программ для протоколов TCP и UDP значительно различаются. Во-первых, из-за отсутствия процедуры рукопожатия нет необходимости во впускающем сокете; во-вторых, входные и выходные потоки данных не связаны с сокетами; в-третьих, при пересылке каждой группы байтов указывается IP-адрес хоста-получателя и номер порта сервера; в-четвертых, принимающей стороне приходится выделять информационные байты из принятых пакетов. Напомним порядок действий, выполняемых нашим приложением.
1. Клиент считывает со стандартного устройства ввода (клавиатуры) строку символов и посылает эту строку серверу через свой сокет.
2. Сервер принимает строку через свой сокет.
3. Сервер переводит все символы строки в верхний регистр.
4. Сервер отсылает модифицированную строку клиенту.
5. Клиент получает строку и печатает ее с помощью стандартного устройства вывода (монитора).
На рис. 2.22 приведена схема взаимодействия клиента и сервера при использовании протокола UDP.
Ниже следует текст программы UDPClient.java.
import java.io.*;
import java.net.*;
class UDPClient {
public static void main(String args[]) throws Exception
{
BufferedReader inFromUser =
new BufferedReader(new InputStreamReader (System.in));
DatagramSocket clientSocket = new DatagramSocketO;
InetAddress IPAddress =
InetAddress.getByName(«hostname»);
byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];
String sentence = inFromUser.readLineO;
sendData = sentence.getBytesO;
DatagramPacket sendPacket =
new DatagramPacket(sendData. sendData.length. IPAddress, 9876); clientSocket.send(sendPacket);
DatagramPacket receivePacket —
new DatagramPacket(receiveData. receiveData.length);
clientSocket.recei ve(recei vePacket);
String modifiedSentence =
new String(receivePacket.getDataO);
System.out.print!n(«FROM SERVER;» + modifiedSentence);
cli entSocket. closeO;
}
}
Как показано на рис. 2.23, программа UDPClient создает один поток данных и один сокет. Сокет имеет тип DatagramSocket и имя clientSocket. Обратите внимание, что при использовании протоколов UDP и TCP типы сокетов различаются: в случае TCP обычно применяют тип Socket. Поток inFromUser является входным для программы и связан со стандартным устройством ввода (клавиатурой). Когда пользователь вводит символы, они попадают в программу через входной поток данных. Аналогичным образом входной поток был определен в программе TCPClient. Следует отметить одну важную деталь: при использовании протокола UDP ни один из потоков (ни входной, ни выходной) не связан с сокетом. Отправка байтов осуществляется в пакетах с помощью объекта типа DatagramSocket.
Теперь более подробно рассмотрим строки кода программы UDPClient.
DatagramSocket clientSocket = new DatagramSocketO;
Эта команда создает объект clientSocket типа DatagramSocket, однако в отличие от программы TCPClient не инициирует установление TCP-соединения. По этой причине конструктор DatagramSocketO не принимает в качестве аргументов имя хоста сервера и номер порта. Таким образом, действие приведенной строки сводится лишь к созданию сокета клиента.
InetAddress IPAddress = InetAddress.getByName(«hostname»);
Для того чтобы иметь возможность передавать удаленному процессу информацию, необходимо располагать его адресом. Как мы знаем, частью адреса процесса является IP-адрес хоста, на котором он выполняется. Данная строка создает DNS-зап-рос IP-адреса хоста с именем hostname (как и в программе TCPClient, слово hostname необходимо заменить реальным именем хоста). Фактически работа с DNS-запро-сом осуществляется методом getByName(), который принимает в качестве аргумента имя хоста и возвращает полученный IP-адрес. IP-адрес помещается в объект IPAddress типа InetAddress.
byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];
Байтовые массивы sendData и receiveData предназначены для хранения передаваемой и принимаемой информации соответственно.
sendData = sentence.getBytesO;
Строка sentence с помощью метода getBytes() преобразуется в массив байтов, который копируется в переменную sendData.
DatagramPacket sendPacket =
new DatagramPacket(sendData, sendData.length, IPAddress. 9876);
Здесь происходит создание пакета sendPacket, предназначенного для отправки серверу через сокет. В пакет входят данные, содержащиеся в переменной sendData, размер этих данных, IP-адрес хоста назначения и номер порта приложения (в данном случае мы выбрали номер 9876). Обратите внимание на то, что пакет имеет тип DatagramPacket.
clientSocket.send(sendPacket);
Метод send() объекта clientSocket отправляет пакет sendPacket (как упоминалось ранее, clientSocket представляет собой клиентский сокет приложения). Обратимся к механизмам передачи данных, осуществляемым протоколами TCP и UDP. В случае TCP строка символов включается в выходной поток, имеющий логическую связь с сервером. В случае UDP нам необходимо создать пакет, содержащий адрес сервера.
После передачи пакета клиент ожидает приема пакета от сервера.
DatagramPacket receivePacket =
new DatagramPacket(receiveData. receiveData.length);
Здесь клиент создает переменную для хранения пакета receive Packet типа Datagram Packet.
clientSocket.recei ve(receivePacket):
Клиент находится в состоянии ожидания до тех пор, пока не будет начат прием
пакета от сервера; принимаемые символы помещаются в переменную receivePacket.
String modifiedSentence =
new Stri ng(recei vePacket.getData());
В этой строке происходит извлечение данных из объекта receivePacket и преобразование типа из массива байтов в строку с последующим присваиванием переменной modifiedSentence.
System.out.println(«FROM SERVER:» + modifiedSentence);
Эта строка, присутствовавшая ранее в программе TCPClient, печатает на экране клиента строку modifiedSentence.
clientSocket.close();
Вызов метода close() объекта clientSocket приводит к закрытию сокета. Поскольку протокол UDP не использует логическое соединение, при закрытии сокета клиент не посылает серверу сообщений транспортного уровня (как это было в случае TCP).
Теперь ознакомимся с текстом программы UDPServer.java, представляющей серверную часть нашего приложения.
import java.io.*;
import java.net.*;
class UDPServer {
public static void main(String args[]) throws Exception
{
DatagramSocket serverSocket = new DatagramSocket(9876);
byte[] receiveData = new byte[1024];
byte[] sendData — new byte[1024]:
while(true)
{
DatagramPacket receivePacket =
new DatagramPacket(receiveData. receiveData.length); serverSocket.receive(receivePacket);
String sentence = new String(receivePacket.getDataO);
InetAddress IPAddress = receivePacket.getAddress();
int port = recei vePacket. get Port 0;
String capitalizedSentence = sentence.toUpperCase();
sendData = capital izedSentence.getBytesO;
DatagramPacket sendPacket =
new DatagramPacket(sendData. sendData.length. IPAddress. port); serverSocket.send(sendPacket);
}
}
}
Как показано на рис. 2.24, программа UDPServer создает единственный сокет с именем serverSocket типа DatagramSocket; этот же тип использовался нами в программе-клиенте приложения. Аналогично с сокетом не связан ни один из потоков данных.
Рассмотрим некоторые из строк приведенного кода.
DatagramSocket serverSocket = new DatagramSocket(9876);
Эта строка создает объект serverSocket типа DatagramSocket с номером порта 9876. Передача и прием всех данных сервером осуществляется при помощи этого объекта. Поскольку протокол UDP не предполагает установления логического соединения, у нас нет необходимости поддерживать отдельный сокет для каждого клиента и специальный впускающий сокет для рукопожатий с новыми клиентами, как для протокола TCP. В случае нескольких клиентов все они будут осуществлять обмен данными с сервером через единственный сокет serverSocket.
String sentence = new String(receivePacket.getDataO);
InetAddress IPAddress = receivePacket.getAddress();
int port = receivePacket.getPort();
Вышеприведенные строки осуществляют распаковку пакета, переданного клиентом. Первая команда извлекает из пакета данные и помещает их в строковый объект sentence (аналогичная команда есть в программе UDPClient). Следующая команда извлекает IP-адрес, а последняя, третья команда — номер порта сервера (в общем случае номер порта отличается от 9876). Мы подробно рассмотрим номера портов в следующей главе. Извлечение IP-адреса и номера порта обусловлено необходимостью идентификации клиента для передачи модифицированной строки.
Мы полностью рассмотрели пару программ, составляющих UDP-приложение. Для того чтобы протестировать приложение, необходимо поместить программы на разные хосты, а затем скомпилировать и запустить их. Обратите внимание, что в данном случае клиентскую часть приложения можно запустить раньше, чем серверную. Это объясняется тем, что после запуска клиент не предпринимает попыток установить соединение с сервером. Запустив программу-клиент, вы можете начать ввод символов строки, предназначенной для удаленной обработки.