Лекция 11. Сеть ####################################################################################### Введение =========================================================== Введение ----------------------------------------------------------- В данной лекции рассматриваются сетевые приложения на языке Java. Предполагается использования стека протоколов TCP/IP для обмена информацией #. между процессами на одном компьютере; #. между процессами на разных компьютерах, подключенных к сети. Сетевые приложения на языке Java строятся с использованием технологии **Клиент/Сервер**. Приложения состоят из двух частей: **Клиента** и **Сервера**. #. Сервер запускается первым и после некоторых настроек переходит в состояние ожидания, используя определенный **порт**. #. Клиент устанавливает соединение, используя **IP-адрес** компьютера и **порт**, который прослушивает сервер. #. Общение двух программ выполняется через **потоки ввода/вывода**. #. После обмена информацией клиент разрывает соединение. Соединение может быть установлено двумя способами: #. С помощью потоков (**TCP**) #. С помощью датаграмм (**UDP**) .. image:: _static/11/tcp-udp.png .. image:: _static/11/prot.png Для обмена информацией существует специальная абстракция **сокет** (гнездо). Гнезда создаются как у сервера, так и у клиента. Для работы с сетью в Java предусмотрена иерархия пакетов **java.net.*** Приложения можно разделить на те, в которых сервер может устанавливать одновременно только одно соединение с клиентом, и на те, в которых может устанавливаться одновременно несколько соединений. Адреса и имена ----------------------------------------------------------- IP адреса и доменные имена `````````````````````````````````````````````````````````` Для адресации сервера в сети могут использоваться **IP-адреса** или **доменные имена**. Преобразование между ними происходит с помощью класса **InetAddress**: .. code-block:: java InetAddress addr = InetAddress.getByName("www.google.ru"); System.out.println(addr); // выводим IP-адрес Для работы на локальном компьютере можно использовать IP-адрес **127.0.0.1** или имя **"localhost"**. Еще можно получить локальный адрес так: .. code-block:: java InetAddress addr = InetAddress.getByName(null); Сокеты ----------------------------------------------------------- На стороне сервера создаются два сокета: **ServerSocket** и просто **Socket**. **ServerSocket** - заставляет ждать программу подключений клиентов. При создании объекта необходимо указать свободный порт: .. code-block:: java ServerSocket server = new ServerSocket(1234); ... Socket client = server.accept(); // ожидание подключений При вызове **accept** сервер ждет подключений, а при появлении такового возвращает сокет для связи с клиентом. Для создания сокета на стороне клиента нужно указать IP-адрес и порт сервера: .. code-block:: java Socket socket = new Socket("192.168.0.1",1234); После установления соединения можно работать с потоками, связанными с сокетами: .. code-block:: java InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); Ошибки `````````````````````````````````````````````````````````` В процессе установления соединения и обмена данными необходимо перехватывать исключения: * **IOException** - при создании серверного сокета (порт занят). При обработке пользовательского запроса (подключение к порту). При получении потока ввода/вывода. При чтении/записи сообщения из/в поток(а). * **UnknownHostException** - при соединении на стороне клиента (хост не найден). * **NoRouteToHostException** - сервер недоступен. * **ConnectException** - запрос на соединение отклонен. Порядок перехвата исключений: .. code-block:: java try { ... } catch(UnknownHostException e) { ... } catch(NoRouteToHostException e) { ... } catch(ConnectException e) { ... } catch(IOException e) { ... } Простой пример =========================================================== Введение `````````````````````````````````````````````````````````` Для иллюстрации простого клиент/серверного приложения на сокетах создадим две программы: #. **Server** - серверная часть (класс **Server**) #. **Client** - клиентская часть (класс **Client**) Для связи необходимо выбрать свободный порт в системе, например **1234**. Код сервера `````````````````````````````````````````````````````````` .. code-block:: java import java.io.*; import java.net.*; public class Server { public static void main(String[] args) throws IOException { System.out.println("Старт сервера"); // поток для чтения данных BufferedReader in = null; // поток для отправки данных PrintWriter out= null; // серверный сокет ServerSocket server = null; // сокет для обслуживания клиента Socket client = null; .. .. code-block:: java // создаем серверный сокет try { server = new ServerSocket(1234); } catch (IOException e) { System.out.println("Ошибка связывания с портом 1234"); System.exit(-1); } .. try { System.out.print("Ждем соединения"); client= server.accept(); System.out.println("Клиент подключился"); } catch (IOException e) { System.out.println("Не могу установить соединение"); System.exit(-1); } .. code-block:: java // создаем потоки для связи с клиентом in = new BufferedReader( new InputStreamReader(client.getInputStream())); out = new PrintWriter(client.getOutputStream(),true); String input,output; // цикл ожидания сообщений от клиента System.out.println("Ожидаем сообщений"); while ((input = in.readLine()) != null) { if (input.equalsIgnoreCase("exit")) break; out.println("Сервер: "+input); System.out.println(input); } Закрываем все соединения .. code-block:: java out.close(); in.close(); client.close(); server.close(); } } Код клиента `````````````````````````````````````````````````````````` .. code-block:: java import java.io.*; import java.net.*; public class Client { public static void main(String[] args) throws IOException { System.out.println("Клиент стартовал"); Socket server = null; // адрес (имя) сервера должны передаваться как параметр if (args.length==0) { System.out.println("Использование: java Client hostname"); System.exit(-1); } .. .. code-block:: java System.out.println("Соединяемся с сервером "+args[0]); server = new Socket(args[0],1234); BufferedReader in = new BufferedReader( new InputStreamReader(server.getInputStream())); PrintWriter out = new PrintWriter(server.getOutputStream(),true); BufferedReader inu = new BufferedReader(new InputStreamReader(System.in)); String fuser,fserver; Основной цикл отправки сообщений серверу .. code-block:: java while ((fuser = inu.readLine())!=null) { out.println(fuser); fserver = in.readLine(); System.out.println(fserver); if (fuser.equalsIgnoreCase("close")) break; if (fuser.equalsIgnoreCase("exit")) break; } Закрытие соединения и выход .. code-block:: java out.close(); in.close(); inu.close(); server.close(); } } Запуск сервера: .. code-block:: none java Server Запуск клиента (для сервера на локальной машине): .. code-block:: none java Client localhost Обмен двоичными данными =========================================================== Двоичные данные `````````````````````````````````````````````````````````` Рассмотрим процедуру обмена двоичными данными. Предположим, клиент должен отправить серверу содержимое файла. Рассмотрим реализацию сервера: .. code-block:: java InputStream in = null; OutputStream out= null; .. in = client.getInputStream(); try { out = new FileOutputStream("fromClient.txt"); } catch(FileNotFoundException ex) { System.out.println("Ошибка создания файла!"); System.exit(-1); } .. code-block:: java byte[] data =new byte[1024]; int count; try { while ((count = in.read(data)) > 0) { out.write(data, 0, count); } } catch (IOException e) { System.out.println("Ошибка чтения/записи данных"); System.exit(-1); } Реализация на стороне клиента: .. code-block:: java InputStream in = null; OutputStream out = null; File file = null; ... file = new File("client.txt"); try { in = new FileInputStream(file); out = server.getOutputStream(); byte[] bytes = new byte[1024]; int count; while ((count = in.read(bytes)) > 0) { out.write(bytes, 0, count); } } catch (IOException ex) { System.out.println("Ошибка ввода/вывода"); System.exit(-1); } Передача массива `````````````````````````````````````````````````````````` Рассмотрим пример, в котором клиент должен передать серверу массив байт определенной длины. Фрагмент реализации сервера: .. code-block:: java ... in = client.getInputStream(); byte[] responseBytes = new byte[15]; byte[] len=new byte[1]; int bytesRead = 0; try { in.read(len); System.out.println(len[0]); bytesRead = in.read(responseBytes); System.out.println(bytesRead); } catch (IOException e) { e.printStackTrace(); } for(int i=0;i> 8 & 0xff), (byte)(value >> 16 & 0xff), (byte)(value >>> 24) }; } .. code-block:: java public static final int byteArrayToInt(byte[] value) { int ret = ((value[0] & 0xFF) << 24) | ((value[1] & 0xFF) << 16) | ((value[2] & 0xFF) << 8) | (value[3] & 0xFF); return ret; } Работа по стандартным протоколам =========================================================== Протокол HTTP `````````````````````````````````````````````````````````` Можно написать клиентское приложение, которое будет запрашивать веб-ресурсы по протоколу **HTTP**. Необходимо обратиться к работающему веб-серверу и запросить страницу. В качестве примера используется сервер, запущенный на локальном компьютере. .. code-block:: java import java.io.*; import java.net.*; public class HttpClient { public static void main(String[] args) throws IOException { Socket socket = new Socket(); String host = "localhost"; PrintWriter out = null; BufferedReader in = null; .. code-block:: java try { socket.connect(new InetSocketAddress(host , 80)); System.out.println("Соединение установлено"); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (UnknownHostException e) { System.err.println("Невозможно соединиться с: " + host); System.exit(1); } String message = "GET / HTTP/1.0\r\n\r\n"; out.println( message ); System.out.println("Сообщение послано"); String response; while ((response = in.readLine()) != null) { System.out.println( response ); } } } Многопоточный сервер =========================================================== Реализация сервера ----------------------------------------------------------- Многопоточная реализация `````````````````````````````````````````````````````````` Для организации связи с несколькими клиентами сервер нужно сделать **многопоточным**. В главном потоке работает бесконечный цикл с вызовом **accept**. При получении запроса на соединения создается дополнительный поток со своим сокетом, который обслуживает новое соединение. Код сервера: .. code-block:: java import java.io.*; import java.net.*; class ServerOne extends Thread { private Socket socket; private BufferedReader in; private PrintWriter out; public ServerOne(Socket s) throws IOException { socket = s; in = new BufferedReader( new InputStreamReader( socket.getInputStream())); out = new PrintWriter( new BufferedWriter( new OutputStreamWriter( socket.getOutputStream())), true); start(); } ... .. code-block:: java public void run() { try { while (true) { String str = in.readLine(); if (str.equals("END")) break; System.out.println("Получено: " + str); out.println(str); } System.out.println("Соединение закрыто"); } catch (IOException e) { System.err.println("Ошибка чтения/записи"); } finally { try { socket.close(); } catch (IOException e) { System.err.println("Сокет не закрыт"); } } } } .. code-block:: java public class Server { static final int PORT = 1234; public static void main(String[] args) throws IOException { ServerSocket s = new ServerSocket(PORT); System.out.println("Мультипоточный сервер стартовал"); try { while (true) { Socket socket = s.accept(); try { System.out.println("Новое соединение установлено"); new ServerOne(socket); } catch (IOException e) { socket.close(); } } } finally { s.close(); } } } Идентификация клиентов ----------------------------------------------------------- Для идентификации клиентов со стороны сервера можно запросить IP-адрес через сокет, возвращаемый **accept**: .. code-block:: java Socket socket = s.accept(); System.out.println("Новое соединение установлено"); System.out.println("Данные клиента: "+ socket.getInetAddress()); Вопросы для самоконтроля =============================================