Современные фреймворки достаточно хорошо абстрагированы от низкоуровневых деталей собственной реализации. С одной стороны это хорошо - упрощает нам жизнь, но с другой стороны не дает нам лишнего повода узнать фундаментальные вещи. Это справедливо для огромного количества вопросов и областей информационных технологий, но в этом цикле статей речь пойдет о протоколе 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.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; /** * Слушает указанный в первом параметре порт и выводит все приходящие на него * сообщения на консоль. * * @author brovko_rs */ public class TcpServer { public static void main(String[] args) { /* Если аргументы отсутствуют, порт принимает значение по умолчанию */ int port = DEFAULT_PORT; if (args.length > 0) { port = Integer.parseInt(args[0]); } /* Установка кодировки консоли */ String consoleEncoding = System.getProperty("consoleEncoding"); if (consoleEncoding != null) { try { System.setOut(new PrintStream(System.out, true, consoleEncoding)); } catch (java.io.UnsupportedEncodingException ex) { System.err.println("Unsupported encoding set for console: " + consoleEncoding); } } /* Создаем серверный сокет на полученном порту */ 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.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.UnknownHostException; /** * Подключается к серверу и передает ему сообщения вводимые пользователем в * консоли. Хост сервера укаызвается первым аргументом, порт вторым. При * отсутствии аргументов в качестве адреса порта принимается localhost:9999 * * @author brovko_rs */ public class TcpClient { public static void main(String[] args) throws UnsupportedEncodingException { /* Определяем хост сервера и порт */ String host = DEFAULT_HOST; int port = DEFAULT_PORT; if (args.length > 0) { host = args[0]; } if (args.length > 1) { port = Integer.parseInt(args[1]); } /* Установка кодировки консоли */ String consoleEncoding = System.getProperty("consoleEncoding"); if (consoleEncoding != null) { try { System.setOut(new PrintStream(System.out, true, consoleEncoding)); } catch (java.io.UnsupportedEncodingException e) { System.err.println("Unsupported encoding set for console: " + consoleEncoding); } } /* * Создаем сокет для полученной пары хост/порт */ 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, consoleEncoding)); /* * Получаем поток вывода, через который будут передаваться сообщения * серверу */ 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://github.com/programming086/java_http
Немає коментарів:
Дописати коментар