手写http? (基于nio Selector多路复用 Reactor模式 高仿netty 实现)

  服务端 整体流程(这里是把Reactor线程给独立拆分成一个类)
   0.启动main方法
   1.main 方法 启动 添加 mainReactor(连接事件) 和 subReactor(读事件)到数组
   2.initAndRegister()初始化方法 打开一个通道 绑定到Selector 上并绑定感兴趣事件accept
   3.从mainReactorThreads数组中拿出一个ReactorThread去执行建立连接
   4.调用拿出来的 ReactorThread的start方法 运行该线程  遍历当前绑定到Selector的channel 获取到之后 丢给具体的handler
   5.获取到之后 要remove掉当前通道 否则会重复处理
   6.mainReactorThreads中会 随机获取一个数组subReactorThreads中的ReactorThread去执行io操作
   7.此时绑定的就是读事件
   8.mainReactor中的handler方法中 会分发给具体的io handler去进行读取数据
   9.返回响应的数据
 代码 --------NettyServer类    

public class NettyServer {
    private ServerSocketChannel serverSocketChannel;
    // 1、创建多个线程 - accept处理reactor线程 (accept线程)
    private ReactorThread[] mainReactorThreads = new ReactorThread[1];
    // 2、创建多个线程 - io处理reactor线程  (I/O线程)
    private ReactorThread[] subReactorThreads = new ReactorThread[8];
    /** 处理业务操作的线程 池*/
    private static ExecutorService workPool = new ThreadPoolExecutor(5, Runtime.getRuntime().availableProcessors()*2, 50, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50),
                                                                     new ThreadFactory() {
                                                                         @Override
                                                                         public Thread newThread(Runnable r) {
                                                                             Thread thread=new Thread(r);
                                                                             thread.setName("Test 业务线程");
                                                                             return thread;
                                                                         }
                                                                     });

    /**
     * 启动方法
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
       NettyServer nioServer = new NettyServer();
        nioServer.newGroup(); // 1、 创建main和sub两组线程
        nioServer.initAndRegister(); // 2、 创建serverSocketChannel,注册到mainReactor线程上的selector上
        nioServer.bind(); // 3、 为serverSocketChannel绑定端口
    }
    /**
     * 初始化线程组
     */
    private void newGroup() throws IOException {
        // 创建IO线程,负责处理客户端连接以后socketChannel的IO读写
        for (int i = 0; i < subReactorThreads.length; i++) {
            subReactorThreads[i] = new ReactorThread() {
                //实现handler方法
                @Override
                public void handler(SelectableChannel channel) throws IOException {
                    // work线程只负责处理IO处理,不处理accept事件
                    SocketChannel ch = (SocketChannel) channel;
                    //读取指定长度
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    long len = -2;
                    while (ch.isOpen() && (len = ch.read(requestBuffer)) != -1) {
                        // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
                        if (requestBuffer.position() > 0) break;
                    }
                    if(len == -1) { // !很重要客户端关闭连接的读事件信息,不做处理则会有大量的读事件
                       ch.close();
                        return;
                    }
                    if (requestBuffer.position() == 0) return; // 如果没数据了, 则不继续后面的处理
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    System.out.println(Thread.currentThread().getName() + "收到数据,来自:" + ch.getRemoteAddress());

                    // TODO 业务操作 数据库、接口...
                    StringBuffer stringBuffer=new StringBuffer();
                    workPool.submit(() -> {
                        stringBuffer.append("ceshi------");
                        System.out.println(Thread.currentThread().getName()+"执行业务逻辑");
                    });

                    // 响应结果 200
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World My first NettyDemo";
                    stringBuffer.append(response);

                    ByteBuffer buffer = ByteBuffer.wrap(stringBuffer.toString().getBytes());
                    while (buffer.hasRemaining()) {
                        ch.write(buffer);
                    }
                }
            };
        }

        // 创建mainReactor线程, 只负责处理serverSocketChannel
        for (int i = 0; i < mainReactorThreads.length; i++) {
            mainReactorThreads[i] = new ReactorThread() {
                AtomicInteger incr = new AtomicInteger(0);

                @Override
                public void handler(SelectableChannel channel) throws Exception {
                    // 只做请求分发,不做具体的数据读取
                    ServerSocketChannel ch = (ServerSocketChannel) channel;
                    //连接
                    SocketChannel socketChannel = ch.accept();
                    //设置非阻塞
                    socketChannel.configureBlocking(false);
                    // 收到连接建立的通知之后,随机获取一个 分发给I/O线程继续去读取数据
                    int index = incr.getAndIncrement() % subReactorThreads.length;
                    //获取到一个指定的subReactorThread 去执行
                    ReactorThread workEventLoop = subReactorThreads[index];
                    //获取到io的线程
                    workEventLoop.doStart();
                    SelectionKey selectionKey = workEventLoop.register(socketChannel);
                    selectionKey.interestOps(SelectionKey.OP_READ);
                    System.out.println(Thread.currentThread().getName() + "收到新连接 : " + socketChannel.getRemoteAddress());
                }
            };
        }
    }

    /**
     * 初始化服务端的channel,并且开启主线程
     *
     * @throws IOException IO异常
     */
    private void initAndRegister() throws Exception {
        // 1、 创建ServerSocketChannel
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        // 2、 将serverSocketChannel注册到selector
        int index = new Random().nextInt(mainReactorThreads.length);
        //3.启动 执行run方法 把channel分派给不同的handler 去执行一个mainReactorThread
        mainReactorThreads[index].doStart();
        //注册通道到selector mainReactorThreads在第一步时就已经初始化了
        SelectionKey selectionKey = mainReactorThreads[index].register(serverSocketChannel);
        //绑定感兴趣事件accept
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
    }


    /**
     * 绑定端口
     *
     * @throws IOException IO异常
     */
    private void bind() throws IOException {
        //  1、 正式绑定端口,对外服务
        serverSocketChannel.bind(new InetSocketAddress(8080));
        System.out.println("启动完成,端口8080");
    }

}

