Java网络编程之(四): TCP协议使用NIO实现多线程非阻塞Soket通信

上面我们介绍过,nio非阻塞soket通信,但是想要更加的让我们的程序性能更好,我们就需要用到线程池操作,

废话不多说,直接上代码

package com.example.demo.tcpserver;

/**
 * @ClassName NIOServerV3
 * @Description TODO
 * @Author zhurongfei
 * @Data 2020/5/5 13:03
 * Version 1.0
 **/

import com.example.demo.util.StringToHex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * NIO selector 多路复用reactor线程模型
 */
@Component
public class NIOServerV3 {
    @Autowired
    ServerThread serverThread;
    /**
     * 日志
     */
    private static Logger log = Logger.getLogger(NIOServerV3.class.getClass().toString());
    /** 处理业务操作的线程 */
    private static ExecutorService workPool = Executors.newCachedThreadPool();
    /***
     * 连接数
     */
   static List<String> countList = new ArrayList<>();
    /**
     * 封装了selector.select()等事件轮询的代码
     */
    abstract class ReactorThread extends Thread {

        Selector selector;
        LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

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

        private ReactorThread() throws IOException {
            selector = Selector.open();
        }

        volatile boolean running = false;

        @Override
        public void run() {
            // 轮询Selector事件
            while (running) {
                try {
                    // 执行队列中的任务
                    Runnable task;
                    while ((task = taskQueue.poll()) != null) {
                        task.run();
                    }
                    selector.select(1000);

                    // 获取查询结果
                    Set<SelectionKey> selected = selector.selectedKeys();
                    // 遍历查询结果
                    Iterator<SelectionKey> iter = selected.iterator();
                    while (iter.hasNext()) {
                        // 被封装的查询结果
                        SelectionKey key = iter.next();
                        iter.remove();
                        int readyOps = key.readyOps();
                        // 关注 Read 和 Accept两个事件
                        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                            try {
                                SelectableChannel channel = (SelectableChannel) key.attachment();
                                channel.configureBlocking(false);
                                handler(channel);
                                if (!channel.isOpen()) {
                                    key.cancel(); // 如果关闭了,就取消这个KEY的订阅
                                }
                            } catch (Exception ex) {
                                key.cancel(); // 如果有异常,就取消这个KEY的订阅
                            }
                        }
                    }
                    selector.selectNow();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private SelectionKey register(SelectableChannel channel) throws Exception {
            // 为什么register要以任务提交的形式,让reactor线程去处理?
            // 因为线程在执行channel注册到selector的过程中,会和调用selector.select()方法的线程争用同一把锁
            // 而select()方法实在eventLoop中通过while循环调用的,争抢的可能性很高,为了让register能更快的执行,就放到同一个线程来处理
            FutureTask<SelectionKey> futureTask = new FutureTask<>(() -> channel.register(selector, 0, channel));
            taskQueue.add(futureTask);
            return futureTask.get();
        }

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

    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 void newGroup() throws IOException {
        // 创建IO线程,负责处理客户端连接以后socketChannel的IO读写
        for (int i = 0; i < subReactorThreads.length; i++) {
            subReactorThreads[i] = new ReactorThread() {
                @Override
                public void handler(SelectableChannel channel) throws IOException {
                    // work线程只负责处理IO处理,不处理accept事件
                    SocketChannel ch = (SocketChannel) channel;
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    if( ch.read(requestBuffer) == -1){
                       String address = ""+ch.socket().getRemoteSocketAddress();
                        boolean status = countList.contains(address);
                        if(status){
                            Iterator<String> iterator = countList.iterator();
                            while (iterator.hasNext()) {
                                String next = iterator.next();
                                if (address.contains(next)) {
                                    iterator.remove();
                                    log.info("连接名:"+address+"已断开");
                                    log.info("当前连接数为:"+countList.size());
                                }
                            }
                        }
                    }
                    while (ch.isOpen() && ch.read(requestBuffer) != -1) {
                        // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
                        if (requestBuffer.position() > 0) break;
                    }
                    if (requestBuffer.position() == 0) return; // 如果没数据了, 则不继续后面的处理
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    StringToHex toHex = new StringToHex();
                    String returnStr = toHex.BinaryToHexString(content);
                    String firstline = returnStr;
                    log.info(Thread.currentThread().getName() + "收到数据,来自:" + ch.getRemoteAddress());

                    // TODO 业务操作 数据库、接口...
                    workPool.submit(() -> {
                        log.info("接收的16进制数据"+firstline);
                        try {
                            serverThread.readClient(firstline,toHex);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    });

                    // 响应结果 200
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World";
                    ByteBuffer buffer = ByteBuffer.wrap(response.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;
                    ReactorThread workEventLoop = subReactorThreads[index];
                    workEventLoop.do    Start();
                    SelectionKey selectionKey = workEventLoop.register(socketChannel);
                    selectionKey.interestOps(SelectionKey.OP_READ);
                    countList.add(""+socketChannel.getRemoteAddress());
                    log.info(Thread.currentThread().getName() + "收到新连接 : " + socketChannel.getRemoteAddress());
                    log.info("总连接数为:"+countList.size());
                }
            };
        }


    }

    /**
     * 初始化channel,并且绑定一个eventLoop线程
     *
     * @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);
        mainReactorThreads[index].doStart();
        SelectionKey selectionKey = mainReactorThreads[index].register(serverSocketChannel);
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
    }


 public static void main(String[] args) throws Exception {
        NIOServerV3 nioServerV3 = new NIOServerV3();
        nioServerV3.newGroup(); // 1、 创建main和sub两组线程
        log.info("创建main和sub两组线程完成!");
        nioServerV3.initAndRegister(); // 2、 创建serverSocketChannel,注册到mainReactor线程上的selector上
        log.info(" 创建serverSocketChannel,注册到mainReactor线程上的selector上完成!");
        nioServerV3.bind(); // 3、 为serverSocketChannel绑定端口
        log.info("为serverSocketChannel绑定端口完成!");
    }
}

虽然nio能解决一条通道,多个连接的情况,但是操作繁琐,复杂,理解起来难度太大,所以衍生出来如Netty、Main等高性能的网络编程辅助服务器,他们比nio更好操作,更便捷,更加的高性能!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
JAVA SOCKET 编程的经典之书,(中文版)里面的代码可直接复制使用! 目录: 第1章简介..........3 1.1 计算机网络,分组报文和协议..........3 1.2 关于地址..........6 1.3 关于名字..........8 1.4 客户端和服务器..........8 1.5 什么是套接字..........9 1.6 练习..........10 第2章基本套接字..........10 2.1 套接字地址..........10 2.2 TCP套接字..........17 2.2.1 TCP客户端..........17 2.2.2 TCP服务器端..........22 2.2.3 输入输出流..........26 2.3 UDP套接字..........28 2.3.1 DatagramPacket类..........28 2.3.2 UDP客户端..........30 2.3.3 UDP服务器端..........36 2.3.4 使用UDP套接字发送和接收信息..........38 2.4 练习..........40 第3章发送和接收数据..........41 3.1 信息编码..........42 3.1.1 基本整型..........42 3.1.2 字符串和文本..........48 3.1.3 位操作:布尔值编码..........50 3.2 组合输入输出流..........51 3.3 成帧与解析..........52 3.4 Java特定编码..........58 3.5 构建和解析协议消息..........59 3.5.1 基于文本的表示方法..........62 3.5.2 二进制表示方法..........65 3.5.3 发送和接收..........67 3.6 结束..........76 3.7 练习..........76 第4章进阶..........77 4.1 多任务处理..........77 4.1.1 Java 多线程..........78 4.1.2 服务器协议..........80 4.1.3 一客户一线程..........84 4.1.4 线程池..........86 4.1.5 系统管理调度:Executor接口..........89 4.2 阻塞和超时..........91 4.2.1 accept(),read()和receive()..........91 4.2.2 连接和写数据..........92 4.2.3 限制每个客户端的时间..........92 4.3 多接收者..........94 4.3.1 广播..........94 4.3.2 多播..........95 4.4 控制默认行为..........100 4.4.1 Keep-Alive..........100 4.4.2 发送和接收缓存区的大小..........101 4.4.3 超时..........101 4.4.4 地址重用..........102 4.4.5 消除缓冲延迟..........102 4.4.6 紧急数据..........103 4.4.7 关闭后停留..........103 4.4.8 广播许可..........103 4.4.9 通信等级..........104 4.4.10 基于性能的协议选择..........104 4.5 关闭连接..........104 4.6 Applets..........111 4.7 结束..........112 4.8 练习..........112 第5章 NIO..........112 5.1 为什么需要NIO?..........113 5.2 与Buffer一起使用Channel..........115 5.3 Selector..........118 5.4 Buffer详解..........125 5.4.1 Buffer索引..........125 5.4.2 创建Buffer..........126 5.4.3 存储和接收数据..........128 5.4.4 准备Buffer:clear(),flip(),和rewind()..........130 5.4.5 压缩Buffer中的数据..........132 5.4.6 Buffer透视:duplicate(),slice()等..........134 5.4.7 字符编码..........136 5.5 流(TCP)信道详解..........136 5.6 Selector详解..........139 5.6.1 在信道中注册..........139 5.6.2 选取和识别准备就绪的信道..........141 5.6.3 信道附件..........143 5.6.4 Selector小结..........144 5.7 数据报(UDP)信道..........144 5.8 练习..........149 1. 使用定长的写缓冲区改写TCPEchoClientNonblocking.java。..........149 2.使用Buffer和DatagramChannel编写一个回显客户端。..........149 第6章深入剖析..........149 6.1 缓冲和TCP..........152 6.2 死锁风险..........155 6.3 性能相关..........158 6.4 TCP套接字的生存周期..........158 6.4.1 连接..........158 6.4.2 关闭TCP连接..........164 6.5 解调多路复用揭秘..........167 6.6 练习..........169
### 回答1: 《Java网络编程(第版)》是一本经典的网络编程教材,主要介绍了Java语言在网络编程方面的应用。这本电子书包含了全面而详细的内容,涵盖了从基础知识到高级应用的全部内容。 书中首先介绍了网络编程的基本概念,如网络协议、套接字等,并通过实例来演示了如何使用Java语言进行网络通信。然后,书中介绍了Java提供的网络编程API,包括Socket、ServerSocket、URLConnection等,以及相关工具类。这些API和工具类提供了丰富的功能,能够满足各种网络编程需求。 此外,书中还介绍了常见的网络编程技术和协议,如TCP/IP、HTTP、FTP等,以及相关的安全性和性能优化等方面的知识。这些内容对于开发者来说十分实用,能够帮助他们更好地理解和应用网络编程技术。 《Java网络编程(第版)》的编写风格简洁明快,重点突出,易于理解。每个章节都包含了大量的代码示例和实践项目,能够帮助读者巩固所学知识,并将其应用到实际项目中。 总之,这本电子书是一本全面而实用的Java网络编程教材,适用于那些希望深入学习和应用网络编程技术的开发者。无论是初学者还是有一定经验的程序员,都可以从中获得丰富的知识和实践经验,提升自己的技术水平。 ### 回答2: 《Java网络编程(第版)》是一本关于Java网络编程的电子书。这本书是经验丰富的作者写的,内容丰富全面,包括了Java网络编程的基础知识和高级应用,并且还结合了实际案例和代码示例,非常适合想要学习和深入理解Java网络编程的读者。 这本电子书首先介绍了Java网络编程的基本知识,包括Socket编程、TCP和UDP协议、URL和URI等。然后,它详细讲解了Java网络编程的高级应用,如多线程服务器、非阻塞I/O、NIO和AIO等。此外,书中还介绍了Java网络编程相关的网络安全方面的内容,如HTTPS、SSL/TLS等。 这本电子书特点突出,内容深入浅出,思路清晰,对于初学者和有一定经验的开发人员都非常有帮助。同时,读者可以通过学习这本书,了解Java网络编程的最新发展和趋势,掌握实际应用中的技巧和经验。此外,电子书还提供了大量的示例代码和练习题,供读者进行实践和巩固。 总的来说,《Java网络编程(第版)》是一本内容全面且实用的电子书,适合有一定Java编程基础的开发人员进行学习和参考。无论是从事网络编程开发的工程师,还是想要深入了解Java网络编程的技术爱好者,都会从这本书中收获丰富的知识和经验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值