NIO重寫服務器

一、Channel與Buffer

Channel可以寫到不同的Buffer

同理,channel也可以從不同的Buffer中讀取數據

 

二、NIO重寫服務器

監聽客戶端到達

接收、回送客戶端

轉發客戶端數據到另外一個客戶端

多端消息的處理

 

2.1 IO阻塞下

2.2 NIO下客戶端連接方式

三、

3.1 圖示

 

3.2 代碼

服務端:

package net.qiujuer.lesson.sample.client;


import net.qiujuer.lesson.sample.client.bean.ServerInfo;
import net.qiujuer.library.clink.utils.CloseUtils;

import java.io.*;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;

public class TCPClient {
    private final Socket socket;
    private final ReadHandler readHandler;
    private final PrintStream printStream;

    public TCPClient(Socket socket,ReadHandler readHandler) throws IOException {
        this.socket = socket;
        this.readHandler = readHandler;
        this.printStream = new PrintStream(socket.getOutputStream());
    }

    public void exit(){
        readHandler.exit();
        CloseUtils.close(printStream);
        CloseUtils.close(socket);
    }

    public void send(String msg){
        printStream.println(msg);
    }

    public static TCPClient startWith(ServerInfo info) throws IOException {
        Socket socket = new Socket();
        // 超時時間
        socket.setSoTimeout(3000);

        // 連接本地,端口2000;超時時間3000ms
        socket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()), info.getPort()), 3000);

        System.out.println("已發起服務器連接,並進入後續流程~");
        System.out.println("客戶端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
        System.out.println("服務器信息:" + socket.getInetAddress() + " P:" + socket.getPort());

        try {
            ReadHandler readHandler = new ReadHandler(socket.getInputStream());
            readHandler.start();

            return new TCPClient(socket,readHandler);
        } catch (Exception e) {
            System.out.println("連接異常");
            CloseUtils.close(socket);
        }

       return null;
    }



    static class ReadHandler extends Thread {
        private boolean done = false;
        private final InputStream inputStream;

        ReadHandler(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public void run() {
            super.run();
            try {
                // 得到輸入流,用於接收數據
                BufferedReader socketInput = new BufferedReader(new InputStreamReader(inputStream));

                do {
                    String str;
                    try {
                        System.out.println("客戶端等待讀取數據");
                        // 客戶端拿到一條數據
                        str = socketInput.readLine();
                    } catch (Exception e) {
                        continue;
                    }
                    if (str == null) {
                        System.out.println("連接已關閉,無法讀取數據!");
                        break;
                    }
                    // 打印到屏幕
                    System.out.println(str);
                } while (!done);
            } catch (Exception e) {
                if (!done) {
                    System.out.println("連接異常斷開:" + e.getMessage());
                }
            } finally {
                // 連接關閉
                CloseUtils.close(inputStream);
            }
        }

        void exit() {
            done = true;
            CloseUtils.close(inputStream);
        }
    }
}
package net.qiujuer.lesson.sample.server.handle;


import net.qiujuer.library.clink.utils.CloseUtils;

