Pengaturcaraan soket di Java: Tutorial

Tutorial ini adalah pengenalan kepada pengaturcaraan soket di Java, dimulai dengan contoh pelayan klien sederhana yang menunjukkan ciri-ciri dasar Java I / O. Anda akan diperkenalkan dengan java.io pakej asli  dan NIO, java.nioAPI I / O tanpa blok yang diperkenalkan di Java 1.4. Akhirnya, anda akan melihat contoh yang menunjukkan rangkaian Java seperti yang dilaksanakan dari Java 7 ke hadapan, di NIO.2

Pengaturcaraan soket mempunyai dua sistem yang saling berkomunikasi. Secara amnya, komunikasi rangkaian terdapat dalam dua pilihan: Transport Control Protocol (TCP) dan User Datagram Protocol (UDP). TCP dan UDP digunakan untuk tujuan yang berbeza dan keduanya mempunyai kekangan yang unik:

  • TCP adalah protokol yang agak mudah dan boleh dipercayai yang membolehkan pelanggan membuat sambungan ke pelayan dan kedua sistem untuk berkomunikasi. Dalam TCP, setiap entiti mengetahui bahawa muatan komunikasi telah diterima.
  • UDP adalah protokol tanpa sambungan dan sesuai untuk senario di mana anda tidak semestinya memerlukan setiap paket untuk sampai ke destinasinya, seperti streaming media.

Untuk menghargai perbezaan antara TCP dan UDP, pertimbangkan apa yang akan berlaku jika anda menstrim video dari laman web kegemaran anda dan ia menjatuhkan bingkai. Adakah anda lebih suka pelanggan melambatkan filem anda untuk menerima bingkai yang hilang atau adakah anda lebih suka agar video terus diputar? Protokol penstriman video biasanya memanfaatkan UDP. Kerana TCP menjamin penghantaran, itu adalah protokol pilihan untuk HTTP, FTP, SMTP, POP3, dan sebagainya.

Dalam tutorial ini, saya memperkenalkan anda untuk pengaturcaraan soket di Java. Saya menyajikan satu siri contoh pelayan-pelanggan yang menunjukkan ciri-ciri dari kerangka Java I / O yang asli, kemudian secara beransur-ansur maju untuk menggunakan ciri-ciri yang diperkenalkan di NIO.2.

Soket Java sekolah lama

Dalam pelaksanaan sebelum NIO, kod soket klien Java TCP dikendalikan oleh java.net.Socketkelas. Kod berikut membuka sambungan ke pelayan:

 Soket soket = Soket baru (pelayan, port); 

Setelah socketcontoh kami disambungkan ke pelayan, kami dapat mulai mendapatkan aliran input dan output ke pemutus. Aliran input digunakan untuk membaca data dari pelayan sementara aliran output digunakan untuk menulis data ke pelayan. Kami dapat menjalankan kaedah berikut untuk mendapatkan aliran input dan output:

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

Kerana ini adalah aliran biasa, aliran yang sama yang akan kami gunakan untuk membaca dan menulis ke fail, kami dapat mengubahnya menjadi bentuk yang paling sesuai untuk kes penggunaan kami. Sebagai contoh, kita dapat membungkus OutputStreamdengan PrintStreamsupaya kita dapat menulis teks dengan mudah seperti kaedah println(). Sebagai contoh lain, kita dapat membungkus InputStreamdengan BufferedReader, melalui InputStreamReader, agar mudah membaca teks dengan kaedah seperti readLine().

muat turun Muat turun kod sumber Kod sumber untuk "Pengaturcaraan soket di Java: Tutorial." Dicipta oleh Steven Haines untuk JavaWorld.

Contoh pelanggan soket Java

Mari kita ikuti contoh pendek yang melaksanakan HTTP GET terhadap pelayan HTTP. HTTP lebih canggih daripada yang dibenarkan oleh contoh kami, tetapi kami dapat menulis kod pelanggan untuk menangani kes paling mudah: meminta sumber dari pelayan dan pelayan mengembalikan respons dan menutup aliran. Kes ini memerlukan langkah-langkah berikut:

  1. Buat soket ke pelayan web yang mendengar di port 80.
  2. Dapatkan a PrintStreamke pelayan dan kirim permintaan GET PATH HTTP/1.0, di mana PATHsumber yang diminta di pelayan. Sebagai contoh, jika kita ingin membuka akar laman web maka jalannya akan menjadi /.
  3. Dapatkan InputStreamke pelayan, bungkus dengan BufferedReaderdan baca respons demi baris.

Penyenaraian 1 menunjukkan kod sumber untuk contoh ini.

