BIO 是JAVA网络通信中同步阻塞的实现方式,NIO是JAVA的同步非阻塞方式,大致示意如下
Netty-JAVA基础实现,NIO基础_第1张图片

每个客户端以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 selectionKeys = selector.selectedKeys();
    
            Iterator 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 selectionKeys = selector.selectedKeys();
    
            Iterator 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下,会出现某名的异常