Java网络编程(七):NIO实战构建高性能Socket服务器 - 实践

news/2025/9/27 14:02:58/文章来源:https://www.cnblogs.com/lxjshuju/p/19115062

1. 单线程NIO服务器架构设计

1.1 架构设计原则

单线程NIO服务器的核心思想是使用一个线程通过Selector监控多个通道的I/O事件,实现高并发处理。这种架构具有以下优势:

  1. 资源效率:避免了传统多线程模型中线程创建和上下文切换的开销
  2. 内存占用低:单线程模型显著减少了内存消耗
  3. 无锁设计:避免了多线程同步的复杂性
  4. 可预测性能:性能表现更加稳定和可预测

1.2 核心组件设计

单线程NIO服务器的架构包含以下核心组件:

组件职责实现要点
Selector监控多个通道的I/O事件使用epoll等高效机制
ServerSocketChannel监听客户端连接请求配置为非阻塞模式
SocketChannel处理客户端数据传输管理读写缓冲区
ByteBuffer数据缓冲区管理合理分配直接/堆缓冲区
事件处理器处理具体的I/O事件实现业务逻辑分离

1.3 服务器架构实现

以下是一个完整的单线程NIO服务器架构实现:

public class NioSocketServer {
private static final int DEFAULT_PORT = 8080;
private static final int BUFFER_SIZE = 1024;
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean running = false;
// 连接管理
private final Map<SocketChannel, ClientConnection> connections = new ConcurrentHashMap<>();public void start(int port) throws IOException {// 初始化Selectorselector = Selector.open();// 创建ServerSocketChannelserverChannel = ServerSocketChannel.open();serverChannel.configureBlocking(false);serverChannel.bind(new InetSocketAddress(port));// 注册接受连接事件serverChannel.register(selector, SelectionKey.OP_ACCEPT);running = true;System.out.println("物联网平台NIO服务器启动,监听端口: " + port);// 启动事件循环eventLoop();}private void eventLoop() {while (running) {try {// 阻塞等待事件,超时时间1秒int readyChannels = selector.select(1000);if (readyChannels == 0) {// 处理超时逻辑,如心跳检测handleTimeout();continue;}// 处理就绪事件Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (!key.isValid()) {continue;}try {if (key.isAcceptable()) {handleAccept(key);} else if (key.isReadable()) {handleRead(key);} else if (key.isWritable()) {handleWrite(key);}} catch (IOException e) {handleException(key, e);}}} catch (IOException e) {System.err.println("事件循环异常: " + e.getMessage());break;}}}public void stop() {running = false;if (selector != null) {selector.wakeup();}}}

1.4 性能优化策略

在物联网平台的实际应用中,可以采用以下优化策略:

  1. 缓冲区池化:重用ByteBuffer对象,减少GC压力
  2. 直接内存使用:对于大数据传输使用DirectByteBuffer
  3. 批量处理:将多个小的写操作合并为批量操作
  4. 零拷贝技术:使用FileChannel.transferTo()等零拷贝API

2. 事件驱动编程模型实现

2.1 事件驱动模型概述

事件驱动编程模型是NIO服务器的核心,它将传统的阻塞式编程转换为基于事件的响应式编程。在这种模型中,程序的执行流程由外部事件驱动,而不是按照预定的顺序执行。

2.2 事件类型定义

在物联网平台的NIO服务器中,主要处理以下事件类型:

事件类型触发条件处理策略
ACCEPT新客户端连接请求接受连接并注册读事件
READ通道有数据可读读取数据并解析协议
WRITE通道可写入数据发送缓冲区中的数据
CONNECT客户端连接完成注册读写事件

2.3 事件处理器实现

public class EventHandler {
private final ProtocolDecoder decoder = new ProtocolDecoder();
private final ProtocolEncoder encoder = new ProtocolEncoder();
public void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
if (clientChannel != null) {
// 配置为非阻塞模式
clientChannel.configureBlocking(false);
// 创建客户端连接对象
ClientConnection connection = new ClientConnection(clientChannel);
// 注册读事件
SelectionKey clientKey = clientChannel.register(key.selector(), SelectionKey.OP_READ);
clientKey.attach(connection);
// 记录连接
connections.put(clientChannel, connection);
System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());
}
}
public void handleRead(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ClientConnection connection = (ClientConnection) key.attachment();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int bytesRead = channel.read(buffer);
if (bytesRead == -1) {
// 客户端关闭连接
closeConnection(key);
return;
}
if (bytesRead > 0) {
buffer.flip();
// 将数据添加到连接的接收缓冲区
connection.appendReceiveBuffer(buffer);
// 尝试解析完整消息
List<Message> messages = decoder.decode(connection.getReceiveBuffer());for (Message message : messages) {// 处理业务逻辑processMessage(connection, message);}}}public void handleWrite(SelectionKey key) throws IOException {SocketChannel channel = (SocketChannel) key.channel();ClientConnection connection = (ClientConnection) key.attachment();ByteBuffer sendBuffer = connection.getSendBuffer();if (sendBuffer.hasRemaining()) {int bytesWritten = channel.write(sendBuffer);if (bytesWritten > 0) {connection.updateLastActiveTime();}}// 如果发送缓冲区已空,取消写事件关注if (!sendBuffer.hasRemaining()) {key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);}}private void processMessage(ClientConnection connection, Message message) {// 根据消息类型处理业务逻辑switch (message.getType()) {case DEVICE_DATA:handleDeviceData(connection, message);break;case HEARTBEAT:handleHeartbeat(connection, message);break;case COMMAND:handleCommand(connection, message);break;default:System.err.println("未知消息类型: " + message.getType());}}}

2.4 异步响应机制

事件驱动模型的一个重要特点是异步响应。当需要向客户端发送数据时,不是立即写入,而是将数据放入发送缓冲区,并注册写事件:

public void sendMessage(ClientConnection connection, Message message) {
try {
ByteBuffer encodedData = encoder.encode(message);
connection.appendSendBuffer(encodedData);
// 注册写事件
SelectionKey key = connection.getChannel().keyFor(selector);
if (key != null && key.isValid()) {
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
selector.wakeup(); // 唤醒selector
}
} catch (Exception e) {
System.err.println("发送消息失败: " + e.getMessage());
}
}

3. 粘包拆包问题的处理方案

3.1 粘包拆包问题分析

在TCP通信中,由于TCP是面向流的协议,发送方发送的多个数据包可能被接收方作为一个数据包接收(粘包),或者一个数据包可能被分成多个数据包接收(拆包)。这在物联网平台的设备通信中是一个常见问题。

粘包现象

拆包现象

3.2 解决方案设计

常用的解决方案包括:

方案原理优点缺点
固定长度每个消息固定字节数实现简单浪费带宽
分隔符使用特殊字符分隔消息实现简单需要转义处理
长度前缀消息头包含消息长度效率高,无歧义实现稍复杂
自定义协议复杂的协议头结构功能强大实现复杂

3.3 长度前缀方案实现

在物联网平台中,推荐使用长度前缀方案,协议格式如下:

+--------+--------+--------+--------+--------+...+--------+
| Length |  Type  |  Flag  | SeqId  |      Data...      |
+--------+--------+--------+--------+--------+...+--------+
|   4    |   2    |   1    |   4    |      Length-11    |
+--------+--------+--------+--------+--------+...+--------+

协议解码器实现:

public class ProtocolDecoder {
private static final int HEADER_LENGTH = 11; // 4+2+1+4
private static final int MAX_MESSAGE_LENGTH = 1024 * 1024; // 1MB
public List<Message> decode(ByteBuffer buffer) {List<Message> messages = new ArrayList<>();while (buffer.remaining() >= HEADER_LENGTH) {// 标记当前位置buffer.mark();// 读取消息长度int messageLength = buffer.getInt();// 验证消息长度if (messageLength < HEADER_LENGTH || messageLength > MAX_MESSAGE_LENGTH) {throw new IllegalArgumentException("无效的消息长度: " + messageLength);}// 检查是否有完整消息if (buffer.remaining() < messageLength - 4) {// 消息不完整,重置位置buffer.reset();break;}// 读取消息头short messageType = buffer.getShort();byte flag = buffer.get();int sequenceId = buffer.getInt();// 读取消息体int dataLength = messageLength - HEADER_LENGTH;byte[] data = new byte[dataLength];buffer.get(data);// 创建消息对象Message message = new Message(messageType, flag, sequenceId, data);messages.add(message);}// 压缩缓冲区,移除已处理的数据buffer.compact();return messages;}}

3.4 缓冲区管理策略

为了高效处理粘包拆包问题,需要为每个连接维护接收缓冲区:

public class ClientConnection {
private final SocketChannel channel;
private final ByteBuffer receiveBuffer;
private final ByteBuffer sendBuffer;
private long lastActiveTime;
public ClientConnection(SocketChannel channel) {
this.channel = channel;
this.receiveBuffer = ByteBuffer.allocate(8192); // 8KB接收缓冲区
this.sendBuffer = ByteBuffer.allocate(8192);   // 8KB发送缓冲区
this.lastActiveTime = System.currentTimeMillis();
}
public void appendReceiveBuffer(ByteBuffer data) {
// 确保缓冲区有足够空间
if (receiveBuffer.remaining() < data.remaining()) {
// 扩展缓冲区或压缩现有数据
expandOrCompactBuffer();
}
receiveBuffer.put(data);
updateLastActiveTime();
}
private void expandOrCompactBuffer() {
receiveBuffer.flip();
if (receiveBuffer.remaining() > 0) {
// 如果还有未处理的数据,创建更大的缓冲区
ByteBuffer newBuffer = ByteBuffer.allocate(receiveBuffer.capacity() * 2);
newBuffer.put(receiveBuffer);
this.receiveBuffer = newBuffer;
} else {
// 如果没有未处理的数据,直接清空
receiveBuffer.clear();
}
}
public ByteBuffer getReceiveBuffer() {
receiveBuffer.flip();
return receiveBuffer;
}
}

3.5 协议编码器实现

public class ProtocolEncoder {
public ByteBuffer encode(Message message) {
byte[] data = message.getData();
int totalLength = 11 + data.length; // 头部11字节 + 数据长度
ByteBuffer buffer = ByteBuffer.allocate(totalLength);
// 写入消息长度
buffer.putInt(totalLength);
// 写入消息类型
buffer.putShort(message.getType());
// 写入标志位
buffer.put(message.getFlag());
// 写入序列号
buffer.putInt(message.getSequenceId());
// 写入数据
buffer.put(data);
buffer.flip();
return buffer;
}
}

4. 异常处理和连接管理策略

4.1 异常分类和处理策略

在NIO服务器中,异常处理是保证系统稳定性的关键。异常可以分为以下几类:

异常类型处理策略影响范围
网络异常关闭连接,清理资源单个连接
协议异常记录日志,发送错误响应单个连接
系统异常记录日志,继续运行整个服务器
资源异常释放资源,降级服务整个服务器

4.2 异常处理实现

public class ExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandler.class);
public void handleException(SelectionKey key, Exception e) {
SocketChannel channel = (SocketChannel) key.channel();
ClientConnection connection = (ClientConnection) key.attachment();
if (e instanceof IOException) {
// 网络I/O异常,通常表示连接断开
handleNetworkException(key, connection, (IOException) e);
} else if (e instanceof ProtocolException) {
// 协议解析异常
handleProtocolException(key, connection, (ProtocolException) e);
} else {
// 其他未知异常
handleUnknownException(key, connection, e);
}
}
private void handleNetworkException(SelectionKey key, ClientConnection connection, IOException e) {
logger.warn("网络异常,关闭连接: {}", connection.getRemoteAddress(), e);
closeConnection(key);
}
private void handleProtocolException(SelectionKey key, ClientConnection connection, ProtocolException e) {
logger.error("协议异常: {}", e.getMessage());
// 发送错误响应
Message errorResponse = createErrorResponse(e.getErrorCode(), e.getMessage());
sendMessage(connection, errorResponse);
// 根据错误严重程度决定是否关闭连接
if (e.isFatal()) {
closeConnection(key);
}
}
private void handleUnknownException(SelectionKey key, ClientConnection connection, Exception e) {
logger.error("未知异常", e);
closeConnection(key);
}
private void closeConnection(SelectionKey key) {
try {
SocketChannel channel = (SocketChannel) key.channel();
ClientConnection connection = (ClientConnection) key.attachment();
// 从连接管理器中移除
connections.remove(channel);
// 取消SelectionKey
key.cancel();
// 关闭通道
channel.close();
logger.info("连接已关闭: {}", connection.getRemoteAddress());
} catch (IOException e) {
logger.warn("关闭连接时发生异常", e);
}
}
}

4.3 连接生命周期管理

在物联网平台中,设备连接的生命周期管理至关重要:

public class ConnectionManager {
private final Map<String, ClientConnection> deviceConnections = new ConcurrentHashMap<>();private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);// 连接超时时间(毫秒)private static final long CONNECTION_TIMEOUT = 300_000; // 5分钟private static final long HEARTBEAT_INTERVAL = 60_000;  // 1分钟public void start() {// 启动心跳检测任务scheduler.scheduleAtFixedRate(this::checkHeartbeat,HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS);// 启动连接清理任务scheduler.scheduleAtFixedRate(this::cleanupTimeoutConnections,CONNECTION_TIMEOUT, CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);}public void registerConnection(String deviceId, ClientConnection connection) {deviceConnections.put(deviceId, connection);connection.setDeviceId(deviceId);logger.info("设备连接注册: {}", deviceId);}public void unregisterConnection(String deviceId) {ClientConnection connection = deviceConnections.remove(deviceId);if (connection != null) {logger.info("设备连接注销: {}", deviceId);}}private void checkHeartbeat() {long currentTime = System.currentTimeMillis();for (ClientConnection connection : deviceConnections.values()) {long lastActiveTime = connection.getLastActiveTime();if (currentTime - lastActiveTime > HEARTBEAT_INTERVAL * 2) {// 发送心跳请求sendHeartbeatRequest(connection);}}}private void cleanupTimeoutConnections() {long currentTime = System.currentTimeMillis();List<String> timeoutDevices = new ArrayList<>();for (Map.Entry<String, ClientConnection> entry : deviceConnections.entrySet()) {ClientConnection connection = entry.getValue();if (currentTime - connection.getLastActiveTime() > CONNECTION_TIMEOUT) {timeoutDevices.add(entry.getKey());}}// 清理超时连接for (String deviceId : timeoutDevices) {ClientConnection connection = deviceConnections.get(deviceId);if (connection != null) {logger.warn("设备连接超时,强制关闭: {}", deviceId);forceCloseConnection(connection);unregisterConnection(deviceId);}}}private void sendHeartbeatRequest(ClientConnection connection) {Message heartbeat = new Message(MessageType.HEARTBEAT_REQUEST, (byte) 0,generateSequenceId(), new byte[0]);sendMessage(connection, heartbeat);}public void shutdown() {scheduler.shutdown();try {if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {scheduler.shutdownNow();}} catch (InterruptedException e) {scheduler.shutdownNow();Thread.currentThread().interrupt();}}}

4.4 资源监控和限流

为了防止资源耗尽,需要实现连接数限制和资源监控:

public class ResourceMonitor {
private static final int MAX_CONNECTIONS = 10000;
private static final long MAX_MEMORY_USAGE = 512 * 1024 * 1024; // 512MB
private final AtomicInteger connectionCount = new AtomicInteger(0);
private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
public boolean canAcceptNewConnection() {
// 检查连接数限制
if (connectionCount.get() >= MAX_CONNECTIONS) {
logger.warn("连接数已达上限: {}", MAX_CONNECTIONS);
return false;
}
// 检查内存使用情况
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
if (heapUsage.getUsed() > MAX_MEMORY_USAGE) {
logger.warn("内存使用已达上限: {}MB", MAX_MEMORY_USAGE / 1024 / 1024);
return false;
}
return true;
}
public void onConnectionAccepted() {
connectionCount.incrementAndGet();
}
public void onConnectionClosed() {
connectionCount.decrementAndGet();
}
public void logResourceUsage() {
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
logger.info("当前连接数: {}, 堆内存使用: {}MB/{MB",
connectionCount.get(),
heapUsage.getUsed() / 1024 / 1024,
heapUsage.getMax() / 1024 / 1024);
}
}

5. 总结

通过本文的详细分析和实现,我们构建了一个完整的高性能NIO Socket服务器。这个服务器具备以下特点:

  1. 高并发处理能力:单线程处理大量并发连接,资源利用率高
  2. 可靠的协议处理:有效解决粘包拆包问题,确保数据完整性
  3. 健壮的异常处理:分类处理各种异常情况,保证系统稳定性
  4. 完善的连接管理:实现连接生命周期管理和资源监控

在物联网平台的实际应用中,这种架构能够高效处理大量设备连接和数据传输,为构建可扩展的物联网系统提供了坚实的技术基础。通过合理的优化和调优,单个NIO服务器实例可以支持数万个并发连接,满足大规模物联网应用的需求。

在下一篇文章中,我们将探讨如何将多个NIO服务器实例组合成集群,实现更大规模的并发处理能力和高可用性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/919553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完整教程:【大模型理论篇】用于时间序列预测的纯解码器基础模型TimesFM-2.5

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做卡盟网站教程wordpress 翻页

输入&#xff1a;df_Grp&#xff0c;类型是pandas.core.groupby.generic.DataFrameGroupBy 我们先来学习一下如何将分组后的其中一个分组给转换成DataFrame类型&#xff1a; tmp dict(list(df_Grp)) tmpname[] tmpname [i for i,j in df_Grp] #下面这行代码其实就转化成Dat…

Unigine整合Myra UI Library全纪录(3):整合与优化

MyraIntegration 当Texture2DManager,MyraRenderer和MyraPlatform都实现了之后,就可以将它们整合起来了。 首先,IMyraPlatform.Renderer返回实现好的MyraRenderer,而IMyraRenderer.TextureManager则返回实现好的Te…

MOS管 SI2302 KX2302 集成上下拉电阻,优化线路电路,降低物料成本

MOS管 SI2302 KX2302 集成上下拉电阻,优化线路电路,降低物料成本20VDS/+10VGSN沟道增强型MOSFET 功能VDS=20伏 RDS(ON)=64.1MQ(MAX.)@VGS=10V,ID=3A RDS(ON)=80.2MQ(MAX.)@VGS=4.5VID=3A EXCELLENT GATE CHARGEXRDS…

企业网站建设预算地方门户网站怎么赚钱

信号&#xff1a;事件 槽&#xff1a;事件处理函数 信号与槽&#xff08;Signal & Slot&#xff09;是 Qt 编程的基础&#xff0c;也是 Qt 的一大创新。因为有了信号与槽的编程机制&#xff0c;在 Qt 中处理界面各个组件的交互操作时变得更加直观和简单。 信号&#xff0…

Tita 项目经营一体化建筑业企业解决方案

一、建筑行业项目经营管理痛点剖析 (一)项目目标与企业战略脱节 建筑企业承接项目众多,各项目目标常孤立制定,与企业长期战略关联不紧密。例如企业战略聚焦绿色建筑领域拓展,某新建住宅项目却未将绿色环保施工指标…

CD78.【C++ Dev】以AVL任务的bug讲讲调试技巧

CD78.【C++ Dev】以AVL任务的bug讲讲调试技巧pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &qu…

网站建设 落地页搭建网站需要的软件下载

1.vim输入中文乱码 在/etc/vimrc最下面输入以下代码 set fileencodingsutf-8,gb2312,gbk,gb18030 set termencodingutf-8 set fileformatsunix set encodingprc 2.vim复制的时候如果有#号&#xff0c;下面的代码开头都会有#号 在编辑模式输入:set paste 再进行粘贴即可 3…

怎样做网站公司做百度推广的业务员电话

kubeadm安装k8s1.25版本集群步骤 环境说明实验环境规划集群搭建规划 初始化安装k8s集群的实验环境安装虚拟机更新yum源和操作系统配置机器主机名配置主机hosts文件&#xff0c;相互之间通过主机名互相访问配置主机之间无密码登录关闭交换分区swap&#xff0c;提升性能修改机器内…

登封网站建设公司设备网站模板

一、类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。但是空类中并不是真的什么都没有&#xff0c;任何类在什么都不写的时候&#xff0c;编译器会自动生成以下 6 个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成…

实用指南:AI 时代的安全防线:国产大模型的数据风险与治理路径

实用指南:AI 时代的安全防线:国产大模型的数据风险与治理路径pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…

写给自己的年终复盘以及未来计划

写完这篇文章,这个系列就告一段落了,忽然间感觉好轻松啊。 依然是理财小白,到底什么时候才能脱离韭菜的队伍?还需要不停的投入精力学习才行,继续努力💪。 2020年的年终复盘 股票篇 年初加入韭菜大军,一路坚持…

最近难得的一点思考

大部分时候,我们是不需要思考的,可是也有那么一两天,你需要思考,如果你不想思考,那么反问自己一些问题,给出问题的答案。大部分情况下,写文章不是一件快乐的事情,但是为什么还要写呢? 这是一个好问题。你怎么…

快递网站模板企业网站建设市场前景

uni.chooseImage的返回值在H5平台和其他平台的返回值有所差异&#xff0c;具体差异看下图 根据图片可以看出要想判断上传的文件类型是不能直接使用type进行判断的&#xff0c;所以我使用截取字符串的形式来判断&#xff0c;当前上传图片的后缀名是否符合所需要求。 要求&#…

石家庄制作网站毕业设计网站开发要做什么

一、HTTPS 是什么 HTTPS 也是⼀个应用层协议&#xff0c;是在 HTTP 协议的基础上引入了⼀个加密层. HTTP 协议内容都是按照文本的方式明文传输的。这就导致在传输过程中出现⼀些被篡改的情况. 在互联网上, 明文传输是比较危险的事情!!! HTTPS 就是在 HTTP 的基础上进行了加密…

陕西省咸阳市建设银行网站北京工厂和商务楼宇稳步复工

题目描述这里有提示&#xff0c;初始页面 进入题目页面如下 很简洁的页面只有一行HELLO WORLD ctrlu查看了源码也没有信息 用burp suite抓包&#xff0c;并发送到重放器 根据提示&#xff08;初始页面&#xff09;修改访问index.php文件 index.php index.php 是一种常见的…

np.random.rand

np.random.rand 是 NumPy 库中一个常用的函数,用于创建包含随机数(浮点数)的数组。np.random.rand 详解 1. 核心功能 np.random.rand 生成的随机数服从**[0.0, 1.0) 范围内的均匀分布(Uniform Distribution)**。均…

Nexpose 8.22.0 for Linux Windows - 漏洞扫描

Nexpose 8.22.0 for Linux & Windows - 漏洞扫描Nexpose 8.22.0 for Linux & Windows - 漏洞扫描 Rapid7 on-prem Vulnerability Management, released Sep 24, 2025 请访问原文链接:https://sysin.org/blog/…

冯延巳-风乍起,吹皱一池春水。

《谒金门风乍起》 冯延巳 风乍起,吹皱一池春水。闲引鸳鸯香径里,手挼红杏蕊。 斗鸭阑干独倚,碧玉搔头斜坠。终日望君君不至,举头闻鹊喜。首先我对冯延巳不是很了解,但是总是觉得大概在哪里听说过。仔细想来,可能…

完整教程:逻辑回归中的决策边界解析与应用实例

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …