網(wǎng)絡編程-NIO 手動寫一個框架

一、服務端

1、服務端處理器

首先我們下一個服務端部分的處理器

/**
 * 類說明:
 * 編寫NIO的服務端部分的處理器,主要是為了啟動服務,收到請求的報文,然后返回返回的報文
 */
public class NioServerHandle  implements Runnable{
   private Selector selector;//選擇器
   private ServerSocketChannel serverChannel;//通道,用來溝通系統(tǒng)和程序之間的交互事件
   private volatile boolean started;  //端口監(jiān)聽是否啟動狀態(tài)

    /**
     * 構造方法
     * 初始化選擇器 selector
     * 初始化渠道  serverChannel
     * 設置渠道的阻塞方式  ,true為阻塞式編程,false為非阻塞式
     *  這里主要是基本的API使用方式
     * @param port   服務開發(fā)的端口號
     */
    public NioServerHandle(int port) {
        try {
            selector = Selector.open();   
            serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            started = true;

            serverChannel.socket().bind( new InetSocketAddress( port ) );
            serverChannel.register( selector,SelectionKey.OP_CONNECT  );
            started = true;
            System.out.println("服務端口已經(jīng)啟動");

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
    /**
     * 停止服務
     */
    public void stop(){
        started = false;
    }
    @Override
    public void run() {
        while (started){//這里是判斷服務是否啟動著,如果為false就關閉接口
            try {
                selector.select();//選擇器選擇中,這是哥阻塞方法,除非有事件觸發(fā),否則一般都是阻塞了
                Set<SelectionKey> keys = selector.selectedKeys();//獲取到所有觸發(fā)的事件
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while (it.hasNext()){//遍歷所有的事件
                    key = it.next();
                    it.remove();
                    handleInput(key);//依次處理事件
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(selector!=null){
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
  
   /**
     * 處理事件的方法
     * 如果事件為可讀事件,就讀取緩存中的所有東西,經(jīng)過處理,然后返回。
     * 這個方法有個欠缺,如果請求的報文長度大于1024字節(jié)的時候,就會出現(xiàn)報文不完全的情況。
     * @param key 操作類型
     * @throws IOException
     */
    private void handleInput(SelectionKey key) throws IOException {
        if(key.isValid()){//先判斷事件是不是有效事件
          if(key.isAcceptable()){
                ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
                SocketChannel sc = ssc.accept();
                System.out.println("=======建立連接===");
                sc.configureBlocking(false);
                sc.register(selector,SelectionKey.OP_READ);
            }
            //處理新接入的請求信息
            if(key.isReadable()){ 
                /**isReadbale()官網(wǎng)的解釋,判斷這個key的同時是否完成
                 * Tests whether this key's channel is ready for reading.
                 */
                System.out.println("=====socket channel 數(shù)據(jù)準備完成,可以去讀 讀取===");
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer buffer  = ByteBuffer.allocate(1024);
                int readBytes = sc.read(buffer);//從sc中讀取1024的數(shù)據(jù)放到buffer里邊
                if(readBytes>0){
                    buffer.flip();//讀寫轉換
                    byte[] bytes = new byte[buffer.remaining()];////buffer.remaining  :buffer可以讀取的數(shù)據(jù)的長度
                   /**
                     * buffer.get(bytes)方法的官方解釋,將buffer里邊的byte傳入到參數(shù)的數(shù)組中
                     * This method transfers bytes from this buffer into the given
                     *   destination array.
                     */
                    buffer.get(bytes); 
                    String message = new String(bytes,"utf-8");
                    System.out.println("服務器收到消息,"+message);
                    String result = response(message);//這個是調(diào)用業(yè)務邏輯的方法
                    doWrite(sc,result);//將返回的報文寫入到channer中

                }
                else if(readBytes<0){
                    key.cancel();
                    sc.close();
                }
            }

  if (key.isWritable()){
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer attr = (ByteBuffer) key.attachment();
                if(attr.hasRemaining()){
                    int count =  sc.write( attr );
                    System.out.println("write "+ count +
                    "byte and has R"+attr.hasRemaining());
                }else {
                    sc.register( selector,SelectionKey.OP_READ );
                }

            }
        }
    }
 /**
     * 開始處理返回報文,傳入的參數(shù)是通道和返回報文,將返回報文寫入到渠道中去
     * @param sc  傳入的渠道信息
     * @param response  傳入的返回報文
     * @throws IOException
     */
 private void doWrite(SocketChannel sc, String response) throws IOException {
        byte[] bytes = response.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        writeBuffer.put(bytes);
        writeBuffer.flip();
//        sc.write(writeBuffer);
        serverChannel.register( selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE,writeBuffer );
    }
    /**
     * 業(yè)務邏輯處理
     * @param msg 傳入的參數(shù)是請求報文。
     * @return
     */
    public static String response(String msg){
        return "Hello,"+msg+",Now is "+new java.util.Date(
                System.currentTimeMillis()).toString() ;
    }
}

2、服務端啟用函數(shù)

  private  static NioServerHandle nioServerHandle;
    public static void main(String[] args) {
        start();
    }
    private static void start(){
        if(nioServerHandle!=null){
            nioServerHandle.stop();//如果服務已經(jīng)起起來了就關了重新啟動一下
        }
        nioServerHandle = new NioServerHandle(12000);//表示監(jiān)聽的端口號
        new Thread(nioServerHandle,"server").start();//開始服務,run()方法開始調(diào)用
    }

二、客戶端代碼的編寫

1、客戶端的框架部分

/**
 * 類說明:客戶端調(diào)用的處理器
 */
public class NioClientHandle implements Runnable {
    /**
     * 需要定義要鏈接哪個服務端,要連接哪個端口,是否要啟動這個連接,渠道是多少
     */
    private String host;
    private int port;
    private volatile boolean started;
    private Selector selector;
    private SocketChannel socketChannel;

    /**
     * 構造函數(shù),一個服務端用一個實例
     * @param host   ip
     * @param port   端口
     */
    public NioClientHandle(String host,int port){
        this.host = host;
        this.port = port;
        try {
            //創(chuàng)建選擇器
            this.selector = Selector.open();
            //打開監(jiān)聽通道
            socketChannel = SocketChannel.open();
            //設置是否為阻塞模式
            socketChannel.configureBlocking(false);
            started = true;
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
    public void stop(){
        started = false;
    }
    @Override
    public void run() {
        try {
//            先連接
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }

        while (started){
            try {
                selector.select();//阻塞方法,至少一個注冊事件發(fā)生的時候,就會繼續(xù)

                Set<SelectionKey> keys = selector.selectedKeys();//獲取到事件
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key =null;
                //遍歷所有的
                while(it.hasNext()){
                    key = it.next();
                    it.remove();//現(xiàn)將處理過的selectionKey從選定的集合中刪除,如果沒有刪除,仍然在事件集合中以一個激活的建出現(xiàn),這會是我們再次處理

                    try {
                        handleInput(key);//具體處理事件的方法
                    } catch (Exception e) {
                        if(key!=null){
                            key.cancel();
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 處理的邏輯
     * @param key 傳入的操作類型
     * @throws Exception
     */
    private void handleInput(SelectionKey key) throws Exception {
        if(key.isValid()){

            SocketChannel sc = (SocketChannel) key.channel();//
            if (key.isConnectable()){//如果是連接事件的話
                if(sc.finishConnect()){//確認鏈接已經(jīng)建立
                    socketChannel.register(selector, SelectionKey.OP_READ);//連接建立就去訂閱閱讀事件,然后有返回的時候,就會進入下邊的readable事件處理里邊
                }else {
                    System.exit(-1);
                }
            }
            if(key.isReadable()){
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(buffer);
                if(readBytes>0){
                    //讀到的數(shù)據(jù),進行具體的業(yè)務處理//這里應該是返回的報文

                }else if(readBytes<0){
                    key.cancel();
                    sc.close();
                }
            }
        }
    }

    /**
     * 進行連接的具體操作
     * @throws IOException
     */
    private void doConnect() throws IOException {
        if(socketChannel.connect(new InetSocketAddress(host,port))){}//如果連接的時候已經(jīng)連接上了就直接處理
        else{
            socketChannel.register(selector,SelectionKey.OP_CONNECT);//如果沒有連接上就訂閱事件,進入到Connectable里邊
        }
    }
   /**
     * 進行連接的具體操作
     * @throws IOException
     */
    private void doConnect() throws IOException {
        if(socketChannel.connect(new InetSocketAddress(host,port))){}//如果連接的時候已經(jīng)連接上了就直接處理
        else{
            socketChannel.register(selector,SelectionKey.OP_CONNECT);//如果沒有連接上就訂閱事件,進入到Connectable里邊
        }
    }
//發(fā)送消息
    public void sendMsg(String msg){
      //socketChannel,一開始初始化的
        doWrite(socketChannel,msg);
    }

    /**
     * 用來發(fā)送信息
     * @param sc  渠道
     * @param msg 寫入的信息
     * @return
     */
    private void doWrite(SocketChannel sc,String msg){
        byte[] bytes = msg.getBytes();
        //定義buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate( bytes.length );
        //將bytes放入buffer
        byteBuffer.put( bytes );
        //改變buffer的讀寫狀態(tài)
        byteBuffer.flip();
        //緩存進入渠道
        try {
            sc.write( byteBuffer );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、啟用框架

public class NioClient {
    private static  NioClientHandle nioClientHandle;

    public static void main(String[] args) {
        start();
        NioClient.sendMsg("1123123");

    }

    private static void start() {
        if(nioClientHandle!=null){
            nioClientHandle.stop();
            nioClientHandle = new NioClientHandle("",1);
            new Thread(nioClientHandle,"server").start();
        }
    }

    private static boolean sendMsg(String msg) {
        nioClientHandle.sendMsg(msg);
        return false;
    }
}

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 概要 64學時 3.5學分 章節(jié)安排 電子商務網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,789評論 0 3
  • Python語言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個如下例子,分析運行結果: 代碼一: a = 1 def...
    時光清淺03閱讀 556評論 0 0
  • Python語言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個如下例子,分析運行結果: 代碼一: a = 1 def...
    伊森H閱讀 3,175評論 0 15
  • 減肥是一個愛美女生一輩子的話題,可是減肥有很多種,有的女生選了我認為最壞的那種節(jié)食減肥。 節(jié)食減肥vs好好吃飯 我...
    酒十三閱讀 418評論 0 7
  • 今天四月份了,回顧上個月,反思能改進的。 3月份讓自己做了很多改變,也堅持做了很多事情。每天都沒斷過的:瑜伽健身、...
    璐籽堡閱讀 248評論 0 0

友情鏈接更多精彩內(nèi)容