Programiranje soketa u Javi: Vodič

Ovaj je vodič uvod u programiranje soketa u Javi, započinjući jednostavnim primjerom klijent-poslužitelj koji pokazuje osnovne značajke Java I / O-a. Upoznat ćete se s izvornim  java.io paketom i NIO-om, neblokirajućim I / O ( java.nio) API-ima predstavljenim u Javi 1.4. Konačno, vidjet ćete primjer koji pokazuje Java umrežavanje implementirano od Jave 7 prema naprijed, u NIO.2.

Programiranje utičnica svodi se na dva sustava koji međusobno komuniciraju. Općenito, mrežna komunikacija dolazi u dva okusa: protokol za kontrolu prometa (TCP) i protokol korisničkog datagrama (UDP). TCP i UDP koriste se u različite svrhe i oba imaju jedinstvena ograničenja:

  • TCP je relativno jednostavan i pouzdan protokol koji omogućava klijentu da uspostavi vezu s poslužiteljem i dva sustava za komunikaciju. U TCP-u svaki entitet zna da su primljeni njegovi komunikacijski tereti.
  • UDP je protokol bez veze i dobar je za scenarije u kojima vam nije potreban svaki paket da biste stigli na svoje odredište, kao što je streaming medija.

Da biste uvidjeli razliku između TCP-a i UDP-a, razmislite što bi se dogodilo da strujite videozapise sa svoje omiljene web stranice i kad padnu okviri. Želite li da klijent uspori vaš film da primi kadrove koji nedostaju ili biste radije da se video nastavi reproducirati? Protokoli za streaming video zapisa obično koriste UDP. Budući da TCP jamči isporuku, to je odabrani protokol za HTTP, FTP, SMTP, POP3 i tako dalje.

U ovom uputstvu upoznajem vas s programiranjem soketa na Javi. Predstavljam niz primjera klijent-poslužitelj koji pokazuju značajke iz izvornog Java I / O okvira, a zatim postupno prelaze na korištenje značajki predstavljenih u NIO.2.

Old-school Java utičnice

U implementacijama prije NIO-a, Java TCP klijentskim socket kodom obrađuje java.net.Socketklasa. Sljedeći kod otvara vezu s poslužiteljem:

 Socket socket = nova Socket (poslužitelj, port); 

Jednom kada se naša socketinstanca poveže s poslužiteljem, možemo početi dobivati ​​ulazne i izlazne tokove u sever. Ulazni tokovi koriste se za čitanje podataka s poslužitelja, dok se izlazni tokovi koriste za upisivanje podataka na poslužitelj. Za dobivanje ulaznih i izlaznih tokova možemo izvršiti sljedeće metode:

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

Budući da su to obični tokovi, isti tokovi koje bismo koristili za čitanje i zapisivanje u datoteku, možemo ih pretvoriti u oblik koji najbolje odgovara našem slučaju upotrebe. Na primjer, mogli bismo omotati OutputStreams PrintStreamtako da možemo lako pisati tekst metodama poput println(). Kao drugi primjer, mogli bismo omotati a InputStreams BufferedReader, preko anta InputStreamReader, kako bismo lako čitali tekst metodama poput readLine().

preuzimanje Preuzmite izvorni kod Izvorni kod za "Programiranje soketa u Javi: Vodič." Stvorio Steven Haines za JavaWorld.

Primjer klijenta Java utičnice

Poradimo na kratkom primjeru koji izvršava HTTP GET protiv HTTP poslužitelja. HTTP je sofisticiraniji nego što to dopušta naš primjer, ali možemo napisati klijentski kôd za rješavanje najjednostavnijeg slučaja: zatražite resurs od poslužitelja i poslužitelj vraća odgovor i zatvara tok. Ovaj slučaj zahtijeva sljedeće korake:

  1. Stvorite utičnicu za web poslužitelj koji preslušava na portu 80.
  2. Nabavite a PrintStreamna poslužitelj i pošaljite zahtjev GET PATH HTTP/1.0, gdje PATHje traženi resurs na poslužitelju. Na primjer, ako bismo željeli otvoriti korijen web stranice, tada bi put bio /.
  3. Nabavite InputStreamposlužitelj, zamotajte ga s BufferedReaderi pročitajte odgovor red po red.

