Reactor 模式的 JAVA NIO 多线程服务器

「NIO系列」——之Reactor模型 通过之前的Unix的IO模型介绍,想必也了解到了5种IO模型。java的NIO是属于同步非阻塞IO,关于IO多路复用,java没有相应的IO模型,但有相应的编程模式,Reactor就是基于NIO中实现多路复用的一种模式。本文将从以下几点阐述Reactor模式reactor是什么为何要用,能解决什么问题如何用,更好的方式其他事件处理模式。... 阅读详情

JAVA NIO 多线程服务器 1.2版
Reactor 模式的 JAVA NIO 多线程服务器

从JDK 1.4开始,Java的标准库中就包含了NIO,即所谓的“New IO”。其中最重要的功能就是提供了“非阻塞”的IO,当然包括了Socket。NonBlocking的IO就是对select(Unix平台下)以及 WaitForMultipleObjects(Windows平台)的封装,提供了高性能、易伸缩的服务架构。

说来惭愧,直到JDK1.4才有这种功能,但迟到者不一定没有螃蟹吃,NIO就提供了优秀的面向对象的解决方案,可以很方便地编写高性能的服务器。

话说回来,传统的Server/Client实现是基于Thread per request,即服务器为每个客户端请求建立一个线程处理,单独负责处理一个客户的请求。比如像Tomcat(新版本也会提供NIO方案)、Resin等Web服务器就是这样实现的。当然为了减少瞬间峰值问题,服务器一般都使用线程池,规定了同时并发的最大数量,避免了线程的无限增长。

但这样有一个问题:如果线程池的大小为100,当有100个用户同时通过HTTP现在一个大文件时,服务器的线程池会用完,因为所有的线程都在传输大文件了,即使第101个请求者仅仅请求一个只有10字节的页面,服务器也无法响应了,只有等到线程池中有空闲的线程出现。

另外,线程的开销也是很大的,特别是达到了一个临界值后,性能会显著下降,这也限制了传统的Socket方案无法应对并发量大的场合,而“非阻塞”的IO就能轻松解决这个问题。

下面只是一个简单的例子:服务器提供了下载大型文件的功能,客户端连接上服务器的12345端口后,就可以读取服务器发送的文件内容信息了。注意这里的服务器只有一个主线程,没有其他任何派生线程,让我们看看NIO是如何用一个线程处理N个请求的。

NIO服务器最核心的一点就是反应器模式:当有感兴趣的事件发生的,就通知对应的事件处理器去处理这个事件,如果没有,则不处理。所以使用一个线程做轮询就可以了。当然这里这是个例子,如果要获得更高性能,可以使用少量的线程,一个负责接收请求,其他的负责处理请求,特别是对于多CPU时效率会更高。

关于使用NIO过程中出现的问题,最为普遍的就是为什么没有请求时CPU的占用率为100%?出现这种问题的主要原因是注册了不感兴趣的事件,比如如果没有数据要发到客户端,而又注册了写事件(OP_WRITE),则在 Selector.select()上就会始终有事件出现,CPU就一直处理了,而此时select()应该是阻塞的。

线程模型

NIO 的选择器采用了多路复用(Multiplexing)技术,可在一个选择器上处理多个套接字,通过获取读写通道来进行 IO 操作。由于网络带宽等原因,在通道的读、写操作中是容易出现等待的,所以在读、写操作中引入多线程,对性能提高明显,而且可以提高客户端的感知服务质量。所以本文的模型将主要通过使用读、写线程池来提高与客户端的数据交换能力。

如下图所示,服务端接受客户端请求后,控制线程将该请求的读通道交给读线程池,由读线程池分配线程完成对客户端数据的读取操作;当读线程完成读操作后,将数据返回控制线程,进行服务端的业务处理;完成业务处理后,将需回应给客户端的数据和写通道提交给写线程池,由写线程完成向客户端发送回应数据的操作。


(NIO 多线程服务器模型)

