Программирование сокетов на Java: учебное пособие

Это руководство представляет собой введение в программирование сокетов на Java, начиная с простого примера клиент-сервер, демонстрирующего основные функции ввода-вывода Java. Вы познакомитесь как с исходным  java.io пакетом, так и с NIO, неблокирующими java.nioAPI-интерфейсами ввода- вывода ( ), представленными в Java 1.4. Наконец, вы увидите пример, демонстрирующий сетевое взаимодействие Java, реализованное начиная с Java 7 и далее в NIO.2.

Программирование сокетов сводится к тому, что две системы взаимодействуют друг с другом. Обычно сетевая связь бывает двух видов: протокол управления транспортом (TCP) и протокол дейтаграмм пользователя (UDP). TCP и UDP используются для разных целей, и оба имеют уникальные ограничения:

  • TCP - относительно простой и надежный протокол, который позволяет клиенту установить соединение с сервером, а две системы - для связи. В TCP каждый объект знает, что его информационные данные получены.
  • UDP - это протокол без установления соединения, и он подходит для сценариев, когда вам не обязательно нужно, чтобы каждый пакет достиг своего места назначения, например, для потоковой передачи мультимедиа.

Чтобы оценить разницу между TCP и UDP, подумайте, что произойдет, если вы транслируете потоковое видео с вашего любимого веб-сайта, и на нем пропадают кадры. Вы бы предпочли, чтобы клиент замедлял ваш фильм, чтобы получить пропущенные кадры, или вы бы предпочли, чтобы видео продолжало воспроизводиться? Протоколы потоковой передачи видео обычно используют UDP. Поскольку TCP гарантирует доставку, это предпочтительный протокол для HTTP, FTP, SMTP, POP3 и т. Д.

В этом руководстве я познакомлю вас с программированием сокетов на Java. Я представляю серию примеров клиент-сервер, которые демонстрируют возможности исходной инфраструктуры ввода-вывода Java, а затем постепенно перехожу к использованию функций, представленных в NIO.2.

Старые Java-сокеты

В реализациях до NIO код сокета клиента TCP Java обрабатывается java.net.Socketклассом. Следующий код открывает соединение с сервером:

 Сокет сокет = новый сокет (сервер, порт); 

Как только наш socketэкземпляр подключен к серверу, мы можем начать получать потоки ввода и вывода на сервер. Входные потоки используются для чтения данных с сервера, а выходные потоки используются для записи данных на сервер. Мы можем выполнить следующие методы для получения входных и выходных потоков:

InputStream in = socket.getInputStream (); OutputStream out = socket.getOutputStream ();

Поскольку это обычные потоки, те же потоки, которые мы использовали бы для чтения и записи в файл, мы можем преобразовать их в форму, которая лучше всего подходит для нашего варианта использования. Например, мы могли бы обернуть OutputStream, PrintStreamчтобы мы могли легко писать текст такими методами, как println(). В качестве другого примера, мы могли бы обернуть InputStreamс BufferedReader, через InputStreamReader, чтобы легко читать текст с методами , как readLine().

скачать Скачать исходный код Исходный код для «Программирование сокетов на Java: учебное пособие». Создано Стивеном Хейнсом для JavaWorld.

Пример клиента сокета Java

Давайте рассмотрим небольшой пример, который выполняет HTTP GET для HTTP-сервера. HTTP более сложен, чем позволяет наш пример, но мы можем написать клиентский код для обработки простейшего случая: запросить ресурс у сервера, и сервер вернет ответ и закрывает поток. В этом случае необходимо выполнить следующие действия:

  1. Создайте сокет для веб-сервера, прослушивающего порт 80.
  2. Получите PrintStreamна сервер и отправьте запрос GET PATH HTTP/1.0, где PATHнаходится запрошенный ресурс на сервере. Например, если мы хотим открыть корень веб-сайта, тогда путь будет /.
  3. Получите InputStreamна сервере, заключите в него BufferedReaderи прочтите ответ построчно.

В листинге 1 показан исходный код этого примера.

Листинг 1. SimpleSocketClientExample.java

пакет com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; импорт java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; открытый класс SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Использование: SimpleSocketClientExample"); System.exit (0); } Строка server = args [0]; Строка path = args [1]; System.out.println ("Загрузка содержимого URL:" + сервер); try {// Подключение к серверу Socket socket = new Socket (server, 80); // Создание входных и выходных потоков для чтения и записи на сервер PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader in = новый BufferedReader (новый InputStreamReader (socket.getInputStream ())); // Следуйте протоколу HTTP GET HTTP / 1.0, за которым следует пустая строка out.println ("GET" + path + "HTTP / 1.0"); out.println (); // Считываем данные с сервера, пока не закончим чтение документа String line = in.readLine (); в то время как (строка! = ноль) {System.out.println (строка); line = in.readLine (); } // Закрываем наши потоки in.close (); out.close (); socket.close (); } catch (исключение e) {e.printStackTrace (); }}}

Листинг 1 принимает два аргумента командной строки: сервер для подключения (при условии, что мы подключаемся к серверу через порт 80) и ресурс, который нужно получить. Он создает объект, Socketкоторый указывает на сервер и явно указывает порт 80. Затем он выполняет команду:

ПОЛУЧИТЬ ПУТЬ HTTP / 1.0 

Например:

GET / HTTP / 1.0 

Что сейчас произошло?

Когда вы получаете веб-страницу с веб-сервера, например www.google.com, HTTP-клиент использует DNS-серверы для поиска адреса сервера: он начинает с запроса у сервера домена верхнего уровня домена, в comкотором авторитетный сервер доменных имен находится для www.google.com. Затем он запрашивает у этого сервера доменных имен IP-адрес (или адреса) для www.google.com. Затем он открывает сокет для этого сервера на порту 80. (Или, если вы хотите определить другой порт, вы можете сделать это, добавив двоеточие, за которым следует номер порта, например:. :8080) Наконец, HTTP-клиент выполняет указанный метод HTTP, такие , как GET, POST, PUT, DELETE, HEAD, или OPTI/ONS. У каждого метода свой синтаксис. Как показано в приведенных выше фрагментах кода, для этого GETметода требуется путь, за которым следуетHTTP/version numberи пустая строка. Если бы мы хотели добавить заголовки HTTP, мы могли бы сделать это до ввода новой строки.

В листинге 1 мы извлекли OutputStreamи обернули его в, PrintStreamчтобы упростить выполнение наших текстовых команд. Наш код получил InputStream, обернул его в InputStreamReader, который преобразовал его в Reader, а затем обернул в BufferedReader. Мы использовали PrintStreamдля выполнения нашего GETметода, а затем использовали BufferedReaderдля чтения ответа построчно, пока мы не получили nullответ, указывающий, что сокет был закрыт.

Теперь выполните этот класс и передайте ему следующие аргументы:

java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com / 

Вы должны увидеть результат, аналогичный приведенному ниже:

Загрузка содержимого URL: www.javaworld.com HTTP / 1.1 200 OK Дата: 21 сентября 2014 г., 22:20:13 GMT Сервер: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Gasoline-Local X-Gasoline-Age: 8 Content-Length: 168 Last-Modified: Tue, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Content-Type : text / html Vary: Accept-Encoding Connection: закрыть страницу тестирования бензина

Успех

Этот вывод показывает тестовую страницу на веб-сайте JavaWorld. Он ответил, что говорит HTTP версии 1.1, и ответ 200 OK.

Пример сервера сокетов Java

Мы рассмотрели клиентскую сторону, и, к счастью, коммуникационный аспект на стороне сервера также прост. С упрощенной точки зрения процесс выглядит следующим образом:

  1. Создайте ServerSocket, указав порт для прослушивания.
  2. Invoke the ServerSocket's accept() method to listen on the configured port for a client connection.
  3. When a client connects to the server, the accept() method returns a Socket through which the server can communicate with the client. This is the same Socket class that we used for our client, so the process is the same: obtain an InputStream to read from the client and an OutputStream write to the client.
  4. If you server needs to be scalable, you will want to pass the Socket to another thread to process so that your server can continue listening for additional connections.
  5. Call the ServerSocket's accept() method again to listen for another connection.

As you'll soon see, NIO's handling of this scenario would be a bit different. For now, though, we can directly create a ServerSocket by passing it a port to listen on (more about ServerSocketFactorys in the next section):

 ServerSocket serverSocket = new ServerSocket( port ); 

And now we can accept incoming connections via the accept() method:

 Socket socket = serverSocket.accept(); // Handle the connection ... 

Multithreaded programming with Java sockets

Listing 2, below, puts all of the server code so far together into a slightly more robust example that uses threads to handle multiple requests. The server shown is an echo server, meaning that it echoes back any message it receives.

While the example in Listing 2 isn't complicated it does anticipate some of what's coming up in the next section on NIO. Pay special attention to the amount of threading code we have to write in order to build a server that can handle multiple simultaneous requests.

Listing 2. SimpleSocketServer.java

package com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.I/OException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SimpleSocketServer extends Thread { private ServerSocket serverSocket; private int port; private boolean running = false; public SimpleSocketServer( int port ) { this.port = port; } public void startServer() { try { serverSocket = new ServerSocket( port ); this.start(); } catch (I/OException e) { e.printStackTrace(); } } public void stopServer() { running = false; this.interrupt(); } @Override public void run() { running = true; while( running ) { try { System.out.println( "Listening for a connection" ); // Call accept() to receive the next connection Socket socket = serverSocket.accept(); // Pass the socket to the RequestHandler thread for processing RequestHandler requestHandler = new RequestHandler( socket ); requestHandler.start(); } catch (I/OException e) { e.printStackTrace(); } } } public static void main( String[] args ) { if( args.length == 0 ) { System.out.println( "Usage: SimpleSocketServer " ); System.exit( 0 ); } int port = Integer.parseInt( args[ 0 ] ); System.out.println( "Start server on port: " + port ); SimpleSocketServer server = new SimpleSocketServer( port ); server.startServer(); // Automatically shutdown in 1 minute try { Thread.sleep( 60000 ); } catch( Exception e ) { e.printStackTrace(); } server.stopServer(); } } class RequestHandler extends Thread { private Socket socket; RequestHandler( Socket socket ) { this.socket = socket; } @Override public void run() { try { System.out.println( "Received a connection" ); // Get input and output streams BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); PrintWriter out = new PrintWriter( socket.getOutputStream() ); // Write out our header to the client out.println( "Echo Server 1.0" ); out.flush(); // Echo lines back to the client until the client closes the connection or we receive an empty line String line = in.readLine(); while( line != null && line.length() > 0 ) { out.println( "Echo: " + line ); out.flush(); line = in.readLine(); } // Close our connection in.close(); out.close(); socket.close(); System.out.println( "Connection closed" ); } catch( Exception e ) { e.printStackTrace(); } } }