NIO TCP/IP协议 网络通信

解释:NIO → non-blocking input output(非阻塞式的输入和输出)
使用场景:一般用于高并发场景下;请求 (客户端)、响应 (服务端)两方,约定好使用TCP/IP通信;双方约定好报文格式 (报文头+报文体)及编码格式 (这里用UTF-8),报文头内容 (约定好长度比如8位,不够前面补零)里面内容为报文体长度,再根据报文头内容,获取后面的报问体的内容。
例如:报文示例: 00000007aaaaaaa;报文体内容为7个a,所以报文头长度为7不够八位前面补零。

一、 服务端

NioServerThread: nio的服务类(selector、buffer、channel)
ServerThreadOperate: 新线程用来处理客户端请求

/**
 * selector轮询线程的开启并绑定channel通道
 * @author zhb
 */
public class NioServerThread {
    
    private static int count = 0;
    // 事件轮询器
    private static Selector selector;
    // 服务通道
    private static ServerSocketChannel serverSocketChannel;
    
    // 开启一个服务线程
    public void action() throws IOException, InterruptedException{
        // 开启服务端的事件轮询器,只有这一个线程
        selector = Selector.open();
        // 开通服务端的socket通道
        serverSocketChannel = ServerSocketChannel.open();
        // socket为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(Constant.serverSocketPort));    
        // channel和socket绑定,注册可以接入事件
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        System.err.println("---------------------线程开启-----------------------");
        handerSelector();
    }
    
    /**
     * 处理轮询事件
     * @throws IOException 
     */
    private void handerSelector() throws IOException {
        
        while(true){
            //多长时间轮询一次
//          selector.select(1000);
            selector.select();
            //事件轮询器,查出的kernel中的所有的事件标识
            Set selectedKeys = selector.selectedKeys();   
            Iterator iterator = selectedKeys.iterator();
            
            // SelectionKey 事件标识,每个请求的客户端都有自己的一个key作为唯一标识,该key不同的事件处理
            SelectionKey key;
            while(iterator.hasNext()){
                key = iterator.next();
                // 将该事件去除掉,防止重复
                iterator.remove();      
                //检查key事件是否被处理过,如果没有被处理过
                if(key.isValid()){
                    // 处理客户端的请求接入
                    if(key.isAcceptable()){
                        handlerAccept(key);
                    // 处理可读事件   
                    }else if(key.isReadable()){ 
                        handlerRead(key);
                    // 处理可写事件   
                    }if(key.isWritable()){ 
                        handlerWrite(key); 
                    }
                }
            }
        }
    }
    private void handlerWrite(SelectionKey key) throws UnsupportedEncodingException, IOException, ClosedChannelException {
        SocketChannel accept = (SocketChannel)key.channel();
        String resStr = "ab";
        System.err.println("可以写了");
        byte[] resByte = resStr.getBytes(Constant.charset);
        
        ByteBuffer resBuffer = ByteBuffer.allocate(resByte.length);
        resBuffer.put(resByte);
        resBuffer.flip();
        accept.write(resBuffer);
        accept.register(selector, SelectionKey.OP_CONNECT);
    }   

    /**
     * 处理可读的事件 
     * (1)只处理读取数据,不管响应信息
     * (2)读取到请求信息,根据请求信息返回处理后的响应信息(常见的),
     * 
     * @param selector 事件轮询器
     * @param key  某个客户端的标识
     * @throws IOException
     * @throws UnsupportedEncodingException
     * @throws ClosedChannelException
     */
    private static void handlerRead(SelectionKey key) throws IOException, UnsupportedEncodingException, ClosedChannelException {
        // 该通道是接收该客户连接请求时,开启的通道
        SocketChannel socketChannel = (SocketChannel)key.channel();
        
        socketChannel.configureBlocking(false);
        
        // 按照客户端和服务端约定好的(报文头+报文体)的格式信息
        String reqHeadStr = getStrFromChannel(socketChannel, Constant.reqHeadLength);
        String reqBodyStr = getStrFromChannel(socketChannel, Integer.valueOf(reqHeadStr));
        System.err.println("-------------------nio服务端接收请求数据→" + reqBodyStr);
        // 这种情况可以开启一个新的线程处理
        new Thread(new ServerThreadOperate(reqBodyStr, socketChannel)).start();
        
        // 下面是简单的处理信息,可以用简单的测试信息
//      ByteBuffer buffer = ByteBuffer.allocate(1024);
//      socketChannel.read(buffer);
//      // 整理buffer中的数据,position复位
//      buffer.flip();
//      // 建立和buffer中相同长度的字节的数组
//      byte[] bytes = new byte[buffer.remaining()];
//      buffer.get(bytes);
//      buffer.clear();
//      String reqStr = new String(bytes,"utf-8");
//      // 这里简单处理请返回的信息
//      simplehandler(socketChannel, reqBodyStr);
        
        // 根据上面的处理情况,再注册这里的信息
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }
    
    
    // 根据参数长度,从该客户的channel中获取返回字符串
    public static String getStrFromChannel(SocketChannel socketChannel, int bufferLength) throws IOException{
        
        // 先读取报文头内容
        ByteBuffer buffer = ByteBuffer.allocate(bufferLength);
        byte[] reqbyte = new byte[buffer.remaining()];
        // 先从通道中读取请求报文头长度的buffer信息
        socketChannel.read(buffer);
        // 整理buffer中的数据,position复位
        buffer.flip();
        // 把buffer中的信息放入到byte字节数组中
        buffer.get(reqbyte);
        buffer.clear();
        // 将字节数组转成字符串,返回
        return new String(reqbyte, Constant.charset);
    }

