1. 程式人生 > >【Java網路程式設計】:JDK API實現OIO和NIO

【Java網路程式設計】:JDK API實現OIO和NIO

前言

網路程式設計是Java的一大難點,JDK自帶的api可以實現網路程式設計。

我們將從一個應用程式開始我們對傳輸的學習,這個應用程式只簡單地接受連線,然後向客戶端寫“Hi!”,然後關閉連線。

1. OIO實現

應用程式的阻塞(OIO)版程式碼如下:

package chx.demo;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
public class PlainOioServer { public void serve(int port) throws IOException { //1. 將伺服器繫結到指定埠 final ServerSocket socket = new ServerSocket(port); try { for(;;) { // 2. for迴圈不斷地接受連線 final Socket clientSocket = socket.accept(); System.
out.println( "Accepted connection from " + clientSocket); // 3. 建立一個新的執行緒來處理該連線 new Thread(new Runnable() { @Override public void run() { OutputStream out; try
{ // 4. 將訊息寫給已連線的客戶端 out = clientSocket.getOutputStream(); out.write("Hi!\r\n".getBytes( Charset.forName("UTF-8"))); out.flush(); // 5. 關閉連線 clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } finally { try { clientSocket.close(); } catch (IOException ex) { // ignore on close } } // 7.啟動執行緒 } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }

這段程式碼完全可以處理中等數量的併發客戶端。但是隨著應用程式變得流行起來,你會發現它並不能很好地伸縮到支撐成千上萬的併發連入連線。

你決定改用非同步網路程式設計,但是很快就發現非同步API 是完全不同的,以至於現在你不得不重寫你的應用程式。

2. NIO實現

其非阻塞版程式碼如下:

package nia.chapter4;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class PlainNioServer {
    public void serve(int port) throws IOException {
        // 例項化ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        
        // 1. 將伺服器繫結到選定的埠
        ss.bind(address);
        // 2. 開啟Selector來處理 Channel
        Selector selector = Selector.open();
        // 3. 將ServerSocketChannel註冊到Selector以接受連線
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;){
            try {
                // 4. 等待需要處理的新事件;阻塞將一直持續到下一個傳入事件
                selector.select();
            } catch (IOException ex) {
                ex.printStackTrace();
                //handle exception
                break;
            }
            // 5. 獲取所有接收事件的SelectionKey例項
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    // 6.檢查事件是否是一個新的已經就緒可以被接受的連線
                    if (key.isAcceptable()) {
                        ServerSocketChannel server =
                                (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        // 7.接受客戶端,並將它註冊到選擇器
                        client.register(selector, SelectionKey.OP_WRITE |
                                SelectionKey.OP_READ, msg.duplicate());
                        System.out.println(
                                "Accepted connection from " + client);
                    }
                    // 8.檢查套接字是否已經準備好寫資料
                    if (key.isWritable()) {
                        SocketChannel client =
                                (SocketChannel) key.channel();
                        ByteBuffer buffer =
                                (ByteBuffer) key.attachment();
                        while (buffer.hasRemaining()) {
                            // 9. 將資料寫到已連線的客戶端
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        // 10.關閉連線
                        client.close();
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        // ignore on close
                    }
                }
            }
        }
    }
}

如同你所看到的,雖然這段程式碼所做的事情與之前的版本完全相同,但是程式碼卻截然不同。

如果為了用於非阻塞I/O 而重新實現這個簡單的應用程式,都需要一次完全的重寫的話,那麼不難想象,移植真正複雜的應用程式需要付出什麼樣的努力。

所以Java JDK原生實現NIO是比較複雜的,學習門檻比較大。