同时整个服务端的流程处理,建立于事件机制上。在 [接受连接->读->业务处理->写 ->关闭连接 ]这个过程中,触发器将触发相应事件,由事件处理器对相应事件分别响应,完成服务器端的业务处理。
下面我们就来详细看一下这个模型的各个组成部分。

 

 

public class MiniServer extends Thread
{
    private static final Log log = LogFactory.getLog(MiniServer.class);
   
    private final Selector s;
    private final ServerSocketChannel ssc;
    private ExecutorService executor;
   
    public MiniServer(int portnumber,ExecutorService executor) throws IOException
    {
        this.executor=executor;
        s = Selector.open();
        ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(portnumber));
        ssc.configureBlocking(false);
        ssc.register(s,SelectionKey.OP_ACCEPT);
    }
   
    public void run()
    {
        try
        {
            while(s.isOpen())
            {
                int nKeys=s.select();
                if(nKeys>0)
                {
                    Iterator<SelectionKey> it = s.selectedKeys().iterator();
                    while (it.hasNext())
                    {
                        SelectionKey key = it.next();
                        it.remove();
                        if (!key.isValid() || !key.channel().isOpen())
                            continue;
                        if(key.isAcceptable())
                        {
                            SocketChannel sc = ssc.accept();
                            if (sc != null)
                            {
                                sc.configureBlocking(false);
                                sc.register(s, SelectionKey.OP_READ, new Reader(executor));
                            }
                        }
                        else if(key.isReadable()||key.isWritable())
                        {
                            Reactor reactor = (Reactor) key.attachment();
                            reactor.execute(key);
                        }
                    }
                }
            }
        }
        catch(IOException e)
        {
            log.info(e);
        }
    }
}


public interface Reactor
{
    void execute(SelectionKey key);
}


public class Reader implements Reactor
{
    private static final Log log = LogFactory.getLog(Reader.class);
   
    private byte[] bytes=new byte[0];
    private ExecutorService executor;
   
    public Reader(ExecutorService executor)
    {
        this.executor=executor;
    }
   
    @Override
    public void execute(SelectionKey key)
    {
        SocketChannel sc = (SocketChannel) key.channel();
        try
        {
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            int len=-1;
            while(sc.isConnected() && (len=sc.read(buffer))>0)
            {
                buffer.flip();
                  byte [] content = new byte[buffer.limit()];
                buffer.get(content);
                bytes=NutUtil.ArrayCoalition(bytes,content);
                buffer.clear();
            }
            if(len==0)
            {
                key.interestOps(SelectionKey.OP_READ);
                key.selector().wakeup();
            }
            else if(len==-1)
            {
                Callable<byte[]> call=new ProcessCallable(bytes);
                Future<byte[]> task=executor.submit(call);
                ByteBuffer output=ByteBuffer.wrap(task.get());
                sc.register(key.selector(), SelectionKey.OP_WRITE, new Writer(output));
            }
        }
        catch(Exception e)
        {
            log.info(e);
        }
    }
}


public class Writer implements Reactor
{
    private static final Log log = LogFactory.getLog(Writer.class);
   
    private ByteBuffer output;
   
    public Writer(ByteBuffer output)
    {
        this.output=output;
    }
   
    public void execute(SelectionKey key)
    {
        SocketChannel sc = (SocketChannel) key.channel();
        try
        {
            while(sc.isConnected() && output.hasRemaining())
            {
                int len=sc.write(output);
                if(len<0)
                {
                    throw new EOFException();
                }
                if(len==0)
                {
                    key.interestOps(SelectionKey.OP_WRITE);
                    key.selector().wakeup();
                    break;
                }
            }
            if(!output.hasRemaining())
            {
                output.clear();
                key.cancel();
                sc.close();
            }
        }
        catch(IOException e)
        {
            log.info(e);
        }
    }
}

 

