Програмиране на сокети в Java: Урок

Този урок е въведение в програмирането на сокети в Java, започвайки с прост пример клиент-сървър, демонстриращ основните характеристики на Java I / O. Ще се запознаете както с оригиналния  java.io пакет, така и с NIO, неблокиращите java.nioAPI на I / O ( ), въведени в Java 1.4. И накрая, ще видите пример, който демонстрира Java мрежа, реализирана от Java 7 напред, в NIO.2.

Програмирането на сокети се свежда до две системи, комуникиращи помежду си. Като цяло мрежовата комуникация се предлага в два вида: протокол за контрол на транспорта (TCP) и протокол за потребителски дейтаграми (UDP). TCP и UDP се използват за различни цели и двете имат уникални ограничения:

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

За да оцените разликата между TCP и UDP, помислете какво би се случило, ако стриймвате видео от любимия си уебсайт и той изпуска кадри. Бихте ли предпочели клиентът да забави филма ви, за да получи липсващите кадри, или предпочитате видеоклипът да продължи да се възпроизвежда? Протоколите за стрийминг на видео обикновено използват UDP. Тъй като TCP гарантира доставка, това е избраният протокол за HTTP, FTP, SMTP, POP3 и т.н.

В този урок ще ви запозная с програмирането на сокети в Java. Представям поредица от примери за клиент-сървър, които демонстрират функции от оригиналната I / O рамка на Java, след което постепенно преминават към използването на функции, въведени в NIO.2.

Староучилищни сокети Java

В изпълненията преди NIO кодът на сокет за клиент на Java TCP се обработва от java.net.Socketкласа. Следният код отваря връзка със сървър:

 Socket socket = нов Socket (сървър, порт); 

След като нашият socketекземпляр е свързан със сървъра, можем да започнем да получаваме входни и изходни потоци към sever. Входящите потоци се използват за четене на данни от сървъра, докато изходните потоци се използват за запис на данни на сървъра. Можем да изпълним следните методи за получаване на входни и изходни потоци:

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

Тъй като това са обикновени потоци, същите потоци, които бихме използвали за четене и запис във файл, можем да ги преобразуваме във формата, която най-добре отговаря на нашия случай на употреба. Например, бихме могли да обвием OutputStreamс, за PrintStreamда можем лесно да напишем текст с методи като println(). За друг пример бихме могли да увием InputStreamс BufferedReader, чрез InputStreamReader, за да четем лесно текст с методи като readLine().

изтегляне Изтеглете изходния код Изходен код за „Програмиране на сокети в Java: Урок.“ Създадено от Стивън Хейнс за JavaWorld.

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

Нека да разгледаме кратък пример, който изпълнява HTTP GET срещу HTTP сървър. HTTP е по-сложен, отколкото позволява нашият пример, но можем да напишем клиентски код, за да се справим с най-простия случай: заявете ресурс от сървъра и сървърът връща отговора и затваря потока. Този случай изисква следните стъпки:

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

Листинг 1 показва изходния код за този пример.

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

пакет com.geekcap.javaworld.simplesocketclient; импортиране на java.io.BufferedReader; импортиране на java.io.InputStreamReader; импортиране на java.io.PrintStream; импортиране на java.net.Socket; публичен клас SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } String сървър = аргументи [0]; Път на низа = аргументи [1]; System.out.println ("Зареждане на съдържание на URL:" + сървър); опитайте {// Свържете се със сървъра Socket socket = new Socket (server, 80); // Създаване на входни и изходни потоци за четене и запис на сървъра PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader в = new 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 (линия); линия = in.readLine (); } // Затваряме нашите потоци в .close (); out.close (); socket.close (); } catch (Изключение e) {e.printStackTrace (); }}}

Листинг 1 приема два аргумента от командния ред: сървърът, към който да се свържете (ако приемем, че се свързваме със сървъра на порт 80) и ресурсът за извличане. Той създава a, 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да можем по-лесно да изпълняваме нашите текстови команди. Нашият код получи a InputStream, уви това в an InputStreamReader, което го преобразува в a Reader, и след това го уви в a 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: Benzin-Local X-Gasoline-Age: 8 Content-Length: 168 Последна промяна: Tue, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Тип съдържание : text / html Вари: Приемане-кодиране на връзката: затворете страницата за тестване на бензин

Успех

Този изход показва тестова страница на уебсайта на JavaWorld. Той отговори, че говори HTTP версия 1.1 и отговорът е 200 OK.

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

Покрихме клиентската страна и за щастие комуникационният аспект на сървърната страна е също толкова лесен. От опростена гледна точка процесът е следният:

  1. Създайте a ServerSocket, като посочите порт за слушане.
  2. Извикайте метода ServerSocket' accept(), за да слушате на конфигурирания порт за клиентска връзка.
  3. Когато клиент се свърже със сървъра, accept()методът връща, Socketчрез който сървърът може да комуникира с клиента. Това е същият Socketклас, който използвахме за нашия клиент, така че процесът е един и същ: получете InputStreamчетене от клиента и OutputStreamзапис на клиента.
  4. Ако сървърът ви трябва да бъде мащабируем, ще искате да предадете на Socketдруга нишка за обработка, за да може сървърът ви да продължи да слуша за допълнителни връзки.
  5. Обадете се на ServerSocket-те accept()метод отново да слушат за друга връзка.

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(); } } }