NioSocket 客户端与服务端交互实现

       java Nio是jdk1.4新增的io方式—–nio(new IO),这种方式在目前来说算不算new,更合适的解释应该是non-block IO。


       non-block是相对于传统的io方式来讲的。传统的Io方式是阻塞的,我们拿网络io来举例,传统的io模型如下:

Java 发送socket包保持心跳 java socket发消息_客户端



       服务端主线程负责不断地server.accept(),如果没有客户端请求主线程就会阻塞,当客户端请求时,主线程会通过线程池创建一个新的线程执行。简单解释就是一个线程负责一个客户端的socket,当客户端因网络等原因传递速度慢的时候,服务端对应的客户端的线程就会等待,很浪费资源。同时线程过少的话会影响服务的吞吐量,而线程过多的话由于上下文切换等原因会导致效率十分低下,传统的io方式并不适合如今的网络流量。


       Nio的模型如下:

Java 发送socket包保持心跳 java socket发消息_Java 发送socket包保持心跳_02



       nio相比传统的io模型,最大的特点是优化了线程的使用。nio通过selector可以使用一个线程去管理多个socket句柄,说是管理也不太合适,nio是采用的事件驱动模型,selector负责的是监控各个连接句柄的状态,不是去轮询每个句柄,而是在数据就绪后,将消息通知给selector,而具体的socket句柄管理则是采用多路复用的模型,交由操作系统来完成。selector充当的是一个消息的监听者,负责监听channel在其注册的事件,这样就可以通过一个线程完成了大量连接的管理,当注册的事件发生后,再调用相应线程进行处理。这样就不需要为每个连接都使用一个线程去维持长连接,减少了长连接的开销,同时减少了上下文的切换提高了系统的吞吐量。

java Nio的组成

java Nio主要由三个核心部分组成:
- Buffer
- Channel
- Selector

       所有的io的Nio都是从一个channel开始的,Channel有点类似于流,但是和流不同的是,channel是可以双向读写的。Channel有几种类型,主要包含文件io操作和网络io:
- FileChannel (文件io)
- DatagramChannel (udp数据报)
- SocketChannel (tcp客户端)
- ServerSocketChannel (tcp服务端)

       Buffer是一个中间缓存区,数据可以从channel读取到buffer,也可以从buffer写到channel中,在java中,传统方式与io的交互,需要将数据从堆内存读取到直接内存中,然后交由c语言来调用系统服务完成io的交互。而使用Buffer可以直接在直接内存中开辟内存区域,减少了io复制的操作,从而提高了io操作的效率。

#基本数据类型的buffer
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer

#文件内存映射buffer
- MappedByteBuffer

#直接内存区buffer
- DirectBuffer

       Selector允许单个线程处理多个channel,可以将多个channel教给selector管理,并注册相应的事件,而selector则采用事件驱动的方式,当注册的事件就绪后,调用相应的相应的线程处理该时间,不用使用线程去维持长连接,减少了线程的开销。Selector通过静态工厂的open方法建立,然后通过channel的register注册到Channel上。注册后通过select方法等待请求,select请求有long类型参数,代表等待时间,如果等待时间内接受到操作请求,则返回可以操作请求的数量,否则超时往下走。传入参数为零或者无参方法,则会采用阻塞模式知道有相应请求。收到请求后调用selectedKeys返回SelectionKey的集合。SelectionKey保存了处理当前请求的Channel和Selector,并且提供了不同的操作类型。SelectionKey的操作有四种:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE

下面为一个客户端与服务端实用NioSocket交互的简单例子:

//对selectionKey事件的处理
/**
 * description:
 *
 * @author wkGui
 */
interface ServerHandlerBs {
    void handleAccept(SelectionKey selectionKey) throws IOException;

    String handleRead(SelectionKey selectionKey) throws IOException;
}


/**
 * description:
 *
 * @author wkGui
 */
public class ServerHandlerImpl implements ServerHandlerBs {
    private int bufferSize = 1024;
    private String localCharset = "UTF-8";

