Netty-JAVA基礎實現,NIO基礎

BIO 是JAVA網絡通信中同步阻塞的實現方式,NIO是JAVA的同步非阻塞方式,大致示意如下
Netty-JAVA基礎實現,NIO基礎

每個客戶端以socketchannel(可以視同bio下的socket)向服務器發送連接或者請求,服務器端在啓動時創建一個ServerSocketChannel,用於綁定服務的端口和IP以及處理連接到socket的請求.同時ServerSocketChannel也註冊在selector下。

當selector以指定的時間間隔進行輪詢時,如果發現有新的請求進來,會根據情況採取兩個方向的處理。一個是新建連接的請求,由ServerSocketChannel通過accept去生成一個新的socketChannel,並註冊到selector上。或者在輪詢時發現個某個socketchannel滿足了某類處理要求(如讀數據),則進行相應的處理處理

下附相關代碼
客戶端
package netty.nio;

public class ClientThreadMain {

public static void main(String[] args) {

    String ip = "127.0.0.1";
    int port = 9999;

    new Thread(new NIOSocketClient(ip,port)).start();

}

}

package netty.nio;

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

/**

  • NIO模式客戶端實現
  • @author [email protected]
  • */
    public class NIOSocketClient implements Runnable{

    private String ip;
    private int port;
    private boolean running = true;
    private Selector selector;
    private SocketChannel socketChannel;

    //初始化時完成ip,port selector與socketChannel的設置
    public NIOSocketClient(String ip,int port) {
    this.ip = ip;
    this.port = port;

    try {
        selector = Selector.open();
        socketChannel = SocketChannel.open();
        //設置爲非阻塞的
        socketChannel.configureBlocking(false);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    }

    @Override
    public void run() {
    //發起連接請求
    connect();
    while(running) {

        /*
         * 超時毫秒數
         * timeout --If positive, block for up to timeoutmilliseconds,
         *  more or less, while waiting for achannel to become ready; 
         * if zero, block indefinitely;must not be negative
         */
        try {
            selector.select(1000);
            //迭代處理selector中所有的SelectionKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
    
            Iterator<SelectionKey> it = selectionKeys.iterator();
            SelectionKey key = null;
            while(it.hasNext()) {
    
                key = it.next();
                it.remove();
                handler(key);
            }
    
        } catch (IOException e) {
    
            e.printStackTrace();
        }
    }

    }

    //處理器,處理當連接成功與收到返回成功後的各自處理業務
    private void handler(SelectionKey selectionKey) {

    if(selectionKey.isValid()) {
    
        SocketChannel sc = (SocketChannel)selectionKey.channel();
        //如果已經聯通,那麼開始發送數據
        if(selectionKey.isConnectable()) {
            if(sc.isConnected()) {
                try {
                    sc.register(selector, SelectionKey.OP_READ);
                    sendMessage(sc);
                    selectionKey.cancel();
                    try {
                        sc.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                } catch (ClosedChannelException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            //如果可以讀取,那麼開始進行讀取
            if(selectionKey.isReadable()) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                try {
                    int bufferSize = sc.read(buffer);
                    if(bufferSize>0) {
                        //按照這個長度創建一個比特數組接收傳入的數據
                        //The number of elements remaining in this buffer
                        byte[] bytes = new byte[buffer.remaining()];
                        //轉成實際的字符串打印出來
                        String info  = new String(bytes,"UTF-8");
                        System.out.println("傳回的信息爲:"+info);
    
                    }else {
                        selectionKey.cancel();
                        sc.close();
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    }

    /*

    • 發送消息
      */
      private void sendMessage(SocketChannel sc) {

      byte[] bytes = "HELLO JAVA NIO".getBytes();
      ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
      buffer.put(bytes);
      buffer.flip();
      try {
      sc.write(buffer);
      if(!buffer.hasRemaining()) {
      System.out.println("已經發送信息");
      }
      } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }
      }

    /*

    • 一啓動線程就發起服務端請求
      */
      private void connect() {

      try {
      socketChannel.connect(new InetSocketAddress(ip,port));
      if(socketChannel.finishConnect()) {
      //如果連通即將socketChannel註冊到selector上,關注的事件是讀
      socketChannel.register(selector, SelectionKey.OP_READ);
      //向服務端發送數據
      sendMessage(socketChannel);
      }

      else {
          socketChannel.register(selector, SelectionKey.OP_CONNECT);
      }

      } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }
      }

}
上邊代碼可以看出客戶端的Selector也在輪詢確認服務端的返回情況,並進行處理

以下是服務端實現

package netty.nio;

/**

  • NIO模式服務端實現
  • @author [email protected]
  • */
    public class NIOSocketServer {

    public static void main(String[] args) {

    int port = 9999;
    
    NIOSocketSelector selector = new NIOSocketSelector(port);
    
    new Thread(selector).start();

    }

}

package netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
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;

/**

  • NIO的多路複用器的實現
  • @author [email protected]
  • */
    public class NIOSocketSelector implements Runnable{

    //多路複用器
    private Selector selector;
    //設通道綁定隊列的最大長度
    private int backlog = 800;
    //任務持續執行標誌
    private boolean running = true;

    private int requestNum =0;

    //通道
    private ServerSocketChannel serverSocketChannel;

    //傳入端口參數
    public NIOSocketSelector(int port) {

    try {
        //設置多路複用器
        selector = Selector.open();
        //設置通道
        serverSocketChannel = ServerSocketChannel.open();
        /* 設置通道爲非阻 塞模式
         * block If true then this channel will be placed inblocking mode; 
         * if false then it will be placednon-blocking mode
         */
        serverSocketChannel.configureBlocking(false);
        /* 最大隊列長度
         * backlog - requested maximum length of the queue of incoming connections
         */
        serverSocketChannel.socket().bind( new InetSocketAddress(port), backlog);
    
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    }

    @Override
    public void run() {

    while(running) {
        //System.out.println("啓動服務端 監聽請求");
    
        /*
         * 超時毫秒數
         * timeout --If positive, block for up to timeoutmilliseconds,
         *  more or less, while waiting for achannel to become ready; 
         * if zero, block indefinitely;must not be negative
         */
        try {
            //每秒進行一次輪詢處理
            selector.select(1000);
            //迭代處理selector中所有的SelectionKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
    
            Iterator<SelectionKey> it = selectionKeys.iterator();
            SelectionKey key = null;
            while(it.hasNext()) {
    
                key = it.next();
                it.remove();
                handler(key);
            }
    
        } catch (IOException e) {
    
            e.printStackTrace();
        }
    
    }
    if(selector!=null) {
        try {
            selector.close();
        }catch(IOException ie) {
    
        }
    }

    }

    private void handler(SelectionKey selectionKey) {

    //如果當前的Key可用,取到當前key對應的channel
    if(selectionKey.isValid()) {
        ServerSocketChannel ssc;
        SocketChannel sc;
        //如果是新來的請求就新建一條socketchannel綁定到selector
        if(selectionKey.isAcceptable()) {
    
            ssc = (ServerSocketChannel)selectionKey.channel();
            try {
    
                sc = ssc.accept();
                //設置爲非阻塞模式
                sc.configureBlocking(false);
                //註冊到Selector下,設定爲讀打開
                sc.register(selector, SelectionKey.OP_READ);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        //如果是可以讀取數據了,那就進行數據讀取
        if(selectionKey.isReadable()) {
    
            requestNum  = requestNum +1;
            System.out.println("當前請求數:"+requestNum);
            sc = (SocketChannel)selectionKey.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            try {
                //判斷讀入數據的長度
                int bufferSize = sc.read(buffer);
                if(bufferSize>0) {
                    //將緩存字節數組的指針設置爲數組的開始序列即數組下標0
                    //這樣從buffer的頭部開始對buffer進行遍歷 
                    buffer.flip();
                    //按照這個長度創建一個比特數組接收傳入的數據
                    //The number of elements remaining in this buffer
                    byte[] bytes = new byte[buffer.remaining()];
                    buffer.get(bytes);
                    //轉成實際的字符串打印出來
                    String info  = new String(bytes,"UTF-8");
                    System.out.println("傳入的信息爲:"+info);
                    //對傳入的信息進行響應返回
                    response(sc,new Long(System.currentTimeMillis()).toString());
                }else if(bufferSize<0){
                    selectionKey.cancel();
                    sc.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }

    }

    /*

    • 處理回寫的數據
      */
      private void response(SocketChannel socketChannel,String msg) {

      if(msg!=null && msg.trim().length()>0) {
      byte[] bytes = msg.getBytes();
      //按字符串長度新建ByteBuffer
      ByteBuffer buffer = ByteBuffer.allocate(bytes.length) ;
      buffer.put(bytes);
      buffer.flip();
      try {
      //將返回的數據信息寫入到SocketChannel中
      socketChannel.write(buffer);
      } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }
      }
      }

    public void isRunning(boolean running) {
    this.running = running;
    }

}

這裏注意幾個地方,一個是backlog,這個值在windows下設置爲大於等於1000後會拋一個異常。需要將之調小,目前懷疑的是當對端 口的監聽大於一定數量,會報這個異常

第二是running雖然寫了,但是並沒有用。只用於提供一個可能性,可以在某些必要的時候打斷這種運行

第三是在IDE下進行調試時最好在兩個IDE下進行調試。不要把服務端與客戶端都啓動在一個IDE下,會出現某名的異常

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