Главная » Java, Web » Сокеты. Клиенты. Серверы

0

Связь в сети Интернет осуществляется на основе двух протоколов — TCP (Transmission Control Protocol) и IP (Internet Protocol), оба протокола объединяются в стек-протокол — TCP/IP. Помимо этого часто используется протокол UDP (User Datagram Protocol — протокол пользовательских дата- грамм). Если две программы должны общаться друг с другом посредством протокола TCP/IP, то в каждой программе должен быть создан сокет. Затем сокеты соединяются друг с другом, устанавливая связь между программами. После того как связь будет создана, общение происходит на основе потоков ввода и вывода. Данные, записанные программой в поток вывода, передаются другой программе, которая может быть расположена на другом компьютере. При чтении программы данных из потока, эти данные получаются потоком из другой программы.

Создание связи на основе сокетов происходит по следующей схеме. Одна программа создает сокет, который пассивно слушает, ожидая запросы от другого сокета. Другая программа создает сокет, который посылает запрос на соединение прослушивающему сокету. По получении запроса прослушивающий порт отвечает и устанавливается соединение. После этого программы могут обмениваться данными, то есть записывать в поток и читать из потока. Это может происходить до тех пор, пока одна из программ не закроет связь.

Программа, которая создает прослушивающий сокет, часто называется сервером, а соответвующий сокет называется серверным сокетом. Программа, которая подключается к серверу, создавая запросы, называется клиентом, она работает с клиентским сокетом. Сервер, как правило, предлагает тот или иной сервис, а клиент обращается к серверу за услугами, предоставляемыми этим сервисом. Такая модель носит название клиент-серверной модели связи. Когда клиент подсоединяется к серверу, прослушивающий сокет не прекращает ожидание запросов от клиентов. Сервер продолжает прослушивать возможные запросы от других клиентов. Чтобы была возможность осуществлять одновременную работу с несколькими клиентами, необходимо использовать потоки.

Класс url использует сокеты, но сокеты при этом скрыты. При работе с классом url на другой стороне взаимодействия сокетов располагается серверная программа, как правило, Web-cepeep, который обрабатывает запросы и посылает документ, расположенный по указанному URL (если документ существует). По окончании пересылки данных соединение закрывается.

Для работы с TCP/IP-соединениями в Java существуют классы serversocket и socket. Класс serversocket — это серверный сокет, ожидающий запросы на соединение от клиентов. Класс socket используется для создания клиентских сокетов, запрашивающих сервер. Класс socket может быть использован также и сервером для обработки запросов на соединение от клиента. Это позволяет серверу работать с несколькими сокетами, создавая несколько соединений одновременно. Объект типа serversocket сам по себе не участвует в соединении, он только прослушивает, ожидая запрос на соединение, затем он создает Socket, который используется для работы соединения как такового.

Тот и другой вид сокетов требует задания адресов в сети Интернет. Каждый компьютер имеет IP-адрес. Многие компьютеры могут быть опознаны по доменному имени, например, www.yahoo.com. На каждом компьютере может быть установлено несколько программ. Чтобы иметь возможность выбрать конкретную программу, используются дополнительные адреса — порты. Номер порта — это 16-битное целое число. Сервер не просто прослушивает в ожидании запроса на установление связи, он прослушивает конкретный порт. При обращении к серверу клиент должен знать Интернет-адрес (или доменное имя) и номер порта. Стандартные сервера работают по стандартным протоколам. С ними, как правило, связаны конкретные порты. Так, обычные Web-cepeepa прослушивают порт 80. Все стандартные номера портов являются числами, меньшими 1024. Для детального знакомства с протоколами TCP и UDP можно обратиться к документам RFC768, RFC793. В таблицах 2.1 и 2.2 приведен список наиболее употребительных стандартных номеров портов для протоколов TCP и UDP и связанных с ними протоколов, которые работают поверх протоколов TCP и UDP.

Таблица 2.1. Основные группы портов для сокетов

Номер порта

Номер порта

Описание

десятичныи

восьмеричныи

 

0-63

0-77

Общесетевой стандарт

64-131

100-203

Специальные функции отдельных хостов

132-223

204-337

Зарезервированы

224-255

340-377

Для экспериментального использования

Таблица 2.2. Наиболее употребительные порты TCP и UDP

(по классификации 1980 года)