import java.io.*;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ClientHandler {
    private final SocketChannel socketChannel;
    private final ClientReadHandler readHandler;
    private final ClientWriteHandler writeHandler;
    private final ClientHandlerCallback clientHandlerCallback;
    private final String clientInfo;

    public ClientHandler(SocketChannel socketChannel, ClientHandlerCallback clientHandlerCallback) throws IOException {
        this.socketChannel = socketChannel;

        //設置非阻塞模式
        socketChannel.configureBlocking(false);

        Selector readSelector = Selector.open();
        socketChannel.register(readSelector, SelectionKey.OP_READ);
        this.readHandler = new ClientReadHandler(readSelector);

        Selector writeSelector = Selector.open();
        socketChannel.register(writeSelector,SelectionKey.OP_WRITE);
        this.writeHandler = new ClientWriteHandler(writeSelector);

        this.clientHandlerCallback = clientHandlerCallback;
        this.clientInfo =socketChannel.getRemoteAddress().toString();
        System.out.println("新客戶端連接:" + clientInfo);
    }

    public String getClientInfo(){
        return clientInfo;
    }

    public void exit() {
        readHandler.exit();
        writeHandler.exit();
        CloseUtils.close(socketChannel);
        System.out.println("客戶端已退出:" + clientInfo);
    }

    public void send(String str) {
        writeHandler.send(str);
    }

    public void readToPrint() {
        readHandler.start();
    }

    private void exitBySelf() {
        exit();
        clientHandlerCallback.onSelfClosed(this);
    }

    public interface ClientHandlerCallback {
        //自身關閉通知
        void onSelfClosed(ClientHandler handler);

        //收到消息時通知
        void onNewMessageArrived(ClientHandler handler,String msg);
    }

    class ClientReadHandler extends Thread {
        private boolean done = false;
        private final Selector selector;
        private final ByteBuffer byteBuffer;

        ClientReadHandler(Selector selector) {
            this.selector = selector;
            this.byteBuffer = ByteBuffer.allocate(256);
        }

        @Override
        public void run() {
            super.run();
            try {
                do {
                    // 客戶端拿到一條數據
                    if(selector.select()==0){
                        if(done){
                            break;
                        }
                        continue;
                    }

                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()){
                        if(done){
                            break;
                        }
                        SelectionKey key = iterator.next();
                        iterator.remove();

                        if(key.isReadable()){
                            SocketChannel client = (SocketChannel) key.channel();
                            //清空操作
                            byteBuffer.clear();
                            //讀取
                            int read = client.read(byteBuffer);
                            if(read>0){
                                //丟棄換行符
                                String str = new String(byteBuffer.array(),0,read -1);
                                // 打印到屏幕
//                                System.out.println(str);
                                //onNewMessageArrived()方法在調用它的線程執行。比如連續給服務器發送了3條消息,按理說這裏應該收到三條消息,然後把消息進行轉發
                                //一旦在onNewMessageArrived()進行了阻塞,就會導致後面兩條消息接收的延遲。所以,儘量不阻塞主幹的線程。
                                clientHandlerCallback.onNewMessageArrived(ClientHandler.this,str);
                            }else {
                                System.out.println("客戶端已無法讀取數據!");
                                // 退出當前客戶端
                                ClientHandler.this.exitBySelf();
                                break;
                            }
                        }
                    }
                } while (!done);
            } catch (Exception e) {
                if (!done) {
                    System.out.println("連接異常斷開");
                    ClientHandler.this.exitBySelf();
                }
            } finally {
                // 連接關閉
                CloseUtils.close(selector);
            }
        }

        void exit() {
            done = true;
            selector.wakeup();
            CloseUtils.close(selector);
        }
    }

    class ClientWriteHandler {
        private boolean done = false;
        private final Selector selector;
        private final ByteBuffer byteBuffer;
        private final ExecutorService executorService;

        ClientWriteHandler(Selector selector) {
            this.selector = selector;
            this.byteBuffer = ByteBuffer.allocate(256);
            this.executorService = Executors.newSingleThreadExecutor();
        }

        void exit() {
            done = true;
            CloseUtils.close(selector);
            executorService.shutdownNow();
        }

        void send(String str) {
            if(done) {
                return;
            }
            executorService.execute(new WriteRunnable(str));
        }

        class WriteRunnable implements Runnable {
            private final String msg;

            WriteRunnable(String msg) {
                this.msg = msg+'\n';
            }

            @Override
            public void run() {
                if (ClientWriteHandler.this.done) {
                    return;
                }

                byteBuffer.clear();
                byteBuffer.put(msg.getBytes());
                //反轉操作
                byteBuffer.flip();

                while (!done&&byteBuffer.hasRemaining()) {
                    try {
                        int len = socketChannel.write(byteBuffer);
                        System.out.println("len = "+len+"   msg :"+msg);
                        //len=0 合法
                        if(len<0){
                            System.out.println("客戶端已無法發送數據");
                            ClientHandler.this.exitBySelf();
                            break;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

 

運行結果:

服務器可以接收、發送消息

 

客戶端也可以接收、發送消息

四、put模式

默認情況下,position=0,limit和capacity=容量值

每put一次,position就向後移動一位

五、get模式

position=0

通道從position=0開始讀取,每讀取一次,position後移一位,讀到limit結束

六、clear操作

將position重置爲0,將limit重置爲capacity,但是數據不會清空

 

七、反轉

將limit的值置爲position的值,將position置爲0,這樣就可以進行讀取操作了

 

八、讀模式和寫模式

 

 

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