socket服务器多线程优化

server/2024/12/18 17:38:50/

在单线程环境下一个线程虽然可以执行任务但是所有的任务都交给一个线程来做当任务积累起来时,前面的任务会影响后续任务的执行,并且现在都是多核处理器我们需要尽可能利用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了

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));//创建一个selectorSelector 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来执行registerworker.wakeup();}@Overridepublic 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);}}}}
}


http://www.ppmy.cn/server/151225.html

相关文章

IP数据云查询IP归属地信息

互联网时代&#xff0c;我们每天都会面对大量的网站或App,但你们是否知晓&#xff0c;所有程序员进行程序或者系统的开发都离不开查询IP地址&#xff0c;这是由于对于每个安全的网站/软件来说&#xff0c;基础的服务日志&#xff0c;登录IP等就离不开IP归属地离线库&#xff0c…

如何在服务器上安装 Maven

1. 安装Java Development Kit (JDK) 由于Maven依赖于Java运行环境&#xff0c;因此首先需要确保系统中已经安装了合适的JDK版本。 通过以下命令检查Java版本&#xff0c; java -version如果未安装JDK可以参考如何在服务器上安装 Java OpenJDK相关文档来安装特定版本的JDK。 …

UE5.1_常用路径函数及指向(未完成)

UE5常用路径函数及指向到底是掌握了吗?为什么每次使用每次得查找呢?怎么才能牢牢地记住? 记住源于实实在在掌握,先跟着看,或许可以看到与以往不同的点。 FPaths | Unreal Engine Documentationhttps://docs.unrealengine.com/5.1/en-US/API/Runtime/Core/Misc/FPaths/ …

Element plus 下拉框组件选中一个选项后显示的是 value 而不是 label

最近刚进行 Vue3 Element plus 项目实践&#xff0c;在进行表单二次封装的时候&#xff0c;表单元素 select 下拉框组件选中一个选项后显示的是 value 而不是 label&#xff0c;下面上代码&#xff1a; 原来的写法&#xff1a; <el-selectv-if"v.type select"…

Python实现循环卷积算法

Python实现循环卷积算法 1 基本原理2 程序实现3 运行结果 本文介绍循环卷积算法的基本原理&#xff0c;并使用Python实现循环卷积算法。 1 基本原理 循环卷积的定义式是 y ( k ) ∑ n 0 N − 1 x ( n ) h ( k − n ) ; k 1 , 2... , N − 1 y(k) \sum_{n0}^{N-1}x(n)h(k-…

解决小程序中ios可以正常滚动,而Android失效问题

解决小程序中 iOS 可以正常滚动&#xff0c;而 Android 失效问题 在开发小程序时&#xff0c;我们经常会遇到一些平台兼容性问题。最近&#xff0c;我在开发一个小程序时遇到了一个问题&#xff1a;在 iOS 设备上可以正常滚动加载更多数据&#xff0c;而在 Android 设备上却无…

NVR小程序接入平台/设备EasyNVR深度解析H.265与H.264编码视频接入的区别

随着科技的飞速发展和社会的不断进步&#xff0c;视频压缩编码技术已经成为视频传输和存储中不可或缺的一部分。在众多编码标准中&#xff0c;H.265和H.264是最为重要的两种。今天我们来将深入分析H.265与H.264编码的区别。 一、H.265与H.264编码的区别 1、比特率与分辨率 H.…

计算机网络技术基础:1.计算机网络的产生与发展

从1946年世界上第一台计算机ENIAC的诞生&#xff0c;计算机网络的发展大体可分为以下4个阶段。 一、第一代计算机网络——面向终端的计算机网络 第一代计算机网络也称面向终端的计算机网络&#xff0c;它是以主机为中心的通信系统。这样的系统中&#xff0c;除一台中心计算机&…