Десятичный порт

Восьмеричный порт

Описание

3

3

Для передачи файлов

13

15

Дата и время

15

17

Whois

17

21

Короткие текстовые сообщения

19

23

Генератор символов

21

25

Новая передача файлов ftp

23

27

Новый Telnet

25

31

SMTP

27

33

NSW User System w/COMPASS

29

35

MSG-3 ICP

31

37

MSG-3 Authentication

37

45

Time Server

41

51

Graphics

42

52

Name Server

43

53

Whols

В таблице мы не увидим многих привычных сегодня протоколов. Это и неудивительно, в начале 80-х годов XX века многие из них еще только зарождались. Более подробный и полный список протоколов и стандартных номеров портов приведен в таблице 2.3. Номер порта указан в начале каждой строки, за номером порта следует протокол, который используется для работы на этом порте (TCP и/или UDP), затем идет назначение порта.

Таблица 2.3. Некоторые порты для протоколов tcp и udp и связанные с ними протоколы и службы

Порт/протоколы/описание

Порт/протоколы/описание

П

0/tcp/udp Reserved

П

11/tcp/udp Active Users

П

1/tcp TCP Port Service Multiplexer

П

13/tcp/udp Daytime

П

2/tcp Management Utility

П

17/tcp/udp Quote of the Day

П

3/tcp Compression Process

П

18/tcp/udp RWP rwrite

П

5/tcp Remote Job Entry

П

18/tcp/udp Message Send Protocol

П

7/tcp/udp Echo

П

19/tcp/udp Character Generator

Таблица 2.3 (окончание)

Порт/протоколы/описание

Порт/протоколы/описание

П

20/tcp File Transfer [Default Data]

П

433/tcp/udp NNSP

П

21/tcp File Transfer [Control]

П

433/udp NNSP

п

23/tcp Telnet

П

434/tcp MobilelP-Agent

п

24/tcp/udp any private mail system

П

435/tcp MobillP-MN

п

25/tcp Simple Mail Transfer

П

439/tcp/udp dasp Thomas Obermair

п

37/tcp/udp Time

П

453/tcp/udp CreativeServer

п

38/tcp/udp Route Access Protocol

П

454/tcp/udp ContentServer

п

39/udp Resource Location Protocol

П

455/tcp/udp CreativePartnr

п

41/tcp/udp Graphics

П

512/tcp/udp remote process execution

п

42/udp Host Name Server

П

513/tcp/udp remote login a la telnet

п

43/tcp Who Is

П

514/tcp/udp like exec, but automatic

п

53/tcp/udp Domain Name Server

П

515/tcp spooler

п

70/tcp Gopher

П

517/udp talk

п

79/tcp Finger

П

518/tcp ntalk

п

80/tcp World Wide Web HTTP

П

519/tcp/udp unixtime

п

106/tcp Password Server

П

520/tcp/udp extended file name server

п

107/tcp Remote Telnet Service

П

525/tcp/udp timeserver

п

117/tcp UUCP Path Service

П

526/tcp/udp newdate

п

118/tcp/udp SQL Services

П

530/tcp/udp rpc

п

150/tcp/udp SQL-NET

П

531/tcp/udp chat

п

156/tcp SQL Service

П

532/tcp/udp readnews

п

396/tcp/udp Novell Netware over IP

П

532/udp readnews

п

397/tcp/udp Multi Protocol Trans. Net

П

533/tcp/udp for emergency broadcasts

п

414/tcp/udp InfoSeek

П

540/tcp uucpd

п

420/udp SMPTE

П

541/tcp/udp uucp-rlogin

п

425/tcp ICAD

П

565/tcp/udp whoami

п

426/tcp/udp smartsdp

П

751/tcp/udp pump

п

427/tcp/udp Server Location

 

 

Во время создания сокета следует задать номер порта, который будет прослушивать сервер. Конструктор выглядит следующим образом:

public ServerSocket(int port) throws IOException

После того как создан сокет serversocket, он приступает к прослушиванию и ожиданию запросов на соединение. Метод accept о класса serversocket принимает запрос на соединение, образует соединение с клиентом и возвращает сокет, который будет использоваться для коммуникации с клиентом. Метод accept () выглядит следующим образом: public Socket accept() throws IOException

