Panduan untuk Saluran Soket Ninkron NIO2

1. Gambaran keseluruhan

Dalam artikel ini, kami akan menunjukkan cara membina pelayan sederhana dan pelanggannya menggunakan API saluran Java 7 NIO.2.

Kami akan melihat kelas AsynchronousServerSocketChannel dan AsynchronousSocketChannel yang merupakan kelas utama yang digunakan dalam melaksanakan pelayan dan klien masing-masing.

Sekiranya anda baru menggunakan API saluran NIO.2, kami mempunyai artikel pengenalan di laman web ini. Anda boleh membacanya dengan mengikuti pautan ini.

Semua kelas yang diperlukan untuk menggunakan API saluran NIO.2 digabungkan dalam pakej saluran java.nio.channels :

import java.nio.channels.*;

2. Pelayan Dengan Masa Depan

Contoh AsynchronousServerSocketChannel dibuat dengan memanggil API terbuka statik di kelasnya:

AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();

Saluran soket pelayan asinkron yang baru dibuat terbuka tetapi belum terikat, jadi kami mesti mengikatnya ke alamat tempatan dan memilih port secara pilihan:

server.bind(new InetSocketAddress("127.0.0.1", 4555));

Kita juga boleh melewati nol sehingga menggunakan alamat tempatan dan mengikat ke port sewenang-wenang:

server.bind(null);

Setelah terikat, API penerimaan digunakan untuk memulai penerimaan sambungan ke soket saluran:

Future acceptFuture = server.accept();

Seperti operasi saluran tidak segerak, panggilan di atas akan kembali serta-merta dan pelaksanaannya berterusan.

Seterusnya, kita dapat menggunakan get API untuk meminta respons dari objek Future :

AsynchronousSocketChannel worker = future.get();

Panggilan ini akan disekat sekiranya perlu untuk menunggu permintaan sambungan dari pelanggan. Sebagai pilihan, kami dapat menentukan masa tamat jika kami tidak mahu menunggu selama-lamanya:

AsynchronousSocketChannel worker = acceptFuture.get(10, TimeUnit.SECONDS);

Setelah pengembalian panggilan di atas dan operasi berjaya, kita dapat membuat gelung di mana kita mendengar mesej masuk dan mengucapkannya kembali kepada klien.

Mari buat kaedah yang disebut runServer di mana kita akan melakukan penantian dan memproses sebarang mesej masuk:

public void runServer() { clientChannel = acceptResult.get(); if ((clientChannel != null) && (clientChannel.isOpen())) { while (true) { ByteBuffer buffer = ByteBuffer.allocate(32); Future readResult = clientChannel.read(buffer); // perform other computations readResult.get(); buffer.flip(); Future writeResult = clientChannel.write(buffer); // perform other computations writeResult.get(); buffer.clear(); } clientChannel.close(); serverChannel.close(); } }

Di dalam gelung, semua yang kami lakukan adalah membuat penyangga untuk dibaca dan ditulis bergantung pada operasi.

Kemudian, setiap kali kita membaca atau menulis, kita dapat terus menjalankan kod lain dan ketika kita siap memproses hasilnya, kita memanggil get () API pada objek Future .

Untuk memulakan pelayan, kami memanggil konstruktornya dan kemudian kaedah runServer di dalam utama :

public static void main(String[] args) { AsyncEchoServer server = new AsyncEchoServer(); server.runServer(); }

3. Pelayan Dengan Penyelesai Pengendali

Di bahagian ini, kita akan melihat bagaimana melaksanakan pelayan yang sama menggunakan pendekatan CompletionHandler dan bukannya pendekatan Masa Depan .

Di dalam konstruktor, kami membuat AsynchronousServerSocketChannel dan mengikatnya ke alamat tempatan dengan cara yang sama seperti yang kami lakukan sebelumnya:

serverChannel = AsynchronousServerSocketChannel.open(); InetSocketAddress hostAddress = new InetSocketAddress("localhost", 4999); serverChannel.bind(hostAddress);

Seterusnya, masih di dalam konstruktor, kami membuat gelung sementara di mana kami menerima sebarang sambungan masuk dari klien. Lingkaran sementara ini digunakan dengan ketat untuk mengelakkan pelayan keluar sebelum menjalin hubungan dengan pelanggan .

Untuk mengelakkan gelung berjalan tanpa henti , kami memanggil System.in.read () di hujungnya untuk menyekat pelaksanaan sehingga sambungan masuk dibaca dari aliran input standard:

while (true) { serverChannel.accept( null, new CompletionHandler() { @Override public void completed( AsynchronousSocketChannel result, Object attachment) { if (serverChannel.isOpen()){ serverChannel.accept(null, this); } clientChannel = result; if ((clientChannel != null) && (clientChannel.isOpen())) { ReadWriteHandler handler = new ReadWriteHandler(); ByteBuffer buffer = ByteBuffer.allocate(32); Map readInfo = new HashMap(); readInfo.put("action", "read"); readInfo.put("buffer", buffer); clientChannel.read(buffer, readInfo, handler); } } @Override public void failed(Throwable exc, Object attachment) { // process error } }); System.in.read(); }

Apabila sambungan dibuat, kaedah panggilan balik yang lengkap di dalam CompletionHandler operasi penerimaan dipanggil.

Jenis pengembaliannya adalah contoh AsynchronousSocketChannel . Sekiranya saluran soket pelayan masih terbuka, kami memanggil API penerimaan sekali lagi untuk bersiap sedia untuk sambungan masuk yang lain sambil menggunakan semula pengendali yang sama.

Seterusnya, kami menetapkan saluran soket yang dikembalikan ke contoh global. Kami kemudian memeriksa bahawa ia tidak kosong dan terbuka sebelum melakukan operasi di atasnya.

Titik di mana kita dapat memulakan operasi membaca dan menulis adalah di dalam API panggilan balik yang lengkap dari pengendali operasi penerimaan . Langkah ini menggantikan pendekatan sebelumnya di mana kita meninjau saluran dengan API get .

Perhatikan bahawa pelayan tidak akan keluar lagi setelah sambungan dibuat kecuali kita menutupnya secara jelas.

Perhatikan juga bahawa kami membuat kelas dalaman yang berasingan untuk mengendalikan operasi membaca dan menulis; BacaWriteHandler . Kita akan melihat bagaimana objek lampiran berguna pada ketika ini.

Pertama, mari kita lihat kelas ReadWriteHandler :

class ReadWriteHandler implements CompletionHandler
    
      { @Override public void completed( Integer result, Map attachment) { Map actionInfo = attachment; String action = (String) actionInfo.get("action"); if ("read".equals(action)) { ByteBuffer buffer = (ByteBuffer) actionInfo.get("buffer"); buffer.flip(); actionInfo.put("action", "write"); clientChannel.write(buffer, actionInfo, this); buffer.clear(); } else if ("write".equals(action)) { ByteBuffer buffer = ByteBuffer.allocate(32); actionInfo.put("action", "read"); actionInfo.put("buffer", buffer); clientChannel.read(buffer, actionInfo, this); } } @Override public void failed(Throwable exc, Map attachment) { // } }
    

The generic type of our attachment in the ReadWriteHandler class is a map. We specifically need to pass two important parameters through it – the type of operation(action) and the buffer.

Next, we will see how these parameters are used.

The first operation we perform is a read since this is an echo server which only reacts to client messages. Inside the ReadWriteHandler‘s completed callback method, we retrieve the attached data and decide what to do accordingly.

If it's a read operation which has completed, we retrieve the buffer, change the action parameter of the attachment and perform a write operation right away to echo the message to the client.

If it's a write operation which has just completed, we call the read API again to prepare the server to receive another incoming message.

4. The Client

After setting up the server, we can now set up the client by calling the open API on the AsyncronousSocketChannel class. This call creates a new instance of the client socket channel which we then use to make a connection to the server:

AsynchronousSocketChannel client = AsynchronousSocketChannel.open(); InetSocketAddress hostAddress = new InetSocketAddress("localhost", 4999) Future future = client.connect(hostAddress);

The connect operation returns nothing on success. However, we can still use the Future object to monitor the state of the asynchronous operation.

Let's call the get API to await connection:

future.get()

Setelah langkah ini, kita dapat mulai mengirim pesan ke pelayan dan menerima gema untuk hal yang sama. The sendMessage kaedah kelihatan seperti ini:

public String sendMessage(String message) { byte[] byteMsg = new String(message).getBytes(); ByteBuffer buffer = ByteBuffer.wrap(byteMsg); Future writeResult = client.write(buffer); // do some computation writeResult.get(); buffer.flip(); Future readResult = client.read(buffer); // do some computation readResult.get(); String echo = new String(buffer.array()).trim(); buffer.clear(); return echo; }

5. Ujian

Untuk mengesahkan bahawa aplikasi pelayan dan klien kami berjalan sesuai harapan, kami dapat menggunakan ujian:

@Test public void givenServerClient_whenServerEchosMessage_thenCorrect() { String resp1 = client.sendMessage("hello"); String resp2 = client.sendMessage("world"); assertEquals("hello", resp1); assertEquals("world", resp2); }

6. Kesimpulannya

Dalam artikel ini, kami telah meneroka API saluran soket tak segerak Java NIO.2. Kami dapat melalui proses membina pelayan dan klien dengan API baru ini.

Anda boleh mengakses kod sumber penuh untuk artikel ini dengan menggunakan projek Github.