SRAM:SRAM入门指南与工作原理 SRAM(Static Random Access Memory),即静态随机存取存储器,是一种能够读取和写入数据的内存(RAM)。它使用由晶体管组成的触发器来存储数据,只要有电源供应,数据就可以稳定地保存。与动态随机存取存储器(DRAM)不同,SRAM 不需要周期性地刷新来保持数据。 阅读详情

相关推荐

攻克TypeError: Cannot read properties of undefined (reading ‘NormalModule‘)的四种实战策略

本文详细解析了`TypeError: Cannot read properties of undefined (reading 'NormalModule')`错误的四种实战解决策略。从版本一致性检查、包管理器优化到依赖替换和构建环境配置,帮助开发者快速定位和解决Webpack构建过程中的常见问题,提升开发效率。

weixin_42523670的博客 243

Java NIO服务端多线程化问题

工作中有这么一个项目,电动车充电桩使用socket与服务器通信,项目于今年一月份上线,一直平稳运行,近期由于充电桩数量激增,socket服务端压力倍增,导致服务端开放的端口连接不上。分析代码,发现服务端使用的单线程模式。

qq_20157013的博客 1808

从零到一:微信小程序Camera组件实现高效连续扫码的实战解析

本文详细解析了如何利用微信小程序Camera组件实现高效连续扫码功能,适用于零售仓储、物流配送等场景。通过优化扫码核心机制、防抖处理和图像增强技术,开发者可以突破官方API限制,打造媲美专业扫码枪的流畅体验。文章还提供了性能基准测试和高级功能扩展方案,助力企业提升操作效率。

weixin_29015051的博客 579

Java基础之《netty(10)—Reactor三种模式》

(3)如果是建立连接请求事件,则由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的后续业务处理。服务器端用一个线程通过多路复用搞定所有的IO操作(包括连接、读、写等),编码简单,清晰明了,但是如果客户端连接数量较多,将无法支撑。(1)Select是前面I/O复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求。(4)如果不是建立连接事件,则Reactor会分发调用连接对应的Handler来响应。在处理高并发的时候,存在瓶颈。

csj50的专栏 1084

VS2022下C# WPF项目一键打包成独立exe的完整指南

本文提供了一份在Visual Studio 2022中将C# WPF项目一键打包成独立可执行exe文件的完整指南。通过配置项目属性、启用单文件发布和自包含模式,开发者无需依赖第三方工具,即可生成可在未安装.NET运行时的电脑上直接运行的独立程序,极大简化了桌面应用的分发流程。

weixin_28831399的博客 672

CVAT自动标注避坑指南:从nuctl安装到YOLOv5模型部署的完整流程

本文详细介绍了在CVAT中部署YOLOv5自动标注功能的完整流程与避坑指南。重点解决了从nuctl工具安装、项目创建时因网络问题导致的镜像拉取失败,到最终模型函数部署与CVAT集成的关键步骤。针对国内网络环境,提供了实用的镜像替换方案,帮助用户顺利完成基于CPU的自动化标注环境搭建。

pytorch8learner的博客 229

Java I/O中的Reactor模式

单Reactor单线程,酒店的门童,服务员是同一个人,全程为客户服务单Reactor多线程,1个门童,多个服务员,门童之负责接待,转给服务员主从Reactor多线程,多个门童,多个服务员。

0102的博客 1164

保姆级教程:在Windows 11上用VSCode和Conda搞定Depth-Anything-3(含常见报错修复)

本文提供在Windows 11系统上使用VSCode和Conda配置Depth-Anything-3的完整教程,涵盖环境搭建、依赖安装、常见报错修复及深度图生成实战。特别针对CUDA与PyTorch版本匹配、Hugging Face镜像加速等关键问题提供解决方案,帮助开发者高效实现计算机视觉中的深度估计应用。

weixin_30338481的博客 347

Tomcat源码解析系列三:Tomcat的Reactor机制-http请求连接处理机制

