当前位置: 首页 > news >正文

基于Netty实现WebSocket服务端

本文基于Netty实现WebSocket服务端,实现和客户端的交互通信,客户端基于JavaScript实现。

在【WebSocket简介-CSDN博客】中,我们知道WebSocket是基于Http协议的升级,而Netty提供了Http和WebSocket Frame的编解码器和Handler,我们可以基于Netty快速实现WebSocket服务端。

一、基于Netty快速实现WebSocket服务端

我们可以直接利用Netty提供了Http和WebSocket Frame的编解码器和Handler,快速启动一个WebSocket服务端。服务端收到Client消息后,转发给其他客户端。

服务端代码:

ChannelSupervise:用户存储客户端信息

import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;/*** 用户存储客户端信息*/
public class ChannelSupervise {private static ChannelGroup GlobalGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);private static ConcurrentMap<String, ChannelId> ChannelMap = new ConcurrentHashMap();public static void addChannel(Channel channel) {GlobalGroup.add(channel);ChannelMap.put(channel.id().asShortText(), channel.id());}public static void removeChannel(Channel channel) {GlobalGroup.remove(channel);ChannelMap.remove(channel.id().asShortText());}public static Channel findChannel(String id) {return GlobalGroup.find(ChannelMap.get(id));}public static void send2All(TextWebSocketFrame tws) {GlobalGroup.writeAndFlush(tws);}
}

服务端Netty配置:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;/*** * 实现长链接 客户端与服务端;*/
public class SimpleWsChatServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).// 在 bossGroup 增加一个日志处理器handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();// 基于http协议的长连接 需要使用http协议的解码 编码器pipeline.addLast(new HttpServerCodec());// 以块的方式处理pipeline.addLast(new ChunkedWriteHandler());/*** http数据传输过程中是分段, HttpObjectAggregator 将多个段聚合起来* 当浏览器发起大量数据的时候,会发起多次http请求*/pipeline.addLast(new HttpObjectAggregator(8192));/*** 对于websocket是以frame的形式传递* WebSocketFrame*  浏览器 ws://localhost:7070/ 不在是http协议*  WebSocketServerProtocolHandler 将http协议升级为ws协议 即保持长链接*/pipeline.addLast(new WebSocketServerProtocolHandler("/helloWs"));// 自定义handler专门处理浏览器请求pipeline.addLast(new MyTextWebSocketFrameHandler());}});ChannelFuture channelFuture = serverBootstrap.bind(7070).sync();channelFuture.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workGroup.shutdownGracefully();}}
}

消息转发逻辑:

import com.huawei.websocket.nio2.global.ChannelSupervise;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;import java.text.SimpleDateFormat;
import java.util.Date;/*** TextWebSocketFrame  表示一个文本贞* 浏览器和服务端以TextWebSocketFrame 格式交互*/
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd hh:MM:ss");@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame)throws Exception {System.out.println("服务端收到消息:" + textWebSocketFrame.text());// 回复浏览器String resp = sdf.format(new Date()) + ": " + textWebSocketFrame.text();// 转发给所有的客户端ChannelSupervise.send2All(new TextWebSocketFrame(resp));// 发送给当前客户单// channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame(resp));}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 添加连接System.out.println("客户端加入连接:" + ctx.channel());ChannelSupervise.addChannel(ctx.channel());}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {// 断开连接System.out.println("客户端断开连接:" + ctx.channel());ChannelSupervise.removeChannel(ctx.channel());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("异常发生:" + cause.getMessage());ctx.channel().close();}
}

参考:NIO框架Netty+WebSocket实现网页聊天_nio实现websocket-CSDN博客

一、基于Netty,手动处理WebSocket握手信息:

参考:GitCode - 开发者的代码家园icon-default.png?t=N7T8https://gitcode.com/Siwash/websocketWithNetty/blob/master/README.md

 基于netty搭建websocket,实现消息的主动推送_websocket_rpf_siwash-GitCode 开源社区

 服务端代码:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;import org.apache.log4j.Logger;/*** https://gitcode.com/Siwash/websocketWithNetty/blob/master/README.md*/
