• 多线程笔记


     随着访问量越来越多, BIO 和 伪异步IO 已经不能满足需求了. 所以后面又出了个 NIO.

    1. NIO 使用了一个 通道Channel 的概念, 他是一个双向通道, 可以读取和写入数据.

      程序中使用的 SocketChannel 读写操作都是异步的, 没有读写的数据最直接返回.

    2. NIO 使用了 多路复用器Selector, 轮询客户端的连接, 根据不同的标识, 干不同的事情.

     服务端创建序列图

     server: 

    public class Server implements Runnable{
    
        //多路复用器, 管理所有通道
        private Selector selector;
    
        private ServerSocketChannel serverChannel;
    
        private volatile boolean stop;
    
        public void stop(){
            this.stop = true;
        }
    
        public Server(int port){
            try{
             this.selector = Selector.open();
                //打开服务器通道
                serverChannel = ServerSocketChannel.open();
                //设置非阻塞模式
                serverChannel.configureBlocking(false);
                //绑定地址
                serverChannel.socket().bind(new InetSocketAddress(1234));
                //把服务器通道注册到多路复用器上, 监听 SelectionKey.OP_ACCEPT操作位
                serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    
                System.out.println("服务器端已启动 : " + port);
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            while (!stop) {
                try {
                    //让多路复用器开始监听, 并设置休眠时间为1s,
                    // 无论是否有读写事件发生, 每隔1s他都会被唤醒1次
                    selector.select(1000);
                    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                    //遍历多路复用器
                    while (keys.hasNext()) {
                        SelectionKey selectionKey = keys.next();
                        keys.remove();
                        //判断是否有效
                        if (!selectionKey.isValid()) {
                            continue;
                        }
    
                        //处理新接入的请求消息
                        if (selectionKey.isAcceptable()) {
                            accept(selectionKey);
                        }
    
                         if (selectionKey.isReadable()) {
                            read(selectionKey);
                        }
                    }
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            if(selector != null){
                try {
                    selector.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private  void read(SelectionKey selectionKey) {
            try {
                //读取缓冲区
                ByteBuffer readBuf = ByteBuffer.allocate(1024);
                //获取通道内注册的socket对象
                SocketChannel sc = (SocketChannel) selectionKey.channel();
                //读取数据
                int count = sc.read(readBuf);
                //没有数据
                if(count == -1){
                    selectionKey.channel().close();
                    selectionKey.cancel();
                    return;
                }
                //有数据, 则进行读取, 且读取后要进行复位
                readBuf.flip();
                byte[] bytes = new byte[readBuf.remaining()];
                readBuf.get(bytes);
                String msg = new String(bytes, "UTF-8").trim();
                System.out.println("from client : " + msg);
    
                if("几点了".equals(msg)){
                    String response = new DateTime(2020, 1, 1, 1, 1, 1).toString("yyyy-MM-dd HH:mm:ss");
                    byte[] resBytes = response.getBytes();
                    ByteBuffer outBuffer = ByteBuffer.allocate(resBytes.length);
                    outBuffer.put(resBytes);
                    outBuffer.flip();
                    sc.write(outBuffer);
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private  void accept(SelectionKey selectionKey) {
            try {
                //获取服务通道
                ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
                //执行阻塞方法, 等待客户端接入, 接入成功时, 返回 SocketChannel 实例
                SocketChannel client = serverChannel.accept();
                //设置非阻塞
                client.configureBlocking(false);
                //将新连接注册到多路复用器上, 且注册为 OP_READ
                client.register(selector, SelectionKey.OP_READ);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws Exception {
            new Thread(new Server(1234)).start();
        }
    }

    客户端创建序列图:

    Netty 权威指南里, 将客户端也加入了 Selector,进行轮询, 根据客户端与服务器连接的不同状态, 干不同的事情. 

     我这里偷个懒, 客户端就直接上了, 不加Selector

    public class ClientA {
        public static void main(String[] args) {
            InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 1234);
            ByteBuffer buf = ByteBuffer.allocate(1024);
            SocketChannel socketChannel = null;
            try {
                socketChannel = SocketChannel.open();
                socketChannel.connect(addr);
    
                String msg = "几点了";
                System.out.println("client : " + msg);
                byte[] bytes = msg.getBytes();
                //将数据放入缓冲区
                buf.put(bytes);
                //对缓冲区进行复位
                buf.flip();
                //写出数据
                socketChannel.write(buf);
                //清空缓冲区数据
                buf.clear();
    
                while (true) {
                    //读数据
                    ByteBuffer readBuf = ByteBuffer.allocate(1024);
                    int readBytes = socketChannel.read(readBuf);
                    if (readBytes == -1) {
                        break;
                    }
                    readBuf.flip();
                    byte[] bytes1 = new byte[readBuf.remaining()];
                    readBuf.get(bytes1);
                    String msg1 = new String(bytes1, "UTF-8").trim();
                    System.out.println("from server : " + msg1);
    
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            finally {
                if (socketChannel != null) {
                    try {
                        socketChannel.close();
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    结果:

    client:

    server: 

    参考:

    Netty权威指南

  • 相关阅读:
    Effective Java 第三版——26. 不要使用原始类型
    Effective Java 第三版——25. 将源文件限制为单个顶级类
    Effective Java 第三版——24. 优先考虑静态成员类
    Effective Java 第三版——23. 优先使用类层次而不是标签类
    Effective Java 第三版——22. 接口仅用来定义类型
    Effective Java 第三版——21. 为后代设计接口
    Effective Java 第三版——20. 接口优于抽象类
    Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用
    Effective Java 第三版——18. 组合优于继承
    Effective Java 第三版——17. 最小化可变性
  • 原文地址:https://www.cnblogs.com/elvinle/p/12388564.html
Copyright © 2020-2023  润新知