Tomcat获取连接的时候使用阻塞获取连接的方式,没有使用reactor模型。防止由于selector.select(selectorTimeout)导致的cpu空转,避免了cpu空转的场景。Tomcat处理长连接超时以及获取read事件的时候使用了reactor模式,非阻塞获取可读的socket,提高请求处理的能力。

Running666的博客 781

DaVinci Developer和DaVinci Configurator:AutoSAR开发与配置工具

a. 组件创建:DaVinci Developer允许开发人员创建不同类型的AutoSAR软件组件,如应用软件组件(Application Software Component,SWC)、基础软件组件(Basic Software Component,BSWC)等。除了以上功能,DaVinci Developer和DaVinci Configurator还提供了丰富的文档和代码生成功能,以支持开发人员和配置人员进行文档编写、代码生成和文档浏览等任务。用户可以根据具体需求对软件组件进行灵活的配置。

HmoaAmp的博客 2262

【Bug已解决】NCCL timeout for validation 解决方案

训练用多卡 DDP / FSDP 正常,但一到或者进程直接 hang 在 eval 的某个集合通信上。最迷惑的是:训练阶段(有集合通信)完全正常,偏偏 eval 阶段(看起来更轻量)超时。因为 eval 阶段某些 rank 没参与 collective,导致参与的那部分 rank 永远等不到同伴。

一键难忘的博客 142

【Netty】从0到1(五):NIO-多线程优化

这前几篇文章中,都是采用单线程进行设计,虽然可以运行,但是没有充分利用 CPU 的性能,并且如果有一个事件的处理时间较长,则会影响其他事件的处理。例如,开发一个项目,如果团队只有一个全栈工程师,那么他需要先完成前端,再完成后端,只能按部就班的完成任务,如果前端开发遭遇困难,花费了很多时间,则会大大拉长项目开发周期,而如果一个团队里有前端工程师和后端工程师,则前后端的开发能同步进行,这样会大大提高开发效率。

z1ztai的博客 387

包络检波电路(Envelope Detector)

把 100 kHz 载波想象成"电台的高频信号",把 1 kHz 包络想象成"主持人说话的声波"。而今天电路的工作,就是把这个"调大调小的外壳"(包络)重新剥出来,还原成主持人的声音 🎵。这是三极管开关电路的重要搭档,也是从"二极管家族"过渡到"三极管家族"的桥梁 🔌。——倍压整流看的是"幅值累积"(多个电容接力),包络检波看的是"频率分离"(高低频分开)。同样的"D1 串联 + RC 并联"拓扑,今天被赋予了"AM 解调"的新生命。——电感负载的"泄洪闸",保护三极管开关不被反向电动势击穿。

飞多学堂的博客 96

Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式

本文介绍了Java中的四种I/O模型,同步阻塞,同步非阻塞,多路复用,异步阻塞。同时将NIO和BIO进行了对比,并详细分析了基于NIO的Reactor模式,包括经典单线程模型以及多线程模式和多Reactor模式。 原创文章,转载请务必将下面这段话置于文章开头处(保留超链接)。 本文转发自技术世界,原文链接 http://www.jasongj.com/java/nio_reactor/ J...

GarfieldEr007的专栏 749

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

《netty实战》读书笔记一---selector、reactor模型、NIO与零拷贝

首先先自己熟悉一下selector、epoll、NIO编程、reactor模型 I/O 模型基本说明 I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能 Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进...

阿拉斯加大闸蟹的博客 3891

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

java reactor nio_Java NIO Reactor模式

一、NIO介绍:NIO模型:1、Channel为连接通道,相当于一个客户端与服务器的一个连接,Selector为通道管理器,将Channel注册到Selector上,Selector管理着这些Channel,当管理的某个通道有事件到达时,Selector将会通知应用程序去处理;Selector工作的线程和处理网络事件的应用是在不同的线程中;2、NIO 有一个主要的类Selector,这个类似一个观...

weixin_29300805的博客 336

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Reactor详解