Penyenaraian 1. SimpleSocketClientExample.java

pakej com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; kelas awam SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Penggunaan: SimpleSocketClientExample"); System.exit (0); } Pelayan rentetan = args [0]; Jalur rentetan = args [1]; System.out.println ("Memuatkan kandungan URL:" + pelayan); cuba {// Sambungkan ke pelayan Socket socket = Socket baru (pelayan, 80); // Buat aliran input dan output untuk membaca dan menulis ke pelayan PrintStream out = PrintStream baru (socket.getOutputStream ()); BufferedReader dalam = BufferedReader baru (InputStreamReader baru (socket.getInputStream ())); // Ikuti protokol HTTP GET HTTP / 1.0 diikuti oleh garis kosong out.println ("GET" + path + "HTTP / 1.0"); keluar.println (); // Baca data dari pelayan sehingga kita selesai membaca dokumen String line = in.readLine (); sementara (baris! = null) {System.out.println (baris); line = in.readLine (); } // Tutup aliran kami di.close (); keluar.tutup (); socket.close (); } tangkapan (Pengecualian e) {e.printStackTrace (); }}}

Penyenaraian 1 menerima dua argumen baris perintah: pelayan untuk disambungkan (dengan andaian bahawa kita menyambung ke pelayan di port 80) dan sumber untuk diambil. Ini membuat Sockettitik yang menunjuk ke pelayan dan secara eksplisit menentukan port 80. Kemudian melaksanakan perintah:

DAPATKAN PATH HTTP / 1.0 

Sebagai contoh:

DAPATKAN / HTTP / 1.0 

Apa yang baru berlaku?

Apabila anda mengambil halaman web dari pelayan web, seperti www.google.com, klien HTTP menggunakan pelayan DNS untuk mencari alamat pelayan: ia bermula dengan meminta pelayan domain tingkat atas untuk domain tempat pelayan nama comdomain yang berwibawa untuk www.google.com. Kemudian ia meminta pelayan nama domain untuk alamat IP (atau alamat) untuk www.google.com. Seterusnya, ia membuka soket ke pelayan tersebut di port 80. (Atau, jika anda ingin menentukan port yang lain, anda boleh melakukannya dengan menambahkan titik dua diikuti dengan nombor port, misalnya:. :8080) Akhirnya, klien HTTP melaksanakan kaedah HTTP yang dinyatakan, seperti GET, POST, PUT, DELETE, HEAD, atau OPTI/ONS. Setiap kaedah mempunyai sintaksisnya sendiri. Seperti yang ditunjukkan dalam potongan kod di atas, GETkaedah ini memerlukan jalan diikutiHTTP/version numberdan garisan kosong. Sekiranya kita mahu menambahkan header HTTP, kita boleh melakukannya sebelum memasuki baris baru.

Dalam Penyenaraian 1, kami mengambil OutputStreamdan membungkusnya PrintStreamsehingga kami dapat dengan lebih mudah melaksanakan perintah berdasarkan teks kami. Kod kami memperoleh sebuah InputStream, membungkus itu menjadi InputStreamReader, yang mengubahnya menjadi a Reader, dan kemudian membungkusnya menjadi a BufferedReader. Kami menggunakan kaedah PrintStreamuntuk melaksanakan GETkaedah kami dan kemudian menggunakan BufferedReaderuntuk membaca respons demi baris sehingga kami mendapat nullrespons, yang menunjukkan bahawa soket telah ditutup.

Sekarang jalankan kelas ini dan lulus argumen berikut:

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

Anda mesti melihat output yang serupa dengan yang ada di bawah:

Memuatkan kandungan URL: www.javaworld.com HTTP / 1.1 200 OK Tarikh: Ahad, 21 Sep 2014 22:20:13 Pelayan GMT: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Bensin-Lokal X-Bensin-Umur: 8 Panjang Kandungan: 168 Terakhir Diubahsuai: Sel, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Jenis Kandungan : text / html Berbeza: Sambungan Terima-Pengekodan: tutup Halaman Ujian Bensin

Kejayaan

Output ini menunjukkan halaman ujian di laman web JavaWorld. Ia menjawab kembali bahawa ia menggunakan HTTP versi 1.1 dan responsnya adalah 200 OK.

Contoh pelayan soket Java

Kami telah membahas aspek pelanggan dan untungnya aspek komunikasi dari sisi pelayan sama mudahnya. Dari perspektif sederhana, prosesnya adalah seperti berikut:

  1. Buat ServerSocket, tentukan port untuk didengarkan.
  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(); } } }