В контексте работы в сети используются такие термины, как клиент и сервер. Сервер – это все, что имеет некоторый разделяемый (коллективно используемый) ресурс. Существуют вычислительные серверы, которые обеспечивают вычислительную мощность; серверы печати, которые управляют совокупностью принтеров; дисковые серверы, которые предоставляют работающее в сети дисковое пространство, и Web-серверы, которые хранят Web-приложения. Клиент – любой другой объект, который хочет получить доступ к специфическому серверу. Сервер – это постоянно доступный ресурс, в то время как клиент может «отключиться» после того, как он был обслужен.
Различие между сервером и клиентом существенно только, когда клиент пытается подключиться к серверу. Как только они соединятся, происходит процесс двухстороннего общения, и не важно, что один является сервером, а другой – клиентом.
Работа сервера – слушать соединение, которое выполняется с помощью специального создаваемого серверного объекта (сокета), содержащего IP-адрес и номер порта. Работа клиента – попытаться создать соединение с сервером, которое выполняется с помощью клиентского сокета. Как только соединение установлено, соединение превращается в потоковый объект ввода/вывода. С этого момента можно рассматривать соединение как файл, который можно читать и в который можно записывать данные. Единственная особенность – файл может обладать определенным интеллектом и обрабатывать передаваемые команды.
Эти функции обеспечиваются расширением программы сетевой библиотеки java.net.*;.
Сокеты
Передача данных по сети – сложный процесс, включающий в себя определение пути доставки данных, организацию взаимодействия, алгоритмы синхронизации, обработки сбойных ситуаций и т.п. Программное обслуживание такого процесса сложное. Для упрощения введено понятие сокета (гнезда) как конечной точки коммуникации. Сокет (гнездо, разъем) – это программная абстракция, используемая для представления «терминалов» соединений между двумя машинами.
Каждый из сокетов определяется типом и ассоциированным с ним процессом. Реально для передачи организуются определенные дескрипторы TCP-соединения, так называемые гнезда (socket): гнездо сервера и гнездо клиента, которые в Internet-домене включают в себя IP-адреса сервера и клиента и номера портов, через которые они взаимодействуют. Сервер обычно имеет закрепленный и постоянный во взаимодействии номер порта, а клиенту, обращающемуся по этому номеру для связи к серверу, назначается некоторый другой (эфемерный) номер порта после установления соединения с сервером на сеанс их взаимодействия. Таким образом основной порт освобождается для установления последующих связей (номер порта выбирается сервером из числа не занятых в диапазоне от 1024 до 65 535). Эта комбинация (IP-адрес и номера портов) однозначно определяет отдельные сетевые процессы в сети Internet (номера портов до 1024, как правило, резервируются для широко известных приложений, например 80 – для связывания с серверами Web по протоколу HTTP).
Сокеты для работы в сети можно создать двух типов:
1) потоковые для TCP-соединения. TCP могут передавать даннные только между двумя приложениями, т. к. они предполагают наличие канала между этими приложениями;
2) дейтаграммные. Для дейтаграмм не нужно создавать канал, данные посылаются приложению с использованием адреса, состоящего из сокета и номера порта (в дейтаграммах не гарантируются доставка и корректность последовательности передачи пакетов). Для передачи дейтаграмм не нужны ни механизмы подтверждения связи, ни механизмы управления потоком данных.
Для упрощения представления такого соединения, представим себе сокет, размещенный на некоторой машине, и виртуальный «кабель», соединяющий две машины, каждый конец которого вставлен в сокет. Для TCP-соединений в Java используется два класса сокетов: ServerSocket – класс, используемый сервером, чтобы «слушать» входящие соединения, и Socket – используемый клиентом для инициирования соединения.
Сокеты TCP/IP серверов
Как было указано выше, для создания сокетов серверов используется класс ServerSocket. Указанный класс используется для создания серверов, которые прослушивают либо локальные, либо удаленные программы клиента, чтобы соединяться с ними на опубликованных портах.
Конструкторы класса ServerSocket:
ServerSocket(int port) – создает сокет сервера на указанном порте с длиной очереди по умолчанию 50.
ServerSocket(int port, int maxQueue) – создает сокет сервера на указанном порте с максимальной длиной очереди maxQueue.
ServerSocket(int port, int maxQueue, InetAddress localAddress) – создает сокет сервера на указанном порте с максимальной длиной очереди maxQueue.
Класс ServerSocket имеет метод accept(), который является блокирующим вызовом: сервер будет ждать клиента, чтобы инициализировать связь, и затем вернет нормальный Socket-объект, который будет использоваться для связи с клиентом.
Как только клиент создает соединение по сокету, ServerSocket возвращает с помощью метода accept() соответствующий клиенту объект Socket на сервере, по которому будет происходить связь со стороны сервера. Начиная с этого момента, появляется соединение сокет–сокет, и можно считать эти соединения одинаковыми, т.к. они действительно одинаковые.
ServerSocket httpServer=new ServerSocket(port);//создание сокета
//сервера
Socket reg=httpServer.accept(); //прослушивание (ожидание
// запроса на соединения)
// прием содержания соединения клиента
….
//отправка клиенту сообщения
…
//разрыв соединения
В представленном коде после установки соединения с клиентом метод accept() возвращает объект класса Socket (в данном случае reg), с помощью которого можно создавать и использовать байтовые и символьные потоки для обмена данными с клиентами. Для этого с гнездом связываются входной и выходной потоки, которые реализуются с помощью классов InputStream и OutputStream:
// получение входного и выходного потоков
InputStream inputstream = reg.getInputStream();
OutputStream outputstream = reg.getOutputStream();
Получив объекты, реализующие потоки, можно воспользоваться предоставляемыми ими методами, чтобы организовать взаимодействие по сети. Например, организовать чтение байта из входного потока можно при помощи метода read, а запись байта в выходной поток – с использованием метода write:
int c= inputstream.read(); // чтение байта из входного потока
outputstream. write(с); //запись байта в выходной поток
Сокеты TCP/IP клиентов
Для создания сокета клиента используется конструктор:
Socket(String hostname, int port) – создает сокет, соединяющий локальную хост-машину с именованной хост-машиной и портом; может выбрасывать исключение UnknownHostException или IOException.
Socket(InetAddress ipAddress, int port) – создает сокет, аналогичный предыдущему, но используется уже существующий объект класса InetAddres и порт; может выбрасывать исключение IOException.
Сокет может в любое время просматривать связанную с ним адресную и портовую информацию при помощи методов, представленных в таблице.
| Метод | Описание |
| InetAddress getInetAddress() | Возвращает InetAddress-объект, связанный с Socket-объектом |
| int getPort() | Возвращает удаленный порт, с которым соединен данный Socket-объект |
| int getLocalPort() | Возвращает локальный порт, с котрым соединен данный Socket-объект |
После создания Socket-объект можно применять для получения доступа к связанным с ним потокам ввода/вывода. Данные потоки используются точно так же, как потоки ввода/вывода.
Рассмотрим пример, в котором необходимо создать приложение клиент-сервер. Клиент считывает строку с клавиатуры, отображает ее на экране, передает ее серверу, сервер отображает ее на экране, переводит в верхний регистр и передает клиенту, который, в свою очередь, снова отображает ее на экране.
Программа сервера:
import java.io.*;//импорт пакета, содержащего классы для
//ввода/вывода
import java.net.*;//импорт пакета, содержащего классы для работы в
//сети Internet
public class server
{public static void main(String[] arg)
{//объявление объекта класса ServerSocket
ServerSocket serverSocket = null;
Socket clientAccepted = null;//объявление объекта класса Socket
ObjectInputStream sois = null;//объявление байтового потока ввода
ObjectOutputStream soos = null;//объявление байтового потока вывода
try {
System.out.println("server starting....");
serverSocket = new ServerSocket(2525);//создание сокета сервера для
//заданного порта
clientAccepted = serverSocket.accept();//выполнение метода, который
//обеспечивает реальное подключение сервера к клиенту
System.out.println("connection established....");
//создание потока ввода soos = new
sois = new ObjectInputStream(clientAccepted.getInputStream());
ObjectOutputStream(clientAccepted.getOutputStream());//создание потока
//вывода
String clientMessageRecieved = (String)sois.readObject();//объявление
//строки и присваивание ей данных потока ввода, представленных
//в виде строки (передано клиентом)
while(!clientMessageRecieved.equals("quite"))//выполнение цикла: пока
//строка не будет равна «quite»
{
System.out.println("message recieved: '"+clientMessageRecieved+"'");
clientMessageRecieved = clientMessageRecieved.toUpperCase();//приведение символов строки к
//верхнему регистру
soos.writeObject(clientMessageRecieved);//потоку вывода
//присваивается значение строковой переменной (передается клиенту)
clientMessageRecieved = (String)sois.readObject();//строке
//присваиваются данные потока ввода, представленные в виде строки
//(передано клиентом)
} }catch(Exception e) {
} finally {
try {
sois.close();//закрытие потока ввода
soos.close();//закрытие потока вывода
clientAccepted.close();//закрытие сокета, выделенного для клиента
serverSocket.close();//закрытие сокета сервера
} catch(Exception e) {
e.printStackTrace();//вызывается метод исключения е
}
}
}
}
Программа клиента:
import java.io.*;//импорт пакета, содержащего классы для
// ввода/вывода
import java.net.*;//импорт пакета, содержащего классы для
// работы в сети
public class client {
public static void main(String[] arg) {
try {
System.out.println("server connecting....");
Socket clientSocket = new Socket("127.0.0.1",2525);//установление
//соединения между локальной машиной и указанным портом узла сети
System.out.println("connection established....");
BufferedReader stdin =
new BufferedReader(new InputStreamReader(System.in));//создание
//буферизированного символьного потока ввода
ObjectOutputStream coos =
new ObjectOutputStream(clientSocket.getOutputStream());//создание
//потока вывода
ObjectInputStream cois =
new ObjectInputStream(clientSocket.getInputStream());//создание
//потока ввода
System.out.println("Enter any string to send to server \n\t('quite' − programme terminate)");
String clientMessage = stdin.readLine();
System.out.println("you've entered: "+clientMessage); while(!clientMessage.equals("quite")) {
//выполнение цикла, пока строка //не будет равна «quite»
coos.writeObject(clientMessage);//потоку вывода присваивается
//значение строковой переменной (передается серверу)
System.out.println("~server~: "+cois.readObject());//выводится на
//экран содержимое потока ввода (переданное сервером)
System.out.println("---------------------------");
clientMessage = stdin.readLine();//ввод текста с клавиатуры
System.out.println("you've entered: "+clientMessage);//вывод в
//консоль строки и значения строковой переменной
}
coos.close();//закрытие потока вывода
cois.close();//закрытие потока ввода
clientSocket.close();//закрытие сокета
}catch(Exception e) {
e.printStackTrace();//выполнение метода исключения е
}
}
}
Для запуска приведенного кода сервера и клиента сначала необходимо запустить приложение сервера, потом – приложение клиента.
После запуска сервера на экране появится консольное окно сервера со строкой:
server starting ….
server connecting….
connection established ….
Enter any string to send to server
<”quite”– programme terminate>
После установления соединения на сервере появляется еще одна строка, свидетельствующая об этом:
connection established….
В окне сервера появятся следующие строки:
message recieved: 'Hello USER'
В окне клиента появятся дополнительные строки:
you've entered: Hello USER
~server~: HELLO USER
---------------------------





