zoukankan      html  css  js  c++  java
  • 多线程笔记

     随着访问量越来越多, 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权威指南

  • 相关阅读:
    hudson用SVN插件下载代码,用ant插件打包, 用SSH插件部署
    CSS加DIV布局
    CSS父元素高度随子元素高度变化而变化
    iframe父页面获取iframe子页面的元素 与 iframe子页面获取父页面元素
    基于ZooKeeper的Dubbo注册中心
    最近项目用到Dubbo框架,临时抱佛脚分享一下共探讨。
    接口测试中三种传参请求(Map、request、Integer)解析
    测试培训大纲第一课时,软件测试基础(培训待续中....)
    架构从最简单的数据访问框架(ORM)到资源调度和治理中心(SOA)说起
    让shell脚本在后台飞
  • 原文地址:https://www.cnblogs.com/elvinle/p/12388564.html
Copyright © 2011-2022 走看看