    // 不用开启新线程 ,简单模拟烦回信息
    private static void simplehandler(SocketChannel socketChannel, String reqStr) throws UnsupportedEncodingException, IOException, ClosedChannelException {
        ByteBuffer respBuffer = ByteBuffer.allocate(1024);
        byte[] respByte = ("响应信息:"+reqStr).getBytes(Constant.charset);
        // 将响应信息写入buffer中
        respBuffer.put(respByte);
        System.err.println("服务端返回了请求信息"+respBuffer);
        // 整理buffer,position复位
        respBuffer.flip();
        // 发送响应信息
        socketChannel.write(respBuffer);
    }   

    /**
     * 处理客户端的请求接入的事件
     * 
     * @param selector  事件轮询器
     * @param serverSocketChannel  注册OP_ACCEPT事件的服务端channel
     * @throws IOException
     * @throws ClosedChannelException
     */
    private static void handlerAccept(SelectionKey key) throws IOException,
            ClosedChannelException {
        System.err.println(count+"--------------------------OP_READ------------------");
        
        //返回创建此键的通道
        serverSocketChannel = (ServerSocketChannel)key.channel();
        // 建立和客户端的链接, 因为 OP_ACCEPT是注册在 serverSocketChannel上;每个客户有自己的SocketChannel通道
        SocketChannel accept = serverSocketChannel.accept();
        // 非阻塞
        accept.configureBlocking(false);
        // 开启注册该客户端的可读时间,即该客户上传完所有的请求数据到系统的kernel的buffer缓存中后,开启的的可读事件通知
        accept.register(selector, SelectionKey.OP_READ);
    }
    
    
    public static void main(String[] args) throws IOException, InterruptedException {
        // nio线程的开启
        NioServerThread thread  = new NioServerThread();
        thread.action();
    }
    
}


/**
 * 开启一个新线程,用来处理客户端的请求 ;也可以用简单模式不用这个类
 * @author zhb
 */
public class ServerThreadOperate implements Runnable {
    
    // 客户端请求的信息
    private String reqStr;
    private int count;
    
    // 和某个客户端的通道
    private SocketChannel socketChannel;

    public ServerThreadOperate(String reqStr) {
        this.reqStr = reqStr;
    }

    public ServerThreadOperate(String reqStr, SocketChannel socketChannel) {
        this.reqStr = reqStr;
        this.socketChannel = socketChannel;
    }

    // 线程主体
    public void run() {
        
        ByteBuffer respBuffer = ByteBuffer.allocate(1024);
        byte[] respBodyByte = null;
        try {
            respBodyByte = ("响应信息为:"+reqStr).getBytes(Constant.charset);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
            
        //转成约定好的报文头长度,不足的前面补零
        StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
        byte[] respHeadByte = String.format(format.toString(), respBodyByte.length).getBytes();
        
        respBuffer.put(respHeadByte);
        respBuffer.put(respBodyByte);
        System.err.println("服务端返回了请求信息:"+respBuffer);
        // buffer内的信息要复位整理
        respBuffer.flip();
        try {
            // 烦回要烦回的信息
            socketChannel.write(respBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }

}

/**
 * 系统常量
 * 
 * @author zhb
 *
 */
public class Constant {
    
    // 请求报文头的位数
    public static final int reqHeadLength = 8;
    // 返回响应报文头的长度
    public static final int respHeadLength = 8;
    
    
    // 字节数组和字符创之间的转换时的编码 
    public static final String charset = "UTF-8";
    
    //接收请求的最长时间 单位毫秒
    public static final int reqTimeOut = 5000;
    
    //接收请求的服务端口
    public static final int serverSocketPort = 8080;
}

你可能感兴趣的:(NIO TCP/IP协议 网络通信)