Перевод 1997 (C) Алексей Гузеев
Программирование TCP/IP имет весьма простую концепцию: имеются два процесса, которые общаются между собой через сокеты и обмениваются информацией используя некоторый протокол. Один из этих процессов как правило может быть назван клиентом, а другой - сервером. Один из примеров архитектуры клиент-сервер в интернете - это комбинация Web Browser'а (например, Netscape) и Web Server'а (HTTPd), которые используют HyperText Transport Protocol (HTTP) для обмена информацией друг с другом. Другой пример подобной архитектуры - это сервер FTPd (File Transfer Protocol daemon), предоставляющий сервис передачи файлов. Программы-сервера, такие как HTTPd или FTPd, как правило ждут входящие запросы клиентов на назначенном порту хоста. Удовлетворив запрос, сервер ждёт очередной запрос от (возможно, другого) клиента. Клиенты присоединяются к серверу указанием имени хоста (или ip-адреса) и необязательного номера порта, по которому сервер на хосте ждёт запросов (например, HTTP сервера как правило ждут запросов на порту 80).
Ява, будучи объектно-ориентированным языком программирования, предоставляет необходимые инструменты для разработки как программ-клиентов, так и программ-серверов для интернета.
Все классы, необходимые для связи через интернет предоставляются пакетом java.net (из стандартной библиотеки классов). java.net включает в себя поддержку для программирования сокетов, дэйтаграмм, вспомогательные классы, представляющие интернетовские адреса и URL (Uniform Resource Locator). Это классы ServerSocket
, Socket
, DatagramPacket
, DatagramSocket
, InetAddress
, URL
, URLConnection
, и так далее.
Программирование сокетов напоминает программирование файлов: вы открываете соединение сокета, пишите/читаете используя потоки байт, когда заканчиваете - закрываете соединение. Однако программирование сокетов более динамично по природе нежели работа с файлами, так как оно подразумевает общение между двумя независимыми процессами, каждый из которых читает и/или пишет данные в той или другой форме.
Ява предоставляет два платформонезависимых класса для соединения с помощью сокетов: Socket
и ServerSocket
, используемые соответственно для разработки клиентских и серверных программ. Клиент и сервер обмениваются данными используя входные и выходные потоки байт. При установлении соединения, конструируется новый объект класса Socket
, который и предоставляет входной и выходной потоки к сокету на другой стороне соединения.
На рис. 1 показано клиентское приложение, которое использует сокеты для отсылки SQL запросов на сервер и получения с него результатов. Полный исходный код обоих приложений вы можете найти в листингах 1 и 2. На рис. 2 показано, как оба этих приложения взаимодействуют друг с другом. Кратко, клиентское приложение (javaSQL) посылает SQL запросы промежуточному серверному приложению, который в свою очередь запрашивает базу данных и перенаправляет результат запроса к клиенту. Протокол, использованный этими двумя приложениями очень прост: Клиент посылает пять параметров (Server, user, password, database, SQL выражение) как токены, разделённые символом '|'
(пример см. ниже), и символ '\0'
в конце данных для отметки конца ввода. Сервер просто посылает результат запроса назад.
// Send Server for (idx=0; idx<tServer.getText().length(); idx++) { o.write(tServer.getText().charAt(idx)); } o.write('|');
Исходный код, относящийся к сокетам, в обоих приложениях весьма похож. Основное различие состоит в том, как на каждой стороне конструируется объект класса Socket
. На сервере трубуются два шага, чтобы получить объект класса Socket
. Первый шаг, требуемый только однажды в программе, состоит в создании объекта ServerSocket
, ждущий на некотором порту; может использоваться значение 0
для соединения по анонимному порту (как показано в примере ниже). После успешного создания объкта класса ServerSocket
, вы можете использовать метод accept
для принятия запроса на соединение от клиента. Заметьте, метод accept
ждёт (блокируется) до установки соединения. Как только соединение установлено, метод accept
возвращает объект класса Socket
, который есть по сути ссылка на сокет клиентской стороны. Помещать вызов метода accept
в цикл while
- это принятая практика, позволяющая обрабатывать несколько запросов клиентов (Заметьте, что так как запросы на соединение помещаются в очередь, несколько запросов будут обслужены последовательно). Сделующий фрагмент кода из листинга 2 наглядно демострирует упомянутые в данном параграфе механизмы:
Socket client; . . ServerSocket server=new ServerSocket(0, 10); while (true) { . . client=server.accept(); InputStream i=client.getInputStream(); OutputStream o=client.getOutputStream(); . . }
Клиенты подсоединяются к серверу, создавая новый объект класса Socket
с необходимыми параметрами, указывающими на местоположение сокета сервера. Класс Socket
предоставляет два способа создания новых объектов: первый - используя объект класса InetAddress
(см. секцию о адресах интернета далее) и номер порта; второй - используя имя хоста и номер порта (показано ниже). Следующий фрагмент кода из листинга 1 создает соединение сокетов к серверу javaSQLd используя имя хоста и номер порта, полученные с экрана, затем получает входной и выходной потоки для чтения и записи к сокету сервера:
Integer port=new Integer(tPort.getText()); Socket s=new Socket(tHost.getText(), port.intValue(), true); OutputStream o=s.getOutputStream(); InputStream i=s.getInputStream();
Как показано в фрагменте кода выше, как только объект класса Socket
успешно создан, входной и выходной потоки могут получены для послудующих получения и приёма информации программе на другой стороне соединения сокетов. Чтение и запись в потоки могут выполняться различными формами методов read
и write
классов java.io.InputStream
и java.io.OutputStream
. Следующие строки показывают пример чтения и записи по одному символу за раз:
int onechar; onechar=i.read(); o.write((char)onechar);
Дополнительно, в пакете java.io
имеется несколько других полезных видов классов входных/выходных потоков, которые могут быть сконструированы используя существующие объекты классов InputStream
или OutputStream
. Эти классы делают радобу с входящими и исходящими байтами чуть проще. Некоторые их этих классов, включая BufferedInputStream
, BufferedOutputStream
,
DataInputStream
, DataOutputStream
, FilterInputStream
и FilterOutputStream
.
Коммуникаци с помощью дейтаграмм - это посылка самоопределённых сообщений без гарантии доставки через сеть. Она ненадёжна потому, что доставка пакетов не гарантируется, и даже порядок прибывших пакетов может отличаться от порядка их отсылки. Если необходим надёжный коммуникационный канал, то сокеты - более подходящее решение; однако, сокеты могут быть слишком тяжелы в некоторых ситуациях. Дейтаграммы полезны в ситуациях, когда необходимо широко разослать некритичную информацию одному или более клиенту, например, сервер, периодически рассылающий текущее время клиентам, для синхронизации.
Ява предоставляет два класса для коммуникаций с помощью дейтаграмм: DatagramPacket
и DatagramSocket
. Оба этих класса необходимы любому приложению для отправки и/или приёма дейтаграмм; поэтому, исходный код клиента и сервера практически идентичен. Листинг 3 содержит исходный код примера клиентского дейтаграмного приложения, который общается с примером серверного дейтаграммного приложения, исходный код которого приведён в листинге 4. Эти программы оченть просты по сути - клиентская программа (dgClient.java) посылает строку "Hello"
программе-серверу (dgServer.java), и сервер отвечает посылкой строки "Hello Back"
назад клиенту.
Как вы можете видеть в следующем фрагменте кода из листинга 3, объект класса DatagramPacket
содержит всю необходимую информацию для посылки сообщений через сеть; содержимое сообщения, интернетовский адрес получателя и номер порта содержатся в объекте класса DatagramPacket
:
DatagramSocket socket=new DatagramSocket(4444); DatagramPacket packet=new DatagramPacket(Data, 6, InetAddress.getByName("somehost"), 5555); socket.send(packet);
packet=new DatagramPacket(Data, 20); socket.receive(packet);
Оба метода - send
и receive
класса DatagramSocket
требуют объект класса DatagramPacket
в качестве параметра. Разница состоит в том, как конструируется объект класса DatagramPacket
для использования в разных методах: для приёма, объект класса DatagramPacket
конструируется с буфером определённой длины, чтобы хранить принятые данные; для передачи дополнительно необходимо указать интернет-адрес и номер порта получателя.
Чтобы присоединиться к серверу в интернете, вам необходимо знать интернет-адрес (типа 161.107.9.87) хоста, на котором выполняется сервер, и номер порта, по которому сервер ждёт соединений. Класс InetAddress
используется для представления интернет-адресов. Объекты класса InetAddress
как правило конструируются, используя статический метод getByName
, который принимает строку-параметр указывающий на имя хоста или интернет-адрес. Будучи сконструирован, объект класса InetAddress
может быть передан в качестве параметра другим классам (например, Socket
). Например, следующий код открывает соединение сокетов к HTTP серверу, запущенному на www.javasoft.com:
InetAddress inet=InetAddress.getByName("www.javasoft.com"); Socket s=new Socket(inet, 80, true);
Универсальные локаторы ресурсов (URL'и) - это ссылки на ресурсы в интернете, широко использующиеся в вэб-браузерах для указания файлов на WWW. Ява предоставляет классы, прежде всего URL
и URLConnection
, для работы с URL'ями. Эти классы, использующие сокеты и HTTP как часть скрытой реализации, значительно упрощают задачу чтения/записи ресурсовинтернета. Дополнительно, многие из методов класса java.applet.Applet
принимают объект класса URL
в качестве параметра для доступа к файлам на хосте, содержащим звуки и картинки, или для указания поддерживающему яву браузеру отобразить указанный ресурс, как показано ниже:
getAppletContext().showDocument(new URL("http://www.divya.com/people/anil/"));
Листинг 5 содержит законченную программу на яве (copyURL.java), которая может быть использована для копирования содержимого ресурса интернета в локальный файл. Логика программы проста: она создаёт объект класса URL
, берет входной поток от URL'я, открывает локальный файл для записи, затем читает из URL и пишет в локальный файл. Обратите внимание в следующем фрагменте кода из листинга 5, как открывание входного потока ресурса интернета может быть выполнено двумя простыми строчками кода на яве. Первая строка создаёт объект класса URL
(используя один из четырёх существующих конструкторов), вторая получает ссылку на входной поток URL'я:
URL url=new URL(args[0]); InputStream is=url.openStream();
Откритие выходного потока к ресурсу, идентифицированному URL, требует вызова метода openConnection
класса URL
, который возвращает объект класса URLConnection
. Другим способом объект класса URLConnection
можно создать, передавая объект класса URL
конструктору. Объект класса URLConnection
представляет активное соединение к ресурсу интернета. Следующий код демонстрирует первый метод конструирования объекта класса URLConnection
:
URLConnection urlConnect=url.openConnection(); OutputStream os=urlConnect.getOutputStream();
В дополнение к предоставлению входного и выходного потоков к ресурсу, класс URL
также предоставляет методы для получения информации о текущем URL, такие как имя хоста, номер порта и так далее. Класс URLConnection
содержит более информативные методы, такие как getContentType
, getContentLength
,
getHeaderField
, getLastModified
, getExpiration
и другие подобные методы, которые предоставляют детали о содержимом ресурса. Вот два примера использования этих методов:
System.out.print("Type: "+urlC.getContentType()); Date date=new Date(urlC.getLastModified());
Вот примерный вывод, попрождаемый двумя вышеприведёнными строчками:
Type: image/gif, Modified On: Fri Mar 15 10:07:15 1996
Задолго до того, как появился JDK 1.0, формы HTML с CGI (Common Gateway Interface) использовались для посылки параметров и и получения результатов от CGI скриптов, выполняющихся на сервере. Программы на яве могут быть написаны для взаимодействия со скриптами CGI используя классы, предоставленные в пакете java.net
. CGI имеет два метода для пересылки параметров из HTML форм к скриптам CGI: GET и POST. Для метода GET вэб-браузер посылает параметры в виде строки запроса, приписываемой к URL'ю; для метода POST браузер посылает параметры в блоке данных в информации, посылаемой на веб-сервер. Но в то время, как веб-браузер автоматически обрабатывает детали отсылки данных к вэб-верверу основываясь на ниформации, указанной в HTML форме, на яве вы сами должны построить данные-параметры и послать их на вэб-сервер. Давайте взглянем на простой пример CGI скрипта (login.sh), который требует параметры "User" и "Password", и посмотрим как мы можем реализовать методы GET и POST в яве.
Реализация CGI метода GET проста - вы просто создаёте объект класса URL
со строко, содержащеё протокол, иям хоста, имя скрипта и строку запроса CGI как показано здесь:
URL url=new URL("http://www.somehost.com/cgi-bin/login.sh?User=anil&Password=letmein"); URLConnection urlC=url.openConnection();
По другому, вы можете использовать метод showDocument
класса java.applet.Applet
для запроса браузеру обработать вышеприведённый URL как будто-бы он был набран пользователем:
getAppletContext().showDocument(new URL("http://www.somehost.com/cgi-bin/login.sh?User=anil&Password=letmein"));
Реализация CGI метода POST требует несколько больше возни. Так как этот метод требует передачи параметров через блок данных информации, посылаемой веб-верверу, вы должны писать данные-параметры в выходной поток объекта класса URLConnection
object как показано ниже:
URL url=new URL("http://www.somehost.com/cgi-bin/login.sh"); urlC=url.openConnection(); urlC.setDoOutput(true); DataOutputStream dos=new DataOutputStream(urlC.getOutputStream()); dos.writeBytes("User=anil&Password=letmein"); dos.writeBytes("\r\n");
После успешного использования одного из CGI методов вы можете прочитать результаты посланные CGI скриптом (если, конечно, использован не метод showDocument
) используя входной поток как показано здесь:
int oneChar; InputStream is=urlC.getInputStream(); while ((oneChar=is.read())!=-1) { System.out.print((char)oneChar); }
Имеете ли вы опыт программирования для интернета или нет, после чтения этой статьи вы наверняка поняли, что разработка клиент-серверный приложений на яве весьма проста. Если вам нравится программирование в сети, то вы будете довольны простотой работы с WWW ресурсами, которые доступны ява-программам почти также просто, как и локальные файлы в яве и других языках программирования. Так что - начинайте, и вы сами ощущите, как это здорово - соединяться с удалёнными программами, которые могут быть запущены в практически любой части света!
Эта статья первоначально была опубликована в Dr. Dobb's SourceBook, Sep/Oct 1996.