Работа по протоколу 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. Это адрес самого компьютера. Он используется для отладки приложений клиент-сервер. Вы можете запускать клиент и сервер на одной машине, пользуясь этим адресом.