Современные фреймворки достаточно хорошо абстрагированы от низкоуровневых деталей собственной реализации. С одной стороны это хорошо - упрощает нам жизнь, но с другой стороны не дает нам лишнего повода узнать фундаментальные вещи. Это справедливо для огромного количества вопросов и областей информационных технологий, но в этом цикле статей речь пойдет о протоколе http (часто скрываемом за JSP, JSF и др.) и способах его использования в Java.
Http - протокол взаимодействия двух программ, клиента и сервера, которые могут быть запущены на совершенно разных и очень удаленных друг от друга машинах. Клиентом называют приложение, которое пользуется каким-то сервисом, предоставляемым другим приложением — Сервером, обычно на удаленном компьютере. Практически всегда клиент начинает исходящие соединения, а сервер ожидает входящих соединений (от клиентов), хотя бывают и исключения.
Первое, что надо понять - это способы взаимодействия клиента и сервера. Как они друг друга находят, и как обмениваются данными. Для этого давайте вспомним младшие курсы университета и освежим свои знания о сетевых протоколах. Если аббревиатуры OSI и TCP/IP не вводят вас в тупик, смело листайте дальше. Для тех же, кто старается не захламлять голову деталями, справедливо полагаясь на их доступность в Интернете, напоминаю:
Модель OSI была предложена Международной организацией стандартов ISO (International Standards Organization) в 1984 году. Все сетевые функции в модели разделены на 7 уровней:
Распределение протоколов по уровням модели OSI 7 Прикладной напр., HTTP, SMTP, SNMP, FTP, Telnet, SSH, SCP, SMB, NFS, RTSP, BGP 6 Представления напр., XDR, AFP, TLS, SSL 5 Сеансовый напр., ISO 8327 / CCITT X.225, RPC, NetBIOS, PPTP, L2TP, ASP 4 Транспортный напр., TCP, UDP, SCTP, SPX, RTP, ATP, DCCP, GRE 3 Сетевой напр., IP, ICMP, IGMP, CLNP, OSPF, RIP, IPX, DDP, ARP, RARP 2 Канальный напр., Ethernet, Token ring, HDLC, PPP, X.25, Frame relay, ISDN, ATM, MPLS 1 Физический напр., электрические провода, радиосвязь, волоконно-оптические провода, Wi-Fi
При этом вышестоящие уровни выполняют более сложные, глобальные задачи, для чего используют в своих целях нижестоящие уровни, а также управляют ими. Цель нижестоящего уровня – предоставление услуг вышестоящему уровню, причем вышестоящему уровню не важны детали выполнения этих услуг. Нижестоящие уровни выполняют более простые и конкретные функции. В идеале каждый уровень взаимодействует только с теми, которые находятся рядом с ним (выше и ниже него). Верхний уровень соответствует прикладной задаче, работающему в данный момент приложению, нижний – непосредственной передаче сигналов по каналу связи.
На сегодняшний день основным стеком протоколов является TCP/IP. Этот стек протоколов появился до OSI и успел завоевать популярность.
Распределение протоколов по уровням модели TCP/IP 5 Прикладной
«7 уровень»напр., HTTP, RTP, FTP, DNS
(RIP, работающий поверх UDP, и BGP, работающий поверх TCP, являются частью сетевого уровня)4 Транспортный напр., TCP, UDP, SCTP, DCCP
(протоколы маршрутизации, подобные OSPF, что работают поверх IP, являются частью сетевого уровня)3 Сетевой Для TCP/IP это IP (IP)
(вспомогательные протоколы, вроде ICMP и IGMP, работают поверх IP, но тоже относятся к сетевому уровню; протокол ARP является самостоятельным вспомогательным протоколом, работающим поверх физического уровня)2 Канальный Ethernet, IEEE 802.11 Wireless Ethernet, SLIP, Token Ring, ATM и MPLS 1 Физический напр., физическая среда и принципы кодирования информации, T1, E1
В обеих моделях http находится на прикладном уровне и использует для собственной реализации протоколы более низких уровней. Рассмотреть все уровни - задача далеко выходящая за рамки одной статьи. Поэтому здесь я ограничусь лишь кратким описанием одного из протоколов транспортного уровня, а именно TCP, на который непосредственно опирается http.
Согласно протоколу IP, каждый узел в сети имеет свой IP-адрес, состоящий из 4х байт и обычно записываемый как
n.n.n.n
Каждый узел напрямую «видит» только узлы в своей подсети.TCP протокол базируется на IP для доставки пакетов, но добавляет две важные вещи:
- установление соединения — это позволяет ему, гарантировать доставку пакетов
- порты — для обмена пакетами между приложениями, а не просто узлами
Таким образом, для идентификации машины в сети используется протокол IP и 4х байтный уникальный идентификатор машины - ip адрес. Далее, для идентификации приложения, TCP добавляет понятие порта. Порт - это целое число от 1 до 65535 указывающее, какому приложению предназначается пакет. В TCP пакетах указываются порт источника(клиента) и порт назначения(сервера).
Сервер при запуске сообщает операционной системе, что хотел бы «занять» определенный порт (или несколько портов). После этого все пакеты, приходящие на компьютер к этому порту, ОС будет передавать этому серверу. Говорят, что сервер «слушает» этот порт.
Клиент, начиная соединение, запрашивает у своей ОС какой-нибудь незанятый порт во временное пользование, и указывает его в посланных пакетах как порт источника. Затем на этот порт он получит ответные пакеты от сервера.
Еще одним, очень важным понятием в сетевом взаимодействии, является Сокет (socket - генздо, панель). На самом деле, это не новое понятие, а совокупность старых: ip-адреса и порта.
Вики призывает различать клиентские и серверные сокеты:
Следует различать клиентские и серверные сокеты. Клиентские сокеты грубо можно сравнить с оконечными аппаратами телефонной сети, а серверные — с коммутаторами. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты.
На этом, мой поверхностный экскурс в сетевые технологии, не претендующий на исчерпывающее освещение столь сложного и обширного вопроса, закончен. Для расширения представления о сетевых взаимодействиях читайте специализированные ресурсы, например intuit или загляните в wiki.
Чтож, пришла пора попытаться воспользоваться знаниями и перейти от слов к джаве =)
Ключевыми классами для реализации взаимодействия программ по TCP являются java.net.Socket и java.net.ServerSocket.
- java.net.Socket- этот класс реализует клиентские сокеты. Сокет представляет собой конечную точку для связи между двумя машинами.
- java.net.ServerSocket - этот класс реализует серверный сокет. Серверный сокет ожидает запросы, приходящие по сети от клиентов и может, при необходимости отправлять ответ.
Ниже приведены коды простейших TCP сервера и клиента. Сервер транслирует на консоль все, что ему приходит от клиента.
Сервер:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Слушает указанный в первом параметре порт и выводит все приходящие на него
* сообщения на консоль.
*/
public class TcpServer {
public static void main(String[] args) {
/* Если аргументы отсутствуют, порт принимае значение поумолчанию */
int port = DEFAULT_PORT;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
/* Создаем серверный сокет на полученном порту */
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
System.out.println("Порт занят: " + port);
System.exit(-1);
}
/*
* Если порт был свободен и сокет был успешно создан, можно переходить к
* следующему шагу - ожиданию клинта
*/
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
} catch (IOException e) {
System.out.println("Ошибка при подключении к порту: " + port);
System.exit(-1);
}
/*
* Теперь можно получить поток ввода, в который помещаются сообщения от
* клиента
*/
InputStream in = null;
try {
in = clientSocket.getInputStream();
} catch (IOException e) {
System.out.println("Не удалось получить поток ввода.");
System.exit(-1);
}
/*
* В этой реализации сервер будет без конца читать поток и выводить его
* содержимое на консоль
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String ln = null;
try {
while ((ln = reader.readLine()) != null) {
System.out.println(ln);
System.out.flush();
}
} catch (IOException e) {
System.out.println("Ошибка при чтении сообщения.");
System.exit(-1);
}
}
private static final int DEFAULT_PORT = 9999;
}
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* Подключается к серверу и передает ему сообщения вводимые пользователем в
* консоли. Хост сервера указвается первым аргументом, порт вторым. При
* отсутствии аргументов в качестве адреса порта принимается localhost:9999
*/
public class TcpClient {
public static void main(String[] args) {
/* Определяем хост сервера и порт */
String host = DEFAULT_HOST;
int port = DEFAULT_PORT;
if (args.length > 0) {
host = args[0];
}
if (args.length > 1) {
port = Integer.parseInt(args[1]);
}
/*
* Создаем сокет для полученной пары хост/порт
*/
Socket socket = null;
try {
socket = new Socket(host, port);
} catch (UnknownHostException e) {
System.out.println("Неизвестный хост: " + host);
System.exit(-1);
} catch (IOException e) {
System.out.println("Ошибка ввода/вывода при создании сокета " + host
+ ":" + port);
System.exit(-1);
}
/*
* Для удобства обернем стандартный поток ввода в BufferedReader
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(
System.in));
/*
* Получаем поток вывода, через который будут передаваться сообщения
* серверу
*/
OutputStream out = null;
try {
out = socket.getOutputStream();
} catch (IOException e) {
System.out.println("Не удалось получить поток вывода.");
System.exit(-1);
}
/*
* Все вводимые пользователем сообщения будем транслировать в поток вывода
* созданного сокета
*/
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
String ln = null;
try {
while ((ln = reader.readLine()) != null) {
writer.write(ln + "\n");
writer.flush();
}
} catch (IOException e) {
System.out.println("Ошибка при записи сообщения.");
System.exit(-1);
}
}
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 9999;
}
Коды сервера и клиента достаточно просты и вряд ли вызовут затруднения. Главное, что необходимо для себя уяснить, что сокеты позволяют создать прозрачное соединение между двумя удаленными приложениями, после чего передача между ними данных осуществляется не сложнее записи/чтения обычного потока.
На мой взгляд, понимание TCP является минимальной базой для понимания HTTP. В следующей части я постараюсь дать минимальное представление непосредственно о протоколе Http и стандартном API в Java для его реализации.
UPD: Добавил код сервера, который так опрометчиво забыл опубликовать. Плюс, теперь доступен репозиторий, для цикла статей об http в java: https://bitbucket.org/dok/http
12 комментариев:
улыбнула ссылка на статью в википедии "электрический провод"...ах да, это первая статья, которую дочитал до конца
Было бы интересно увидеть реализацию такого, чтобы на веб-странице например можно было строить график функции синуса (или другой функции) в определенном промежутке (польователь задает начало и конец отрезка).
Подобные сервисы давно существуют. Вот пример: http://school35.ucoz.ru/grapher/grapher_e.htm
ну ты капитан-очевидность)))а вот как его замутить, и чтоб работал, вот в чем загадка, по крайней мере для меня)))я как-то писал на php программки вот и заинтересовался
Да, я замечаю за собой периодически =) Вообще, в простейшей реализации ничего сложного быть не должно. Забираем аргументы у пользователя, на сервере строим картинку с графиком исходя из аргументов, возвращаем ее пользователю. С сегодняшними возможностями html5, вероятно(веб не моя специализация), и сервер подключать к решению задачи не придется.
Было бы круто, если ты напишешь об этом в блоге. Заодно контент у тебя прибавиться)))
у меня пока есть идеи для статей и нет свободного времени. Возможно, когда реализуются первые и появится последнее... Но ведь ты все равно ждешь код на php, а я могу предложить только java.
Меня интересует реализация идеи, пусть хоть и на java)))
Написание кода, подключение модуля к веб-странице и его работа
спасибо, очень доходчиво написано
Добрый день.
А ведь потоки надо закрывать?
Большое спасибо. Пока не разобрался, но скопировал и работает
Отправить комментарий