Pengaturcaraan soket di Java: Tutorial
java.io
pakej asli
dan NIO, java.nio
API 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.Socket
kelas. Kod berikut membuka sambungan ke pelayan:
Soket soket = Soket baru (pelayan, port);
Setelah socket
contoh 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 OutputStream
dengan PrintStream
supaya kita dapat menulis teks dengan mudah seperti kaedah println()
. Sebagai contoh lain, kita dapat membungkus InputStream
dengan BufferedReader
, melalui InputStreamReader
, agar mudah membaca teks dengan kaedah seperti readLine()
.
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:
- Buat soket ke pelayan web yang mendengar di port 80.
- Dapatkan a
PrintStream
ke pelayan dan kirim permintaanGET PATH HTTP/1.0
, di manaPATH
sumber yang diminta di pelayan. Sebagai contoh, jika kita ingin membuka akar laman web maka jalannya akan menjadi/
. - Dapatkan
InputStream
ke pelayan, bungkus denganBufferedReader
dan 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 Socket
titik 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 com
domain 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, GET
kaedah ini memerlukan jalan diikutiHTTP/version number
dan garisan kosong. Sekiranya kita mahu menambahkan header HTTP, kita boleh melakukannya sebelum memasuki baris baru.
Dalam Penyenaraian 1, kami mengambil OutputStream
dan membungkusnya PrintStream
sehingga 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 PrintStream
untuk melaksanakan GET
kaedah kami dan kemudian menggunakan BufferedReader
untuk membaca respons demi baris sehingga kami mendapat null
respons, 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 BensinKejayaan
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:
- Buat
ServerSocket
, tentukan port untuk didengarkan. - Invoke the
ServerSocket
'saccept()
method to listen on the configured port for a client connection. - When a client connects to the server, the
accept()
method returns aSocket
through which the server can communicate with the client. This is the sameSocket
class that we used for our client, so the process is the same: obtain anInputStream
to read from the client and anOutputStream
write to the client. - 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. - Call the
ServerSocket
'saccept()
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 ServerSocketFactory
s 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(); } } }