public class NioWebSocketServer {private final Logger logger = Logger.getLogger(getClass());private void init() {logger.info("正在启动websocket服务器");NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup work = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(boss, work);bootstrap.channel(NioServerSocketChannel.class);bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast("logging", new LoggingHandler("DEBUG"));// 设置log监听器,并且日志级别为debug,方便观察运行流程ch.pipeline().addLast("http-codec", new HttpServerCodec());// 设置解码器ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));// 聚合器,使用websocket会用到ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());// 用于大数据的分区传输ch.pipeline().addLast("handler", new NioWebSocketHandler());// 自定义的业务handler,这里处理WebSocket建链请求和消息发送请求}});Channel channel = bootstrap.bind(7070).sync().channel();logger.info("webSocket服务器启动成功:" + channel);channel.closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();logger.info("运行出错:" + e);} finally {boss.shutdownGracefully();work.shutdownGracefully();logger.info("websocket服务器已关闭");}}public static void main(String[] args) {new NioWebSocketServer().init();}
}
import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;import com.huawei.websocket.nio2.global.ChannelSupervise;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;import org.apache.log4j.Logger;import java.util.Date;/*** 这里处理WebSocket建链请求和消息发送请求*/
public class NioWebSocketHandler extends SimpleChannelInboundHandler<Object> {private final Logger logger = Logger.getLogger(getClass());private WebSocketServerHandshaker handshaker;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {logger.debug("收到消息:" + msg);if (msg instanceof FullHttpRequest) {// 以http请求形式接入,但是走的是websockethandleHttpRequest(ctx, (FullHttpRequest) msg);} else if (msg instanceof WebSocketFrame) {// 处理websocket客户端的消息handlerWebSocketFrame(ctx, (WebSocketFrame) msg);}}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 添加连接logger.debug("客户端加入连接:" + ctx.channel());ChannelSupervise.addChannel(ctx.channel());}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {// 断开连接logger.debug("客户端断开连接:" + ctx.channel());ChannelSupervise.removeChannel(ctx.channel());}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {// 判断是否关闭链路的指令if (frame instanceof CloseWebSocketFrame) {handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());return;}// 判断是否ping消息if (frame instanceof PingWebSocketFrame) {logger.debug("服务端收到ping消息");ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));return;}// 本例程仅支持文本消息,不支持二进制消息if (!(frame instanceof TextWebSocketFrame)) {logger.debug("本例程仅支持文本消息,不支持二进制消息");throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));}// 返回应答消息String request = ((TextWebSocketFrame) frame).text();logger.debug("服务端收到:" + request);TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + ":" + request);// 群发ChannelSupervise.send2All(tws);// 返回【谁发的发给谁】// ctx.channel().writeAndFlush(tws);}/*** 唯一的一次http请求,用于创建websocket* */private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {// 要求Upgrade为websocket,过滤掉get/Postif (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {// 若不是websocket方式,则创建BAD_REQUEST的req,返回给客户端sendHttpResponse(ctx, req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));return;}logger.debug("服务端收到WebSocket建链消息");WebSocketServerHandshakerFactory wsFactory =new WebSocketServerHandshakerFactory("ws://localhost:7070/helloWs", null, false);handshaker = wsFactory.newHandshaker(req);if (handshaker == null) {WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());} else {handshaker.handshake(ctx.channel(), req);}}/*** 拒绝不合法的请求,并返回错误信息* */private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {// 返回应答给客户端if (res.status().code() != 200) {ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);res.content().writeBytes(buf);buf.release();}ChannelFuture f = ctx.channel().writeAndFlush(res);// 如果是非Keep-Alive,关闭连接if (!isKeepAlive(req) || res.status().code() != 200) {f.addListener(ChannelFutureListener.CLOSE);}}
}