    public ServerHandlerImpl() {
    }

    public ServerHandlerImpl(int bufferSize) {
        this(bufferSize, null);
    }

    public ServerHandlerImpl(String localCharset) {
        this(-1, localCharset);
    }

    public ServerHandlerImpl(int bufferSize, String localCharset) {
        this.bufferSize = bufferSize > 0 ? bufferSize : this.bufferSize;
        this.localCharset = localCharset == null ? this.localCharset : localCharset;
    }

    @Override
    public void handleAccept(SelectionKey selectionKey) throws IOException {
        //获取channel
        SocketChannel socketChannel = ((ServerSocketChannel) selectionKey.channel()).accept();
        //非阻塞
        socketChannel.configureBlocking(false);
        //注册selector
        socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));

        System.out.println("建立请求......");
    }

    @Override
    public String handleRead(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

        ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();

        String receivedStr = "";

        if (socketChannel.read(buffer) == -1) {
            //没读到内容关闭
            socketChannel.shutdownOutput();
            socketChannel.shutdownInput();
            socketChannel.close();
            System.out.println("连接断开......");
        } else {
            //将channel改为读取状态
            buffer.flip();
            //按照编码读取数据
            receivedStr = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
            buffer.clear();

            //返回数据给客户端
            buffer = buffer.put(("received string : " + receivedStr).getBytes(localCharset));
            //读取模式
            buffer.flip();
            socketChannel.write(buffer);
            //注册selector 继续读取数据
            socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }
        return receivedStr;
    }

}
//服务端server类
/**
 * description:
 *
 * @author wkGui
 */
public class NioSocketServer {

    private volatile byte flag = 1;

    public void setFlag(byte flag) {
        this.flag = flag;
    }

    public void start() {
        //创建serverSocketChannel,监听8888端口
        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.socket().bind(new InetSocketAddress(8888));
            //设置为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //为serverChannel注册selector
            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("服务端开始工作:");

            //创建消息处理器
            ServerHandlerBs handler = new ServerHandlerImpl(1024);

            while (flag == 1) {
                selector.select();
                System.out.println("开始处理请求 : ");
                //获取selectionKeys并处理
                Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    try {
                        //连接请求
                        if (key.isAcceptable()) {
                            handler.handleAccept(key);
                        }
                        //读请求
                        if (key.isReadable()) {
                            System.out.println(handler.handleRead(key));
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //处理完后移除当前使用的key
                    keyIterator.remove();
                }
                System.out.println("完成请求处理。");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//server端启动类
/**
 * description:
 *
 * @author wkGui
 */
public class ServerMain {
    public static void main(String[] args) {
        NioSocketServer server = new NioSocketServer();
        new Thread(() -> {
            try {
                Thread.sleep(10*60*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                server.setFlag((byte) 0);
            }
        }).start();
        server.start();
    }
}
//客户端client类
/**
 * description:
 *
 * @author wkGui
 */
public class NioSocketClient {
    public void start() {
        try (SocketChannel socketChannel = SocketChannel.open()) {
            //连接服务端socket
            SocketAddress socketAddress = new InetSocketAddress("localhost", 8888);
            socketChannel.connect(socketAddress);

            int sendCount = 0;

            ByteBuffer buffer = ByteBuffer.allocate(1024);

            //这里最好使用selector处理   这里只是为了写的简单
            while (sendCount < 10) {
                buffer.clear();
                //向服务端发送消息
                buffer.put(("current time : " + System.currentTimeMillis()).getBytes());
                //读取模式
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();

                //从服务端读取消息
                int readLenth = socketChannel.read(buffer);
                //读取模式
                buffer.flip();
                byte[] bytes = new byte[readLenth];
                buffer.get(bytes);
                System.out.println(new String(bytes, "UTF-8"));
                buffer.clear();


                sendCount++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

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

//client启动类
/**
 * description:
 *
 * @author wkGui
 */
public class ClientMain {
    public static void main(String[] args) {
        new NioSocketClient().start();
    }
}