socket服务器多线程优化

在单线程环境下一个线程虽然可以执行任务但是所有的任务都交给一个线程来做当任务积累起来时,前面的任务会影响后续任务的执行,并且现在都是多核处理器我们需要尽可能利用cpu所以多线程 的优化就是一个不错的选择。

我们选择多线程后可以对任务进行分类,一个线程只执行某一特定任务,比如我们设置boss线程来处理客户端的连接请求,设置worker线程来处理客户端的读写操作等

但是引入多线程后线程间的执行顺利我们需要控制,比如当boss线程接收到一个请求后执行**SocketChannel sc = ssc.accept();**语句获取到连接,但是还需要将这个连接交给worker线程来处理读写请求,这就要求我们一定存在worker线程并且正在工作,但是工作状态的worker线程在没有任务进来的情况下会被多路选择器(selector)阻塞在select方法处,而我们又需要将连接sc注册到worker的selector上让它去监听这个连接上的事件,由于selector被阻塞我们无法完成注册,这就尬住了。

解决方法就是我们控制register和select方法,使register方法能再select方法执行后执行,我们这时可以将register方法放到worker线程的注册方法中执行,并且唤醒一次selector就可以执行到register了

java 复制代码
package cn.itcast.mytest;

import javafx.concurrent.Worker;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;

import static cn.itcast.nio.c2.ByteBufferUtil.debugAll;

@Slf4j
public class MultiThreadServer {
    public static void main(String[] args) throws IOException {
        //创建一个boss线程,专门用来监听有没有连接请求
        Thread.currentThread().setName("boss");
        //开启服务器
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));
        //创建一个selector
        Selector boss = Selector.open();
        SelectionKey bossKey = ssc.register(boss, SelectionKey.OP_ACCEPT, null);

        //创建多个worker线程(固定数量的),专门用来处理读写事件
        Worker[] workers = new Worker[2];
        for (int i = 0; i < workers.length; i++) {
            workers[i] = new Worker("worker-" + i);
        }

        //不断轮询看是否有新客户接入
        while(true){
            boss.select();
            //有新连接接入
            Iterator<SelectionKey> iterator = boss.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                //删除任务
                iterator.remove();
                if(key.isAcceptable()){
                    //获取连接
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    //将连接交给worker线程处理
                    int index = (int) (sc.hashCode() % workers.length);
                    log.debug("分配给worker-{}", index);
                    workers[index].register(sc);//这个方法是在boss线程中被调用的,所以register方法中的内容实际还是由boss线程执行
                    log.debug("connected...{}", sc.getRemoteAddress());
                }
            }
        }
    }

    static class Worker implements Runnable{
        private Thread thread;
        private Selector worker;
        private String name;
        private boolean start = false;//保证我们一个worker线程只创建一次线程,避免创建太多线程
        private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();

        public Worker(String name) {
            this.name = name;
        }

        public void register(SocketChannel sc) throws IOException {
            if (!start) {
                this.thread = new Thread(this, name);
                this.worker = Selector.open();
                thread.start();
                start = true;
            }
            //将channel注册到worker的selector上,为了这条语句不被select()阻塞我们需要他在select()方法前执行,所以最好是在同一个线程被执行这样我们容易控制先后关系
            //通过线程安全的队列来实现,向队列中添加任务但先不执行
            queue.add(() -> {
                try {
                    sc.register(worker, SelectionKey.OP_READ, null);
                } catch (ClosedChannelException e) {
                    throw new RuntimeException(e);
                }
            });
            //因为在thread.start();后已经执行了worker.select();方法所以我们需要主动唤醒select来执行register
            worker.wakeup();
        }

        @Override
        public void run() {
            //worker的职责就是专门关注读写事件
            while(true) {
                try {
                    //监听
                    worker.select();
                    Runnable task = queue.poll();
                    if(task != null){
                        task.run();//执行注册任务
                    }
                    //获取事件集
                    Iterator<SelectionKey> iterator = worker.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey workerKey = iterator.next();
                        iterator.remove();
                        if (workerKey.isReadable()) {
                            //读取数据
                            try {
                                ByteBuffer buffer = ByteBuffer.allocate(16);
                                SocketChannel sc = (SocketChannel) workerKey.channel();
                                log.debug("read...{}", sc.getRemoteAddress());
                                int read = sc.read(buffer);
                                if (read == -1) {//客户端断开连接
                                    workerKey.cancel();
                                } else {
                                    buffer.flip();
                                    debugAll(buffer);
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

        }
    }
}
相关推荐
流㶡11 小时前
网络爬虫之requests.get() 之爬取网页内容
python·数据爬虫
路由侠内网穿透.11 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
yuankoudaodaokou11 小时前
高校科研新利器:思看科技三维扫描仪助力精密研究
人工智能·python·科技
树℡独11 小时前
ns-3仿真之应用层(三)
运维·服务器·ns3
一条大祥脚12 小时前
ABC357 基环树dp|懒标记线段树
数据结构·算法·图论
VekiSon12 小时前
Linux内核驱动——基础概念与开发环境搭建
linux·运维·服务器·c语言·arm开发
言無咎12 小时前
从规则引擎到任务规划:AI Agent 重构跨境财税复杂账务处理体系
大数据·人工智能·python·重构
苦藤新鸡12 小时前
50.腐烂的橘子
数据结构·算法
无限进步_12 小时前
面试题 02.02. 返回倒数第 k 个节点 - 题解与详细分析
c语言·开发语言·数据结构·git·链表·github·visual studio
张小凡vip12 小时前
数据挖掘(十)---python操作Spark常用命令
python·数据挖掘·spark