Иллюстрированный самоучитель по Java
Работа
по протоколу TCP
Программы-серверы, прослушивающие
свои порты, работают под управлением операционной системы. У машин-серверов
могут быть самые разные операционные системы, особенности которых передаются
программам-серверам.
Чтобы сгладить различия в реализациях
разных серверов, между сервером и портом введен промежуточный программный слой,
названный
сокетом
(socket). Английское слово socket переводится как электрический
разъем, розетка. Так же как к розетке при помощи вилки можно подключить любой
электрический прибор, лишь бы он был рассчитан на 220 В и 50 Гц, к соке-ту можно
присоединить любой клиент, лишь бы он работал по тому же протоколу, что и сервер.
Каждый сокет связан (bind) с одним портом, говорят, что сокет прослушивает (listen)
порт. Соединение с помощью сокетов устанавливается так.
1. Сервер создает сокет, прослушивающий
порт сервера.
2. Клиент тоже создает сокет, через
который связывается с сервером, сервер начинает устанавливать (accept) связь
с клиентом.
3. Устанавливая связь, сервер создает
новый сокет, прослушивающий порт с другим, новым номером, и сообщает этот номер
клиенту.
4. Клиент посылает запрос на сервер
через порт с новым номером.
После этого соединение становится
совершенно симметричным — два сокета обмениваются информацией, а сервер через
старый сокет продолжает прослушивать прежний порт, ожидая следующего клиента.
В Java сокет — это объект класса
socket из пакета java.io. В классе шесть конструкторов, в которые разными способами
заносится адрес хоста и номер порта. Чаще всего применяется конструктор
Socket(String
host, int port)
Многочисленные методы доступа устанавливают
и получают параметры со-кета. Мы не будем углубляться в их изучение. Нам понадобятся
только методы, создающие потоки ввода/вывода:
-
getlnputStream()
— возвращает входной поток типа InputStream;
-
getOutputStream()
— возвращает выходной поток типа OutputStream.
Приведем пример получения файла
с сервера по максимально упрощенному протоколу HTTP.
1. Клиент посылает серверу запрос
на получение файла строкой "POST filename HTTP/1.l\n\n", где filename
— строка с путем к файлу на сервере.
2. Сервер анализирует строку, отыскивает
файл с именем filename и возвращает его клиенту. Если имя файла filename заканчивается
наклонной чертой /, то сервер понимает его как имя каталога и возвращает файл
in-dex.html, находящийся в этом каталоге.
3. Перед содержимым файла сервер
посылает строку вида "HTTP/1.1 code OK\n\n", где code — это код ответа,
одно из чисел: 200 — запрос удовлетворен, файл посылается; 400 — запрос не понят;
404 — файл не найден.
4. Сервер закрывает сокет и продолжает
слушать порт, ожидая следующего запроса.
5. Клиент выводит содержимое полученного
файла в стандартный вывод System, out или выводит код сообщения сервера в стандартный
вывод сообщений System, err.
6. Клиент закрывает сокет, завершая
связь.
Этот протокол реализуется в клиентской
программе листинга 19.3 и серверной программе листинга 19.4.
Листинг 19.3.
Упрощенный HTTP-клиент
import java.net.*;
import java.io.*;
import java.util.*;
class Client{
public static
void main(String[] args){
if (args.length
!= 3){
System.err.println("Usage:
Client host port file");
System.exit(0)
;
}
String host =
args[0];
int port = Integer.parselnt(args[1]);
String file =
args[2];
try{
Socket sock =
new Socket(host, port);
PrintWriter pw
= new PrintWriter(new OutputStreamWriter(
sock.getOutputStreamf)),
true);
pw.println("POST
" + file + " HTTP/1.l\n");
BufferedReader
br = new BufferedReader(new InputStreamReader(
sock.getlnputStream()
) ) ;
String line =
null;
line = br.readLine();
StringTokenizer
st = new StringTokenizer(line);
String code =
null;
if ((st.countTokens()
>= 2) && st.nextToken().equals("POST")){
if ((code = st.nextToken())
!= "200") {
System.err.println("File
not found, code = " + code);
System.exit (0);
}
}
while ((line
= br.readLine()) != null)
System.out.println{line);
sock.close();
}catch(Exception
e){
System.err.println(e);
}
}
}
Закрытие потоков ввода/вывода вызывает
закрытие сокета. Обратно, закрытие сокета закрывает и потоки.
Для создания сервера в пакете java.net
есть класс serversocket. В конструкторе этого класса указывается номер порта
ServerSocket(int port)
Основной метод этого класса accept
() ожидает поступления запроса. Когда запрос получен, метод устанавливает соединение
с клиентом и возвращает объект класса socket, через который сервер будет обмениваться
информацией с клиентом.
Листинг 19.4.
Упрощенный HTTP-сервер
import j ava.net.*;
import java.io.*;
import j ava.uti1.*;
class Server!
public static
void main(String[] args){
try{
ServerSocket ss
= new ServerSocket(Integer.parselnt(args[0]));
while (true)
new HttpConnect(ss.accept());
}catch(ArraylndexOutOfBoundsException
ae){
System.err.println("Usage:
Server port");
System.exit(0);
}catch(IOException
e){
System.out.println(e);
}
}
}
class HttpConnect
extends Thread{
private Socket
sock;
HttpConnect(Socket
s) {
sock = s;
setPriority(NORM_PRIORITY
- 1);
start {) ;
}
public void run(){
try{
PrintWriter pw
= new PrintWriter(new OutputStreamWriter(
sock.getOutputStream()},
true);
BufferedReader
br = new BufferedReader(new InputStreamReader(
sock.getlnputStream()
) ) ;
String req =
br.readLine();
System.out.println("Request:
" + req);
StringTokenizer
st = new StringTokenizer(req);
if ((st.countTokens()
>= 2) && st.nextToken().equals("POST")){
if ((req = st.nextToken()).endsWith("/")
II req.equals(""))
req += "index.html";
try{
File f = new File(req);
BufferedReader
bfr =
new BufferedReader(new
FileReader(f));
char[] data =
new char[(int)f.length()];
bfr.read(data);
pw.println("HTTP/1.1
200 OK\n");
pw.write(data);
pw.flush();
}catch(FileNotFoundException
fe){
pw.println("HTTP/1.1
404 Not FoundXn");
}catch(lOException
ioe){
System.out.println(ioe);
}
}else pw.println("HTTP/l.l
400 Bad RequestW);
sock.close();
}catch(IOException
e){
System.out.println(e);
}
}
}
Вначале следует запустить сервер,
указав номер порта, например:
Java Server 8080
Затем надо запустить клиент, указав
IP-адрес или доменное имя хоста, номер порта и имя файла:
Java Client localhost
8080 Server.Java
Сервер отыскивает файл Server.java
в своем текущем каталоге и посылает его клиенту. Клиент выводит содержимое этого
класса в стандартный вывод и завершает работу. Сервер продолжает работать, ожидая
следующего запроса.
Замечание по отладке
Программы, реализующие стек протоколов
TCP/IP, всегда создают так называемую "петлю" с адресом 127.0.0.1
и доменным именем localhost. Это адрес самого компьютера. Он используется
для отладки приложений клиент-сервер. Вы можете запускать клиент и сервер
на одной машине, пользуясь этим адресом.