在开篇之前,我们对JavaNIO 的使用方式不做过多介绍,这种API的介绍方式网上太多了,没必要详细介绍,我们假设NIO的使用方式,你能够熟练运用。这是NIO系列第三篇: 【NIO系列】——之TCP探秘 【NIO系列】——之IO模型 通过之前的Unix的IO模型介绍,想必也了解到了5种IO模型。java的NIO是属于同步非阻塞IO,关于IO多路复用,java没有相应的IO模型,但有相应的编程模式,Reactor 就是基于NIO中实现多路复用的一种模式。本文将从以下几点阐述Reactor模式: .

好习惯成就伟大 737

java nio proactor_reactor模式与java nio

Reactor是由Schmidt, Douglas C提出的一种模式,在高并发服务器实现中广泛采用。改模式采用事件驱动方式,当事件出现时,后调用相应的事件处理代码(Event Handler)。这个模式是高并发服务器的基础,如nginx和lighttpd。这两种对大并发,但每个请求处理都很快的场景非常适合。通常的web访问就是这个特点。结构包括了5个部分,其中handle和Synchronou...

weixin_30162121的博客 161

AVA NIO 多线程服务器 1.2版

reactor 模式的 java nio 多线程服务器 public class miniserver extends thread{ private static final log log = logfactory.getlog(miniserver.class); private final selector s; private final serversocke...

技术总结 82

Java NIO线程模型分析--单Reactor单线程

涉及到Buffer, ServerSocketChannel, SocketChannel, Selector,SelectionKey等类的使用。 ServerSocketChannel API: SocketChannel API: Client端代码: package com.bruce.javanio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteB...

wdquan19851029的专栏 857

单线程 Reactor 模式

单线程 Reactor 是Reactor 模式最简单的版本,即 Reactor 和所有 Handler 都运行在同一个线程中。

2301_79609207的博客 1206

还愿之Netty-Reactor模式

还愿之Netty-Reactor模式一. 什么是reactor模式1. 多线程时io的致命缺陷2. reactor模式简介二. 单线程的Reactor模型三. 多线程的Reactor模型四. 主从的Reactor模型 一. 什么是reactor模式 1. 多线程时io的致命缺陷 如果以bio的思想去设计一个服务器,用一个while循环,不断的去监听端口是否有套接字的连接。如果有连接建立,就进行业务处理,代码如下所示。 while(true) { Socket socket = ServerSocket

Anbal_Zheng的博客 353

BIO到NIO

同步阻塞IO 单线程BIO 最原始的网络编程思路就是服务器用一个while循环,不断监听端口是否有新的套接字连接,如果有,那么就调用一个处理函数处理。 while(true){ socket = accept(); handle(socket) } 这种方法的最大问题是无法并发,效率太低。如果当前的请求没有处理完,那么后面的请求只能被阻塞,服务器的吞吐量太低。 多线程BIO 针对上面的问题,很自然想到了使用多线程处理IO,也就是很经典的connection per thread,每一个连接

weixin_37850264的博客 165

什么是Netty、Reactor线程模型和nio

Netty是提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty是一个基于NIO的客户、服务器端的编程框架。 例如:dubbo的底层通信是利用netty来实现的。 Reactor是一种线程模式,Reactor是什么? The reactor design_pattern is anevent_handlingpattern for handling service requests delivered concurrently t...

当我谈编程时我谈些什么 314

请收藏好:Doug Lea写的理解 Java NIO 与 Reactor 模式最权威的资料

但每个步骤的性质和开销各不相同,例如:XML 解析、文件传输、Web 页面生成、计算服务等。示例:数据库和事务监控器、多人游戏、聊天等。但分派速度可能较慢,而且通常。,主要适用于多处理器环境。

m0_50180963的博客 374
上一篇: LUN---Logical Unit Number
下一篇: Linux 中的零拷贝技术
Ice008
博客等级 码龄19年 34粉丝 36原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值