 这里我们手动处理了握手信息:

可以看到服务端handler日志:

2024-05-24 09:43:51 DEBUG [NioWebSocketHandler] 收到消息:HttpObjectAggregator$AggregatedFullHttpRequest(decodeResult: success, version: HTTP/1.1, content: CompositeByteBuf(ridx: 0, widx: 0, cap: 0, components=0))
GET /helloWs HTTP/1.1
Host: localhost:7070
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Sec-WebSocket-Key: KYh4//SBKLi+nSu6v1kYqw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
content-length: 0
2024-05-24 09:43:51 DEBUG [NioWebSocketHandler] 服务端收到WebSocket建链消息

Client1的发送和接收消息,在F12小可以看到:

测试用的Client代码如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>WebSocket Client</title></head><body><script>var socket;//check current explorer wether support WebSockekif (window.WebSocket) {socket = new WebSocket("ws://localhost:7070/helloWs");//event : msg from serversocket.onmessage = function(event) {console.log("receive msg:" + event.data);var rt = document.getElementById("responseText");rt.value = rt.value + "\n" + event.data;}//eq open WebSocketsocket.onopen = function(event) {var rt = document.getElementById("responseText");rt.value = "open WebSocket";}socket.onclose = function(event) {var rt = document.getElementById("responseText");rt.value = rt.value + "\n" + "close WebSocket";}} else {alert("current exploer not support websocket")}//send msg to WebSocket Serverfunction send(message) {if (socket.readyState == WebSocket.OPEN) {//send msg to WebSocket by socketsocket.send(message);} else {alert("WebSocket is not open")}}</script><form onsubmit="return false"><textarea name="message" style="height: 300px; width: 300px;"></textarea><input type="button" value="发送消息" onclick="send(this.form.message.value)"><textarea id="responseText" style="height: 300px; width: 300px;"></textarea><input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''"></form></body>
</html>

直接保存为html文件,使用浏览器打开即可运行测试;

相关文章:

基于Netty实现WebSocket服务端

本文基于Netty实现WebSocket服务端&#xff0c;实现和客户端的交互通信&#xff0c;客户端基于JavaScript实现。 在【WebSocket简介-CSDN博客】中&#xff0c;我们知道WebSocket是基于Http协议的升级&#xff0c;而Netty提供了Http和WebSocket Frame的编解码器和Handler&#…...

27【Aseprite 作图】盆栽——拆解

1 橘子画法拆解 (1)浅色3 1 0;深色0 2 3 就可以构成一个橘子 (2)浅色 2 1;深色1 0 (小个橘子) (3)浅色 2 1 0;深色1 2 3 2 树根部分 (1)底部画一条横线 (2)上一行 左空2 右空1 【代表底部重心先在右】 (3)再上一行,左空1,右空1 (4)再上一行,左突出1,…...

【开源】2024最新python豆瓣电影数据爬虫+可视化分析项目

项目介绍 【开源】项目基于pythonpandasflaskmysql等技术实现豆瓣电影数据获取及可视化分析展示&#xff0c;觉得有用的朋友可以来个一键三连&#xff0c;感谢&#xff01;&#xff01;&#xff01; 项目演示 【开源】2024最新python豆瓣电影数据爬虫可视化分析项目 项目截图…...

[JDK工具-5] jinfo jvm配置信息工具

文章目录 1. 介绍2. 打印所有的jvm标志信息 jinfo -flags pid3. 打印指定的jvm参数信息 jinfo -flag InitialHeapSize pid4. 启用或者禁用指定的jvm参数 jinfo -flags [|-]HeapDumpOnOutOfMemoryError pid5. 打印系统参数信息 jinfo -sysprops pid6. 打印以上所有配置信息 jinf…...

【Linux系统编程】进程概念、进程排队、进程标识符、进程状态

目录 什么是进程&#xff1f; 浅谈进程排队 简述进程属性 进程属性之进程标识符 进程操作之进程创建 初识fork fork返回值 原理角度理解fork fork的应用 进程属性之进程状态 再谈进程排队 进程状态 运行状态 阻塞状态 挂起状态 Linux下的进程状态 “R”(运行状…...

Java与GO语言对比分析

你是不是总听到go与java种种对比&#xff0c;其中在高并发的服务器端应用场景会有人推荐你使用go而不是 java。 那我们就从两者运行原理和基本并发设计来对比分析&#xff0c;看看到底怎么回事。 运行原理对比 java java 中 jdk 已经帮我们屏蔽操作系统区别。 只要我们下载并…...

Linux文件系统原理

Linux文件系统 冯诺依曼在1945年提出计算机的五大组成部分 运算器&#xff1a;CPU 控制器&#xff1a;CPU 存储器&#xff1a;内存和硬盘 输入设备&#xff1a;鼠标、硬盘 输出设备&#xff1a;显示器一、硬盘结构 机械硬盘结构 扇区&#xff1a;硬盘的最小存储单位&#xff…...

初识Spring Cache:如何简化你的缓存处理?

文章目录 1、Spring Cache介绍2、 常用注解3、 使用案例 1、Spring Cache介绍 Spring Cache 是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能。 Spring Cache 提供了一层抽象&#xff0c;底层可以切换不同的…...

攻防世界[GoodRe]

攻防世界[GoodRe] 学到知识&#xff1a; 逆向的精髓&#xff1a;三分懂&#xff0c;七分蒙。TEA 算法快速识别&#xff08;蒙&#xff09;&#xff1a; 数据处理的形式&#xff1a;进入加密时的数据和加密结束后的数据&#xff0c;处理时数据的分组等等&#xff0c;都能用来…...

IntelliJ IDEA实用插件:轻松生成时序图和类图

IntelliJ IDEA生成时序图、类图 一、SequenceDiagram1.1 插件安装1.2 插件设置1.3 生成时序图 二、PlantUML Integration2.1 插件安装2.2 插件设置2.3 生成类图 在软件建模课程的学习中&#xff0c;大家学习过多种图形表示方法&#xff0c;这些图形主要用于软件产品设计。在传统…...

SpringBoot + Mybatis-Plus中乐观锁实现

悲观锁 悲观锁是一种悲观思想&#xff0c;它认为数据很可能会被别人所修改 所以总会对数据进行上锁&#xff0c;读操作和写操作都会上锁&#xff0c;性能较低&#xff0c;使用较少&#xff01; 乐观锁 乐观锁是一种乐观思想&#xff0c;它认为数据并不一定会被别人所修改 所以…...

设计模式深度解析:分布式与中心化,IT界两大巨头“华山论剑”

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨IT界的两大巨头交锋✨ &#x1f44b; 在IT界的广阔天地中&#xff0c;有两座…...

转行一年了

关注、星标公众号&#xff0c;直达精彩内容 ID&#xff1a;技术让梦想更伟大 整理&#xff1a;李肖遥 来公司一年了。 说是转行其实还是在半导体行业&#xff0c;熟悉我的朋友知道 &#xff0c;我在18年开始进入半导体行业&#xff0c;那个时候想着行业很重要&#xff0c;站对了…...

【LeetCode 151】反转字符串中的单词

1. 题目 2. 分析 这题要是用Python写&#xff0c;就非常简单了。 3. 代码 class Solution:def reverseWords(self, s: str) -> str:s " ".join(reversed(s.strip().split()))return s...

Behind the Code:Polkadot 如何重塑 Web3 未来

2024 年 5 月 17 日 Polkadot 生态 Behind the Code 第二季第一集 《创造 Web3 的未来》正式上线。第一集深入探讨了 Polkadot 和 Web3 技术在解决数字身份、数据所有权和去中心化治理方面的巨大潜力。 &#x1f50d; 查看完整视频&#xff1a; https://youtu.be/_gP-M5nUidc?…...

for循环里如果std::pair的类型写不对,可能会造成性能损失

第一版 std::map<int, int> t;t.emplace(1, 1);for (const std::pair<int,int>& data : t){int i 0;std::ignore i;}中间留一些空格&#xff0c;是因为ms在调试的时候&#xff0c;尤其是模板比较多的时候&#xff0c;经常断点的行号有问题。比如第5行的断点&…...

【Linux】Linux的基本指令_2

文章目录 二、基本指令8. man9. nano 和 cat10. cp11. mv12. echo 和 > 和 >> 和 <13. more 和 less14. head 和 tail 和 | 未完待续 二、基本指令 8. man Linux的命令有很多参数&#xff0c;我们不可能全记住&#xff0c;我们可以通过查看联机手册获取帮助。访问…...

Effective C++(3)

3.资源管理 条款13&#xff1a;以对象管理资源 以对象管理资源对于传统的堆资源管理&#xff0c;我们需要使用成对的new和delete&#xff0c;这样若忘记delete就会造成内存泄露。因此&#xff0c;我们应尽可能以对象管理资源&#xff0c;并采用RAII&#xff08;Resource Acqu…...

自定义RedisTemplate序列化器

大纲 RedisSerializerFastJsonRedisSerializer自定义二进制序列化器总结代码 在《RedisTemplate保存二进制数据的方法》一文中&#xff0c;我们将Java对象通过《使用java.io库序列化Java对象》中介绍的方法转换为二进制数组&#xff0c;然后保存到Redis中。实际可以通过定制Red…...

Flutter 中的 CupertinoContextMenuAction 小部件:全面指南

Flutter 中的 CupertinoContextMenuAction 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;CupertinoContextMenuAction 是一个专门用于构建 iOS 风格的上下文菜单选项的组件。它为用户提供了一种便捷的方式来执行与特定项目相关的操作&#xff0c;例如在列表项上长按可…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...