---------ReactorThread类

abstract class ReactorThread extends Thread {

        Selector selector;

        /**
         * Selector监听到有事件后,调用这个方法
         */
        public abstract void handler(SelectableChannel channel) throws Exception;

    /**
     * 使用Selector的好处在于:
     * 使用更少的线程来就可以来处理通道了,
     * 相比使用多个线程,避免了线程上下文切换带来的开销。
     * @throws IOException
     */
    public ReactorThread() throws IOException {
            selector = Selector.open();
        }

        volatile boolean running = false;

        @Override
        public void run() {
            // 轮询Selector事件
            while (running) {
                try {
                    //选择一组其相应通道准备好进行I / O操作的键。
                    selector.select(1000);
                    // 返回此选择器的选择键集。
                    Set<SelectionKey> selected = selector.selectedKeys();
                    // 遍历查询结果
                    Iterator<SelectionKey> iter = selected.iterator();
                    while (iter.hasNext()) {
                        // 被封装的查询结果
                        SelectionKey key = iter.next();
                        // 删除已选的key,以防重复处理
                        iter.remove();
                       // Ready Set是通道已经准备就绪的操作的集合,在一个选择后,你会是首先访问这个Ready Set。
                        int readyOps = key.readyOps();
                        // 关注 Read 和 Accept两个事件 监听到read 和accept时间后 调用handler
                        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                           try {
                               //检索当前附件。
                                SelectableChannel channel = (SelectableChannel) key.attachment();
                                //设置非阻塞
                                channel.configureBlocking(false);
                                //在这个地方 把channel分发给Thread去执行
                                handler(channel);
                                if (!channel.isOpen()) {
                                   System.out.println("客户端关闭了连接");
                                    key.cancel(); // 如果关闭了,就取消这个KEY的订阅
                                }
                            } catch (Exception ex) {
                                key.cancel(); // 如果有异常,就取消这个KEY的订阅
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        protected SelectionKey register(SelectableChannel channel) throws Exception {
            return channel.register(selector, 0, channel);
        }

        protected void doStart() {
            if (!running) {
                running = true;
                start();
            }
        }
    }

--------------------------------------------客户端 nioClient

public class NIOClient {

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
        while (!socketChannel.finishConnect()) {
            // 没连接上,则一直等待
            Thread.yield();
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        // 发送内容
        String msg = scanner.nextLine();
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
        // 读取响应
        System.out.println("收到服务端响应:");
        ByteBuffer requestBuffer = ByteBuffer.allocate(1024);

        while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
            // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
            if (requestBuffer.position() > 0) break;
        }
        requestBuffer.flip();
        byte[] content = new byte[requestBuffer.limit()];
        requestBuffer.get(content);
        System.out.println(new String(content));
    /*    scanner.close();
        socketChannel.close();*/
    }

}

运行流程 :

1.启动 NettyServer的main方法

2.启动nioClient的main方法

3.nioClient控制台 输入内容

4.返回结果

效果

nettyServer 控制台输出:

Connected to the target VM, address: '127.0.0.1:57166', transport: 'socket'
启动完成,端口8080
Thread-8收到新连接 : /127.0.0.1:57189
123
Thread-0收到数据,来自:/127.0.0.1:57189
Test 业务线程执行业务逻辑
Disconnected from the target VM, address: '127.0.0.1:57166', transport: 'socket'

客户端控制台 输出:

请输入:
123
收到服务端响应:
ceshi------HTTP/1.1 200 OK
Content-Length: 11

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值