Java NIO 學習(四)--ServerSocketChannel與SocketChannel

本機要講到的ServerSocketChannel、SocketChannel,與Java網絡編程中的ServerSocket、Socket是非常相識,至少從使用方式上來看是這樣,本質上都是TCP網絡套接字,只是多了“channel”;

一、SocketChanel

1、創建SocketChannel

有兩種方法可以獲取一個SocketChannel實例
1. 通過靜態方法open打開一個

SocketChannel socketChannel = SocketChannel.open();
  1. ServerSocketChannel接受一個連接請求後等到
SocketChannel socketChannel = serverSocketChannel.accept();

獲取到實例後可通過connect方法與服務端建立連接:

socketChannel.connect(new InetSocketAddress(1234));

2、讀寫數據

SocketChannel的讀寫數據與其他通道沒有區別,讀數據使用多個read方法,將數據讀取如一個buffer中,返回一個int值,表示成功讀取的字節數,返回-1表示讀取到了數據流的末尾了;

sizeBuffer.clear();
int read = socketChannel.read(sizeBuffer);

使用write方法將buffer中的數據寫入到通道中,同樣需要循環寫入;

buffer.flip();
while (buffer.hasRemaining()) {
    socketChannel.write(dest);
}

3、非阻塞模式

非阻塞就是普通套接字的區別了,SocketChannel在非阻塞模式下,許多方法都是可能直接返回不等待的:
1. connect方法,可能沒有完成連接建立就已經返回,需要使用finishConnect,判斷是否完成了連接;
2. read方法,可能沒有讀取任何就返回了(不是到數據末尾),需要通過返回值判斷;
3. write方法,與阻塞模式一樣,需要在循環中寫入;

二、ServerSocketChannel

1、創建ServerSocketC

通過靜態方法open獲取一個實例,並使用bind方法綁定地址:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(1234));

完成綁定後,可以通過accept方法,接受客戶端連接請求,與客戶端建立連接後,獲取到一個SocketChannel實例,通過這個實例與建立連接的客戶端進行通信;

2、非阻塞模式

ServerSocketChannel同樣有非阻塞模式,此時,accept方法在沒有連接請求是,可能返回Null,需要做判斷處理;

三、結合實例

通過ServerSocketChannel 和 SocketChannel實現一個簡單的消息發送例子:

1、服務端

服務端在循環中等待客戶端連接請求(阻塞模式下),對每一個請求使用一個單獨的線程進行通信處理

public class ServerSocketChannelServer {

    public static void main(String[] args) throws IOException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 10, 1000, TimeUnit.MILLISECONDS, 
                new ArrayBlockingQueue<Runnable>(100));

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(1234));

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            //每個連接使用一個單獨線程處理
            if (socketChannel != null) {
                executor.submit(new SocketChannelThread(socketChannel));
            }
        }
    }
}

2、處理線程

public class SocketChannelThread implements Runnable {

    private SocketChannel socketChannel;
    private String remoteName;

    public SocketChannelThread(SocketChannel socketChannel) throws IOException {
        this.socketChannel = socketChannel;
        this.remoteName = socketChannel.getRemoteAddress().toString();
        System.out.println("客戶:" + remoteName + " 連接成功!");
    }

    @Override
    public void run() {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
        StringBuilder sb = new StringBuilder();
        byte b[];
        while(true) {
            try {
                sizeBuffer.clear();
                int read = socketChannel.read(sizeBuffer);
                if (read != -1) {
                    sb.setLength(0);
                    sizeBuffer.flip();
                    int size = sizeBuffer.getInt();
                    int readCount = 0;
                    b = new byte[1024];
                    //讀取已知長度消息內容
                    while (readCount < size) {
                        buffer.clear();
                        read = socketChannel.read(buffer);
                        if (read != -1) {
                            readCount += read;
                            buffer.flip();
                            int index = 0 ;
                            while(buffer.hasRemaining()) {
                                b[index++] = buffer.get();
                                if (index >= b.length) {
                                    index = 0;
                                    sb.append(new String(b,"UTF-8"));
                                }
                            }
                            if (index > 0) {
                                sb.append(new String(b,"UTF-8"));
                            }
                        }
                    }
                    System.out.println(remoteName +  ":" + sb.toString());
                }
            } catch (Exception e) {
                System.out.println(remoteName + " 斷線了,連接關閉");
                try {
                    socketChannel.close();
                } catch (IOException ex) {
                }
                break;
            }
        }
    }

}

消息的發送和接收,在消息的前4個字節固定發送後面消息的內容長度,接收時,按照長度接收消息內容,使用這個方式的來解決“消息無邊界”問題;

3、客戶端

使用“分散(Scatter)”方式寫入

public class SocketChanneClient {

    public static void main(String[] args) throws IOException {

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(1234));
        while (true) {
            Scanner sc = new Scanner(System.in);
            String next = sc.next();
            sendMessage(socketChannel, next);
        }
    }

    public static void sendMessage(SocketChannel socketChannel, String mes) throws IOException {
        if (mes == null || mes.isEmpty()) {
            return;
        }
        byte[] bytes = mes.getBytes("UTF-8");
        int size = bytes.length;
        ByteBuffer buffer = ByteBuffer.allocate(size);
        ByteBuffer sizeBuffer = ByteBuffer.allocate(4);

        sizeBuffer.putInt(size);
        buffer.put(bytes);

        buffer.flip();
        sizeBuffer.flip();
        ByteBuffer dest[] = {sizeBuffer,buffer};
        while (sizeBuffer.hasRemaining() || buffer.hasRemaining()) {
            socketChannel.write(dest);
        }
    }
}

總最後的例子來看,確實與平常的網絡套接字沒有什麼區別,可以看出的最大區別應該就是非阻塞模式了;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章