После вызова метода accept о от него не будет никакого отклика до тех пор, пока не будет получен запрос на соединение или пока не возникнет какая-либо ошибка. В то время, пока метод блокирован, поток, вызвавший метод, не может продолжать работу. Другие же потоки продолжают выполняться независимо. Объект serversocket будет прослушивать до тех пор, пока не будет вызван метод close о (или если произойдет ошибка). В качестве небольшого примера запишем фрагмент, в котором сервер прослушивает порт 1728, а метод provideService (Socket) служит для осуществления коммуникации с одним клиентом. Базовый вариант серверной программы выглядит следующим образом: try {

ServerSocket server = new ServerSocket(1728); while (true) {

Socket connection = server.accept() ;

provideService(connection); }

}

catch (IOException e) {

System.out.println("Server shut down with error: " + e);

}

На клиентской части сокет создается с использованием конструктора класса socket, при этом необходимо знать адрес компьютера и номер порта:

public Socket(String computer, int port) throws IOException

Первый параметр — это либо : -адрес, либо доменное имя. Этот конструктор блокируется до тех пор, пока не будет установлена связь или пока не возникнет ошибка. После создания связи вызываются методы getlnputStream () и getOutputStream() класса Socket, эти методы используются для работы с потоками. Например:

void doClientConnection(String computerName, int listeningPort) { // computerName — доменное имя или IP-адрес; port — номер порта Socket connection;

InputStream in; OutputStream out; try {

connection = new Socket(computerName,listeningPort); in = connection.getInputStream(); out = connection.getOutputStream();

}

catch (IOException e) { System.out.println(

"Attempt to create connection failed with error: " + e); return;

try {

connection.close (); catch (IOException e) {

На практике ситуация несколько осложняется тем, что возникают ошибки в сетях и ошибки, обусловленные человеческим фактором.

Здесь мы рассмотрели базовые идеи, которые дают представление о методах программирования при работе с сетью. Далее мы рассмотрим некоторые работающие примеры, использующие технологию клиент/серверного программирования.

В качестве примера рассмотрим программы, описывающие клиент и сервер. Клиент образует связь с сервером, читает строку текста, присылаемую с сервера, и показывает ее на экране. Посылаемый сервером текст состоит из текущей даты и времени в соответствии с системным временем компьютера, на котором работает сервер. Сервер будет слушать на порте 32007. Программа приведена в файле DateClient.java (листинг 2.8).

Листинг 2.8. Файл DateClient.java

impo гt j ava.net.*; import java.io.*; public class DateClient {

static final int LISTENING PORT = 32007;

public static void main(String[] args) {

String computer;         // имя компьютера, к которому

// устанавливается соединение Socket connection; // сокет Reader incoming;       // поток

// получаем имя компьютера из командной строки if (args.length > 0)

computer = args[0]; else {

// имя компьютера не задано, печатаем сообщение и // останавливаемся

System.out.println("Usage: java DateClient <server>"); return;

}

// устанавливаем связь и читаем строку текста, затем показываем ее try {

connection = new Socket(computer, LISTENING_PORT);

incoming = new InputStreamReader(connection.getlnputStream());

while (true) {

int ch = incoming.read() ; if (ch == -1 || ch == ‘\n’ || ch == ‘\r’) breaks-

System, out. print ( (char)ch) ;

}

System.out.println(); incoming.close();

}

catch (IOException e) {

TextlO.putln("Error: " + e);

}

Весь процесс установки связи происходит в блоке try.. .catch. Конец строк соответствует символам ‘\п’ или ‘\г’. Серверная программа для работы с этим клиентом (листинг 2.9) названа DateServer (рис. 2.6 и 2.7).

Листинг 2.9. Файл DateServer.java

impo гt j ava.net.*; import java.io.*;

import java.util.Date; public class DateServer {

static final int LISTENING_PORT = 32007; public static void main(String[] args) {

ServerSocket listener; // прослушивает запросы на соединение Socket connection;       // для взаимодействия с другими

// программами

try {

listener = new ServerSocket(LISTENING_PORT); TextlO.putln("Listening on port " + LISTENING_PORT); while (true) {

connection = listener.accept(); sendDate(connection);

}

}

catch (Exception e) {

TextlO.putln("Sorry, the server has shut down.");

TextlO.putln("Error: " + e) ;

return;

}

}

static void sendDate(Socket client) { try {

System.out.println("Connection from " +

client.getlnetAddress().toString()); Date now = new Date(); // дата и время PrintWriter outgoing; // поток вывода outgoing = new PrintWriter(client.getOutputStream()); outgoing.printIn(now.toString()) ; outgoing.flush(); // проверка посылки данных client.close() ;

}

catch (Exception e){

System.out.println("Error: " + e);

Рис. 2.6. Клиент

 

Рис. 2.7. Сервер

После вызова метода out .println о мы используем метод out. flush (). Метод flush о доступен во всех потоках вывода. С его помощью данные посылаются по сетевому соединению. Если мы не используем этот метод, то после записи данных в поток вывода реальные данные могут быть не посланы, поток будет дожидаться поступления достаточного количества данных, и лишь затем они будут посланы. Избежать задержки помогает метод flush о .

В предыдущем примере сервер посылал строку клиенту — этим все заканчивалось. Можно создать такие клиент и сервер, которые могут обмениваться сообщениями, например, из командной строки (листинг 2.10 и 2.11). Сервер закрывает прослушиватель порта по получении первого запроса на соединение. Сервер не сможет установить более одного соединения. После установления соединения клиент и сервер ведут себя схожим образом, позволяя друг другу обмениваться сообщениями (рис. 2.8).

Рис. 2.8. Общение между клиентом и сервером

Листинг 2.10. Файл ChatClient.java

impo гt j ava.net.*; import java.io.*;

public class ChatServer {

static final int DEFAULT_PORT = 1728; // номер порта;

// используется, если порт //не задан в командной строке static final String HANDSHAKE = "CLChat"; // строка приветствия // каждый участник связи посылает эту строку после

// установления связи для того, чтобы быть // уверенным, что "собеседником" является "своя" // программа

static final char MESSAGE = ‘0’; // этот символ посылается в

// начале каждого сообщения static final char CLOSE = Ч'; // этот символ посылается при

// выходе пользователя public static void main(String[] args) { int port;

ServerSocket listeners-

Socket connection; // сокет для связи TextReader incoming; // поток от клиента PrintWriter outgoing; // поток к клиенту String messageOut; // сообщение клиенту String messageln;          // сообщение от клиента

/*

Получить номер порта из командной строки, если его нет, то использовать порт по умолчанию. */

if (args.length == 0)

port = DEFAULT_PORT; else { try {

port = Integer.parselnt(args[0]); if (port < 0 || port > 65535)

throw new NumberFormatException();

}

catch (NumberFormatException e) {

TextlO.putln("Illegal port number, " + args[0]); return;

}

}

/*

Ожидание запроса на соединение. После получения запроса закрываем прослушиватель запросов, создаем потоки и обмениваемся строками приветствия. */

try {

listener = new ServerSocket(port);

TextlO.putln("Listening on port " + listener.getLocalPort ()); connection = listener.accept(); listener.close();

incoming = new TextReader(connection.getlnputStream()) ;

outgoing = new PrintWriter(connection.getOutputStream());

outgoing.println(HANDSHAKE);

outgoing.flush() ;

messageln = incoming.getIn();

if (! messageln.equals(HANDSHAKE)) {

throw new IOException("Connected program is not Chat!");

}

TextlO.putln("Connected. Waiting for the first message.\n");

}

catch (Exception e) {

Text10.putIn("An error occurred while opening connection.");

TextlO.putln(e.toString ());

return;

}

// обмен сообщениями try {

while (true) { TextlO.putln("WAITING…"); messageln = incoming.getIn(); if (messageln.length() >0) {

// Первый символ сообщения — это команда. Если // приходит команда CLOSE, то связь закрывается. //В противном случае первый // символ просто удаляется. if (messageln.charAt(0) == CLOSE) {

TextlO.putln("Connection closed at other end.");

connection.close();

break;

}

messageln = messageln.substring(1);

}

TextlO.putln("RECEIVED: " + messageln); Text10.put("SEND:      ");

messageOut = TextlO.getIn(); if (messageOut.equalsIgnoreCase("quit")) {

// пользователь хочет выйти

outgoing.println(CLOSE);

outgoing.flush();

connection.close();

TextlO.putln("Connection closed.");

break;

}

outgoing.println(MESSAGE + messageOut); outgoing.flush(); if (outgoing.checkError()) { throw new IOException(

"Error occurred while transmitting message.");

}

}

catch (Exception e) {

TextlO.putln("Sorry, an error has occurred.

Connection lost."); TextlO.putln(e.toString ()); System.exit (1);

}

}

Листинг 2.11. Файл ChatServer.java

impo rt j ava.net.*; import java.io.*; public class ChatServer { // если порт не указан

//в командной строке, то используется порт, указанный здесь static final int DEEAULT_PORT = 1728;

static final String HANDSHAKE = "CLChat"; // строка приветствия // Каждая программа посылает строку приветствия, // тем самым подтверждая, что она является // приложением Chat. Это своеобразный "пароль", static final char MESSAGE = ‘0’; // приставка к каждому сообщению static final char CLOSE =4′; // этот символ посылается в качестве

// приставки, когда пользователь // выходит из программы

public static void main(String[] args) { int port; // Порт.

ServerSocket listener; // слушает запросы на соединение Socket connection;    // для связи с клиентом

TextReader incoming; // поток данных от клиента PrintWriter outgoing; // поток данных к клиенту String messageOut;    // сообщение клиенту

String messageln;             // сообщение от клиента

/*

Получаем номер порта из командной строки, если он не указан, то используем порт по умолчанию. */

if (args.length == 0)

port = DEFAULT_PORT; else { try {

port = Integer.parselnt(args[0]); if (port < 0 || port > 65535)

throw new NumberFormatException();

}

catch (NumberFormatException e) {

TextlO.putln("Illegal port number, " + args[0] ) ; return;

}

}

/*

Ожидаем запрос на соединение.

После получения запроса закрываем слушающий сокет, создаем связь и посылаем строку приветствия. */

try {

listener = new ServerSocket(port);

TextlO.putln("Listening on port " + listener.getLocalPort()); connection = listener.accept(); listener.close();

incoming = new TextReader(connection.getlnputStream()); outgoing = new PrintWriter(connection.getOutputStream()); outgoing.println(HANDSHAKE); outgoing.flush();

messageln = incoming.getIn();

if (! messageln.equals(HANDSHAKE)) {

throw new IOException("Connected program is not CLChat!") ;

}

TextlO.putln("Connected. Waiting for the first message.\n");

}

catch (Exception e) {

Text10.putIn("An error occurred while opening connection.");

TextlO.putln(e.toString());

return;

}

// обмен сообщениями try {

while (true) {

TextlO.putln("WAITING…"); messageln = incoming.getIn(); if (messageln.length() >0) {

// Первый символ сообщения — команда. // Если получен смивол CLOSE, то соединение // закрывается. В противном случае // первый символ просто удаляется. if (messageln.charAt(0) == CLOSE) {

TextlO.putln("Connection closed at other end.");

connection.close ();

break;

}

messageln = messageln.substring(1);

}

TextlO.putln("RECEIVED: " + messageln); Text10.put("SEND:      ");

messageOut = TextlO.getIn(); if (messageOut.equalsIgnoreCase("quit")) { // Пользователь выходит. // Информируем об этом собеседника. outgoing.println(CLOSE); outgoing.flush(); connection.close() ; TextlO.putln("Connection closed."); break;

}

outgoing.println(MESSAGE + messageOut); outgoing.flush(); if (outgoing.checkError()) {

throw new IOException("Error occurred while transmitting

message.");

}

}

}

catch (Exception e) {

TextlO.putln("Sorry, an error has occurred. Connection lost."); TextlO.putln(e.toString()); System.exit(1);

}

}

Рис. 2.9. Сервер (листинг 2.11) и клиент telnet на порте 777

Этот пример несколько более приближен к действительности. В нем мы также используем строку приветствия, которая в нашем случае является частью протокола. Можно поэкспериментировать с сервером, подключившись к работающему серверу, например, с помощью клиента Telnet (рис. 2.9). Можно попытаться соединиться с сервером из браузера. Можно также запустить программу-клиент и попытаться установить соединение с каким- либо другим сервером. Проблема лишь в том, что протоколы общения клиентов с серверами будут отличаться друг от друга. Для эффективной коммуникации необходимо учитывать эти протоколы, то есть общение должно происходить в рамках установленного протокола.

Источник: Будилов В. А. Интернет-программирование на Java. — СПб.: БХВ-Петербург, 2003. — 704 е.: ил.

По теме:

  • Комментарии