Popis 1 prikazuje izvorni kod za ovaj primjer.

Popis 1. SimpleSocketClientExample.java

paket com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; uvoz java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; javna klasa SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } Nizni poslužitelj = args [0]; Put niza = args [1]; System.out.println ("Učitavanje sadržaja URL-a:" + poslužitelj); pokušajte {// Povežite se s poslužiteljem Socket socket = new Socket (server, 80); // Stvaranje ulaznih i izlaznih tokova za čitanje i zapisivanje na poslužitelj PrintStream out = novi PrintStream (socket.getOutputStream ()); BufferedReader u = novi BufferedReader (novi InputStreamReader (socket.getInputStream ())); // Slijedite HTTP protokol GET HTTP / 1.0 nakon čega slijedi prazan redak out.println ("GET" + put + "HTTP / 1.0"); out.println (); // Čitanje podataka s poslužitelja dok ne završimo čitanje dokumenta String line = in.readLine (); while (linija! = null) {System.out.println (linija); linija = in.readLine (); } // Zatvorimo naše tokove u.close (); out.close (); socket.close (); } catch (Iznimka e) {e.printStackTrace (); }}}

Popis 1 prihvaća dva argumenta naredbenog retka: poslužitelj za povezivanje (pod pretpostavkom da se povezujemo s poslužiteljem na portu 80) i resurs za dohvaćanje. Stvara a Socketkoji pokazuje na poslužitelj i izričito navodi port 80. Zatim izvršava naredbu:

DOBIJTE PUT HTTP / 1.0 

Na primjer:

GET / HTTP / 1.0 

Što se upravo dogodilo?

Kada web stranicu dohvaćate s web poslužitelja, na primjer www.google.com, HTTP klijent koristi DNS poslužitelje za pronalaženje adrese poslužitelja: započinje traženjem domene najviše razine za comdomenu u kojoj je mjerodavni poslužitelj imena domene www.google.com. Zatim od tog poslužitelja imena domene traži IP adresu (ili adrese) za www.google.com. Dalje, on otvara utičnicu za taj poslužitelj na portu 80. (Ili, ako želite definirati drugi port, to možete učiniti dodavanjem dvotočke iza koje slijedi na primjer broj porta:. :8080) Konačno, HTTP klijent izvršava navedena metoda HTTP, kao što je GET, POST, PUT, DELETE, HEAD, ili OPTI/ONS. Svaka metoda ima svoju sintaksu. Kao što je prikazano u gornjim isječcima koda, GETmetoda zahtijeva put koji slijediHTTP/version numberi prazan redak. Da smo željeli dodati HTTP zaglavlja, mogli smo to učiniti i prije ulaska u novi redak.

U Popisu 1 pronašli smo OutputStreami zamotali ga PrintStreamkako bismo mogli lakše izvršavati naše tekstualne naredbe. Naš je kod dobio InputStream, umotao je u an InputStreamReader, koji ga je pretvorio u a Reader, a zatim umotao u BufferedReader. Koristili smo PrintStreamza izvršavanje naše GETmetode, a zatim BufferedReaderza čitanje odgovora red po red dok nismo dobili nullodgovor, ukazujući da je utičnica zatvorena.

Sada izvršite ovu klasu i dodajte joj sljedeće argumente:

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

Trebali biste vidjeti izlaz sličan onome dolje:

Loading contents of URL: www.javaworld.com HTTP/1.1 200 OK Date: Sun, 21 Sep 2014 22:20:13 GMT Server: 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: close     Gasoline Test Page

Success

This output shows a test page on JavaWorld's website. It replied back that it speaks HTTP version 1.1 and the response is 200 OK.

Java socket server example

We've covered the client side and fortunately the communication aspect of the server side is just as easy. From a simplistic perspective, the process is as follows:

  1. Create a ServerSocket, specifying a port to listen on.
  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(); } } }