Java. HTTP протокол и работа с WEB
оглавление | далее
Работа с TCP/IP в Java. Сокеты
Итак, для начала немного теории. HTTP (Hyper Text Transfert Protocol) был изначально создан для пересылки HTML документов, отсюда и "заточка" этого протокола под работу с отдельными документами, преимущественно текстовыми. HTTP в своей работе использует возможности TCP/IP, поэтому рассмотрим возможности, предоставляемые java для работы с последним.
В джаве для этого существует специальный пакет "java.net", содержащий класс java.net.Socket. Socket в переводе означает "гнездо", название это было дано по аналогии с гнёздами на аппаратуре, теми самыми, куда подключают штепсели. Соответственно этой аналогии, можно связать два "гнезда", и передавать между ними данные. Каждое гнездо принадлежит определённому хосту (Host - хозяин, держатель). Каждый хост имеет уникальный IP (Internet Packet) адрес. На данный момент интернет работает по протоколу IPv4, где IP адрес записывается 4 числами от 0 до 255 - например, 127.0.0.1 (подробнее о распределении IP адресов тут - RFC 790, RFC 1918, RFC 2365, о версии IPv6 читайте тут - RFC 2373)
Гнёзда монтируются на порт хоста (port). Порт обозначается числом от 0 до 65535 и логически обозначает место, куда можно пристыковать (bind) сокет. Если порт на этом хосте уже занят каким-то сокетом, то ещё один сокет туда пристыковать уже не получится. Таким образом, после того, как сокет установлен, он имеет вполне определённый адрес, символически записывающийся так [host]:[port], к примеру - 127.0.0.1:8888 (означает, что сокет занимает порт 8888 на хосте 127.0.0.1)
TCP/IP: логическая структура соединений через сокеты
Для того, чтобы облегчить жизнь, чтобы не использовать неудобозапоминаемый IP адрес, была придумана система DNS (DNS - Domain Name Service). Цель этой системы - сопоставлять IP адресам символьные имена. К примеру, адресу "127.0.0.1" в большинстве компьютеров сопоставленно имя "localhost" (в просторечье - "локалхост").
Локалхост, фактически, означает сам компьютер, на котором выполняется программа, он же - локальный компьютер. Вся работа с локалхостом не требует выхода в сеть и связи с какими-либо другими хостами.
Клиентский сокет
Итак, вернёмся к классу java.net.Socket Наиболее удобно инициализировать его следующим образом:
public Socket(String host, int port) throws UnknownHostException, IOExceptionВ строковой константе host можно указать как IP адрес сервера, так и его DNS имя. При этом программа автоматически выберет свободный порт на локальном компьютере и "привинтит" туда ваш сокет, после чего будет предпринята попытка связаться с другим сокетом, адрес которого указан в параметрах инициализации. При этом могут возникнуть два вида исключений: неизвестный адрес хоста - когда в сети нет компьютера с таким именем или ошибка отсутствия связи с этим сокетом.
Так же полезно знать функцию
public void setSoTimeout(int timeout) throws SocketExceptionЭта функция устанавливает время ожидания (timeout) для работы с сокетом. Если в течение этого времени никаких действий с сокетом не произведено (имеется ввиду получение и отправка данных), то он самоликвидируется. Время задаётся в секундах, при установке timeout равным 0 сокет становится "вечным".
Для некоторых сетей изменение timeout невозможно или установлено в определённых интервалах (к примеру от 20 до 100 секунд). При попытке установить недопустимый timeout, будет выдано соответственное исключение.
Программа, которая открывает сокет этого типа, будет считаться клиентом, а программа-владелец сокета, к которому вы пытаетесь подключиться, далее будет называться сервером. Фактически, по аналогии гнездо-штепсель, программа-сервер - это и будет гнездо, а клиент как раз является тем самым штепселем.
Сокет сервера
Как установить соединение от клиента к серверу я только что описал, теперь - как сделать сокет, который будет обслуживать сервер. Для этого в джава существует следующий класс: java.net.ServerSocket Наиболее удобным инициализатором для него является следующий:
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOExceptionКак видно, в качестве третьего параметра используется объект ещё одного класса - java.net.InetAddress Этот класс обеспечивает работу с DNS и IP именами, по этому вышеприведённый инициализатор в программах можно использовать так:
ServerSocket(port, 0, InetAddress.getByName(host)) throws IOExceptionДля этого типа сокета порт установки указывается прямо, поэтому, при инициализации, может возникнуть исключение, говорящее о том, что данный порт уже используется либо запрещён к использованию политикой безопасности компьютера.
После установки сокета, вызывается функция
public Socket accept() throws IOExceptionЭта функция погружает программу в ожидание того момента, когда клиент будет присоединяться к сокету сервера. Как только соединение установлено, функция возвратит объект класса Socket для общения с клиентом.
Клиент-сервер через сокеты. Пример
Как пример - простейшая программа, реализующая работу с сокетами.
Со стороны клиента программа работает следующим образом: клиент подсоединяется к серверу, отправляет данные, после чего получает данные от сервера и выводит их.
Со стороны сервера это выглядит следующим образом: сервер устанавливает сокет сервера на порт 3128, после чего ждёт входящих подключений. Приняв новое подключение, сервер передаёт его в отдельный вычислительный поток. В новом потоке сервер принимает от клиента данные, приписывает к ним порядковый номер подключения и отправляет данные обратно к клиенту.
Логическая структура работы программ-примеров
Программа простого TCP/IP клиента
(SampleClient.java)
import java.io.*;
import java.net.*;
class SampleClient extends Thread
{
public static void main(String args[])
{
try
{
// открываем сокет и коннектимся к localhost:3128
// получаем сокет сервера
Socket s = new Socket("localhost", 3128);
// берём поток вывода и выводим туда первый аргумент
// заданный при вызове, адрес открытого сокета и его порт
args[0] = args[0]+"\n"+s.getInetAddress().getHostAddress()
+":"+s.getLocalPort();
s.getOutputStream().write(args[0].getBytes());
// читаем ответ
byte buf[] = new byte[64*1024];
int r = s.getInputStream().read(buf);
String data = new String(buf, 0, r);
// выводим ответ в консоль
System.out.println(data);
}
catch(Exception e)
{System.out.println("init error: "+e);} // вывод исключений
}
}
Программа простого TCP/IP сервера
(SampleServer.java)
import java.io.*;
import java.net.*;
class SampleServer extends Thread
{
Socket s;
int num;
public static void main(String args[])
{
try
{
int i = 0; // счётчик подключений
// привинтить сокет на локалхост, порт 3128
ServerSocket server = new ServerSocket(3128, 0,
InetAddress.getByName("localhost"));
System.out.println("server is started");
// слушаем порт
while(true)
{
// ждём нового подключения, после чего запускаем обработку клиента
// в новый вычислительный поток и увеличиваем счётчик на единичку
new SampleServer(i, server.accept());
i++;
}
}
catch(Exception e)
{System.out.println("init error: "+e);} // вывод исключений
}
public SampleServer(int num, Socket s)
{
// копируем данные
this.num = num;
this.s = s;
// и запускаем новый вычислительный поток (см. ф-ю run())
setDaemon(true);
setPriority(NORM_PRIORITY);
start();
}
public void run()
{
try
{
// из сокета клиента берём поток входящих данных
InputStream is = s.getInputStream();
// и оттуда же - поток данных от сервера к клиенту
OutputStream os = s.getOutputStream();
// буффер данных в 64 килобайта
byte buf[] = new byte[64*1024];
// читаем 64кб от клиента, результат - кол-во реально принятых данных
int r = is.read(buf);
// создаём строку, содержащую полученную от клиента информацию
String data = new String(buf, 0, r);
// добавляем данные об адресе сокета:
data = ""+num+": "+"\n"+data;
// выводим данные:
os.write(data.getBytes());
// завершаем соединение
s.close();
}
catch(Exception e)
{System.out.println("init error: "+e);} // вывод исключений
}
}
После компиляции, получаем файлы SampleServer.class и SampleClient.class (все программы здесь и далее откомпилированы с помощью JDK v1.4) и запускаем вначале сервер:
java SampleServerа потом, дождавшись надписи "server is started", и любое количество клиентов:
java SampleClient test1 java SampleClient test2 ... java SampleClient testN
Если во время запуска программы-сервера, вместо строки "server is started" выдало строку типа
init error: java.net.BindException: Address already in use: JVM_Bindто это будет обозначать, что порт 3128 на вашем компьютере уже занят какой-либо программой или запрещён к применению политикой безопасности.
Заметки
Отметим немаловажную особенность сокета сервера: он может принимать подключения сразу от нескольких клиентов одновременно. Теоретически, количество одновременных подключений неограниченно, но практически всё упирается в мощность компьютеров. Кстати, эта проблема конечной мощности компьютеров используется в DOS атаках на серверы: их просто закидывают таким количеством подключений, что компьютеры не справляются с нагрузкой и "падают".
В данном случае я показываю на примере SimpleServer, как нужно обрабатывать сразу несколько одновременных подключений: сокет каждого нового подключения посылается на обработку отдельному вычислительному потоку.
Стоит упомянуть, что абстракцию Socket - ServerSocket и работу с потоками данных используют C/C++, Perl, Python, многие другие языки программирования и API операционных систем, так что многое из сказанного подходит к применению не только для платформы Java.
оглавление | далее




Узнай о чем ты на самом деле сейчас думаешь

