Netty简易聊天室
文章目录
- 本文目的
- 参考说明
- 环境说明
- maven依赖
- 日志配置
- 单元测试
- 功能介绍
- 开发步骤
本文目的
- 通过一个简易的聊天室案例,讲述Netty的基本使用。同时分享案例代码。
- 项目中用到了log4j2,junit5,同时分享这些基础组件的使用。
- 项目中用到了awt,属于古董技术,只是用来做界面。非重点不用关注。
参考说明
本文内容主要来源于马士兵老师的视频教程(Java经典实战项目-坦克大战),结合了老师的讲课内容以及自己的实践做了一些补充。
环境说明
开发工具:idea2023,jdk:1.8,Maven:3.6.3
maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.xxx</groupId><artifactId>xxx</artifactId><version>0.0.1-SNAPSHOT</version><name>xxx</name><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.21</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></dependency><!-- log4j2-slf4j-适配器 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.20.0</version></dependency><!-- log4j2 日志核心 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.20.0</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.96.Final</version></dependency><!-- 单元测试,Junit5 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.9.3</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
日志配置
src/main/resources/log4j2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- log4j2配置文件 -->
<!-- monitorInterval="30" 自动加载配置文件的间隔时间,不低于10秒;生产环境中修改配置文件,是热更新,无需重启应用status="info" 日志框架本身的输出日志级别,可以修改为info, -->
<Configuration status="warn" monitorInterval="30"><!-- 集中配置属性,使用时通过:${LOG_HOME} --><properties><!-- 当前项目名称,供下方引用 --><property name="PROJECT_NAME" value="tank-battle"/><!-- 默认日志格式-包名自动缩减(同步异步通用) --><property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%-5t|%logger{1.}: %msg%n"/><!-- 日志格式-打印代码的精确位置信息,类,方法,行。(建议同步使用)。异步如果打印位置信息,会有严重性能问题 --><property name="LOG_PATTERN_ALL" value="%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%-5t|%location: %msg%n"/><!-- 日志主目录。如果想把日志输出到tomcat底下时使用。 --><property name="LOG_HOME">${web:rootDir}/WEB-INF/logs</property></properties><!-- 日志打印输出方式 --><Appenders><Console name="STDOUT" target="SYSTEM_OUT"><PatternLayout charset="UTF-8" Pattern="${LOG_PATTERN}"/></Console><RollingFile name="FileLog" fileName="logs/${PROJECT_NAME}.log" filePattern="logs/${PROJECT_NAME}-%d_%i.log"><PatternLayout charset="UTF-8" Pattern="${LOG_PATTERN}"/><Policies><!-- 每天生成一个,同时如果超过10MB还会再生成 --><TimeBasedTriggeringPolicy/><SizeBasedTriggeringPolicy size="50 MB"/></Policies><DefaultRolloverStrategy max="99"/></RollingFile></Appenders><!-- 将代码路径与上面的日志打印关联起来 --><Loggers><!-- 当前项目日志 --><Logger name="com.sjj" level="INFO" additivity="false"><AppenderRef ref="STDOUT"/><AppenderRef ref="FileLog"/></Logger><!-- 第三方依赖项目日志 --><logger name="org.springframework" level="info"/><logger name="org.jboss.netty" level="warn"/><!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --><!-- 根节点日志,除了上面配置的之外的日志 --><Root level="WARN"><AppenderRef ref="STDOUT"/><AppenderRef ref="FileLog"/></Root></Loggers>
</Configuration>
单元测试
确认项目已加入Junit5依赖,就是如下这段。
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.9.3</version><scope>test</scope></dependency>
新建单元测试类的步骤。
- 在要创建单元测试的功能类上,依次点Code > generate > Test
- 然后在弹出的窗口中,选择Junit版本为5,测试类名,测试方法等。然后点确定。
- IDEA会自动根据功能类的路径在test目录中创建相同路径但以Test结尾的测试类。并且会自动生成勾选方法的默认测试代码。
- 根据程序的输入和输出,编写单元测试代码。
- 点击方法左边的绿色三角形就可以执行单元测试用例了。
为什么要进行单元测试?
- 方法内部可以很复杂,如果靠肉眼观察,比较耗时间。单元测试可以根据入参和返回值测试方法是否达到要求。
- 代码是开发人员写的,最了解代码逻辑的还是开发人员。测试人员测试不到代码细节。
- 在一个大的功能中,可能会有很多方法,每个方法都要写Main方法来一个个测试比较复杂,而且也不知道测了哪些场景。
为什么有的公司不做单元测试。
- 代码业务可能比较简单,程序员读代码不是很费力。
- 写单元测试需要额外花时间,程序员工作比较忙,没时间写。
功能介绍
简易版聊天室程序。主要用于练习Netty的使用。聊天室功能如下:
- 聊天室支持多客户端,每个客户端都可以看到其他客户端的消息。
- 点击关闭按钮时,关闭当前客户端,同时在服务端的客户端列表中也删除。
- 系统UI非重点,一切从简。
开发步骤
-
首先写一个聊天室的界面(ChatFrame.java)
-
参考坦克大战的界面部分,设置好聊天室的长宽和坐标。
-
界面包含2个输入部分,中间文本域显示当前聊天室的所有聊天内容。底部文本框输入当前用户的聊天内容
-
聊天室窗口初始化时,需要与服务端建立连接。
-
当用户输入完聊天内容后回车,需要将聊天内容通过Netty客户端发送给服务端。
-
当用户关闭窗口时,关闭当前客户端,同时在服务端的客户端列表中也删除。
-
/*** 聊天室客户端-界面<br>** @author namelessmyth* @version 1.0* @date 2023/8/15*/ @Slf4j public class ChatFrame extends Frame {public static final int GAME_WIDTH = ConfigUtil.getInt("chat.frame.width");public static final int GAME_HEIGHT = ConfigUtil.getInt("chat.frame.height");TextArea ta = new TextArea();TextField tf = new TextField();public static final ChatFrame INSTANCE = new ChatFrame();public static void main(String[] args) throws Exception {INSTANCE.setVisible(true);ChatClient.connect();}private ChatFrame() throws HeadlessException {//创建游戏的主Framethis.setTitle("chat room");this.setSize(GAME_WIDTH, GAME_HEIGHT);this.setLocation(800, 100);this.add(ta, BorderLayout.CENTER);this.add(tf, BorderLayout.SOUTH);tf.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {ChatClient.send(tf.getText());tf.setText("");}});this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {ChatClient.close();System.exit(0);}});log.info("chat room Main frame initialization completed");}public void updateText(String text) {ta.setText(ta.getText() + Constants.LINE_SEPERATOR + text);} }
-
-
编写Netty客户端与服务端进行消息通信(ChatClient.java)。
-
参考上面的描述,客户端需要实现如下方法。
- connect(),与服务端建立连接的方法
- send(),向服务端发送聊天消息的方法。
- channelRead,读取服务端信息更新客户端聊天内容方法
- 参考代码如下
-
@Slf4j public class ChatClient {private static SocketChannel channel;/*** 与服务端建立连接的方法*/public static void connect() {EventLoopGroup group = new NioEventLoopGroup(1);try {Bootstrap b = new Bootstrap();b.group(group);b.channel(NioSocketChannel.class);b.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {channel = ch;ch.pipeline().addLast(new MyClientHandler());}});ChannelFuture cf = b.connect("localhost", 8888).sync();//直到服务器被关闭,否则一直阻塞。cf.channel().closeFuture().sync();log.info("the chat client has been closed.");} catch (Exception e) {log.error("ChatClient.connect.Exception.", e);} finally {group.shutdownGracefully();}}/*** 向服务端发送聊天消息的方法* @param msg 聊天内容*/public static void send(String msg) {channel.writeAndFlush(Unpooled.copiedBuffer(msg.getBytes()));log.info("client.send().{}", msg);}/*** 关闭客户端方法,向服务端发送特定消息告知其删除本客户端。*/public static void close() {send("__88__");channel.close();} }@Slf4j class MyClientHandler extends ChannelInboundHandlerAdapter {/*** 读取服务端数据* @param msg 服务端数据*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;String text = buf.toString(StandardCharsets.UTF_8);ChatFrame.INSTANCE.updateText(text);log.info("channelRead.msg:{}", text);}/*** 连接刚建立时的事件处理*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {log.info("connected to server.");}/*** 异常处理*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.error("chat client exceptionCaught:", cause);super.exceptionCaught(ctx, cause);} }
-
-
聊天室服务端(ChatServer.java)。
-
服务端需要记录所有的客户端。(可能有多个)
-
当某个客户端发来消息之后,需要将消息转发给所有客户端。
-
当接收到特殊消息时(客户端关闭),需要将客户端从列表中移除。
-
@Slf4j public class ChatServer {static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);public static void main(String[] args) throws Exception {//总管线程组EventLoopGroup bossGroup = new NioEventLoopGroup(1);//接待员线程EventLoopGroup workerGroup = new NioEventLoopGroup(2);//服务器启动辅助类ServerBootstrap b = new ServerBootstrap();//放在第一位的是总管线程组,第二位的就是接待员线程组。b.group(bossGroup, workerGroup);//异步全双工b.channel(NioServerSocketChannel.class);//接收到客户端连接的处理,相当于BIO的acceptb.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel sc) throws Exception {log.info("a client connected:{}", sc);sc.pipeline().addLast(new MyChildHandler());}});b.bind(8888).sync();} }@Slf4j class MyChildHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ChatServer.clients.add(ctx.channel());}/*** 读取客户端通道内的数据* @param msg 客户端消息* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;String str = buf.toString(StandardCharsets.UTF_8);log.info("channelRead().input,string:{},buf:{}", str, buf);if (StrUtil.equalsIgnoreCase(str, "__88__")) {ChatServer.clients.remove(ctx.channel());ctx.close();log.info("The chat client has been closed:{}", ctx.channel());} else {ChatServer.clients.writeAndFlush(msg);log.info("ChatServer.clients.writeAndFlush:{}", msg);}}/*** 异常处理** @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.error("exceptionCaught:", cause);ChatServer.clients.remove(ctx.channel());ctx.close();} }
-
补充服务端关闭的处理(仅思路,未实现)。
- 通知客户端,服务器准备关闭。
- 拒绝新的连接接入
- 等待所有客户端都处理完成。
- 开始关闭流程,发送消息给客户端,客户端自动处理。
- 确认所有客户端断开。
- server保存现有的工作数据。
- 停止线程组
- 退出。
-
服务端UI
-
为了可以方便的看到所有客户端的连接情况和消息,以及后续进一步实现服务端的关闭效果考虑在服务端实现UI
-
新增一个ServerFrame类,实现服务端UI,服务端左边显示消息,右边显示客户端的连接情况。
-
ServerFrame类初始化时自动启动服务端。服务端接收消息时打印到消息窗口中。
-
有客户端连上或者关闭时显示到右边的窗口中。
-
实现效果如下图
-
-
参考代码如下。(只需要修改服务端代码,客户端不变)
-
@Slf4j public class ServerFrame extends Frame {public static final int GAME_WIDTH = ConfigUtil.getInt("server.frame.width");public static final int GAME_HEIGHT = ConfigUtil.getInt("server.frame.height");TextArea tmsg = new TextArea("messages:");TextArea tclient = new TextArea("clients:");public static final ServerFrame INSTANCE = new ServerFrame();public static void main(String[] args) throws Exception {INSTANCE.setVisible(true);ChatServer.start();}private ServerFrame() throws HeadlessException {//创建游戏的主Framethis.setTitle("chat room");this.setSize(GAME_WIDTH, GAME_HEIGHT);this.setLocation(100, 100);tmsg.setFont(new Font("Calibri",Font.PLAIN,20));tclient.setFont(new Font("Calibri",Font.PLAIN,20));Panel p = new Panel(new GridLayout(1, 2));p.add(tmsg);p.add(tclient);this.add(p);this.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});log.info("Server Main frame initialization completed");}public void updateMsg(String text) {tmsg.setText(tmsg.getText() + Constants.LINE_SEPERATOR + text);}public void updateClient(String text) {tclient.setText(tclient.getText() + Constants.LINE_SEPERATOR + text);} }@Slf4j public class ChatServer {static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);public static void start(){//总管线程组EventLoopGroup bossGroup = new NioEventLoopGroup(1);//接待员线程EventLoopGroup workerGroup = new NioEventLoopGroup(2);try {//服务器启动辅助类ServerBootstrap b = new ServerBootstrap();//放在第一位的是总管线程组,第二位的就是接待员线程组。b.group(bossGroup, workerGroup);//异步全双工b.channel(NioServerSocketChannel.class);//接收到客户端连接的处理,相当于BIO的acceptb.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel sc) throws Exception {log.info("a client connected:{}", sc);sc.pipeline().addLast(new MyChildHandler());}});log.info("chat server has been started");ChannelFuture cf = b.bind(8888).sync();cf.channel().closeFuture().sync();} catch (Exception e) {log.error("ChatServer.exception", e);} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();log.info("chat server has been closed");}} }@Slf4j class MyChildHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ServerFrame.INSTANCE.updateClient("client connected:"+ctx.channel().remoteAddress());ChatServer.clients.add(ctx.channel());}/*** 读取客户端通道内的数据** @param msg 客户端消息* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;String str = buf.toString(StandardCharsets.UTF_8);log.info("channelRead().input,string:{},buf:{}", str, buf);if (StrUtil.equalsIgnoreCase(str, "__88__")) {ChatServer.clients.remove(ctx.channel());ctx.close();ServerFrame.INSTANCE.updateClient("client closed>"+ctx.channel().remoteAddress());log.info("The chat client has been closed:{}", ctx.channel());} else {ChatServer.clients.writeAndFlush(msg);ServerFrame.INSTANCE.updateMsg(ctx.channel().remoteAddress() + ">" + str);log.info("ChatServer.clients.writeAndFlush:{}", msg);}}/*** 异常处理** @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {log.error("ChatServer.exceptionCaught:", cause);ChatServer.clients.remove(ctx.channel());ctx.close();} }
-
启动顺序。先启动ServerFrame,然后启动ChatFrame,ChatFrame可以启动多个。
-
多个客户端发送消息都会在服务端显示。
-
-
相关文章:

Netty简易聊天室
文章目录 本文目的参考说明环境说明maven依赖日志配置单元测试 功能介绍开发步骤 本文目的 通过一个简易的聊天室案例,讲述Netty的基本使用。同时分享案例代码。项目中用到了log4j2,junit5,同时分享这些基础组件的使用。项目中用到了awt&…...

Flutter Cannot run with sound null safety, because the following dependencies
flutter sdk 版本升级到2.0或者更高的版本后,运行之前的代码会报错 Error: Cannot run with sound null safety, because the following dependencies dont support null safety:- package:flutter_swiper- package:flutter_page_indicator- package:transformer_p…...

利用改进的遗传算法(种群隔离与个体迁移)mpi并行解决tsp问题
序 关于tsp问题的概述以及如何使用遗传算法进行求解已经在上一篇文章中说明了:遗传算法解决TSP问题. 但是,作为一种演化算法,遗传算法还存在着许多问题,比如早熟的情况,很容易在算法前期就已经收敛了,大量…...

【C++】—— C++11之线程库
前言: 在本期,我将给大家介绍的是 C11 中新引进的知识,即关于线程库的相关知识。 目录 (一)线程库的介绍 1、线程库的由来 2、线程库的简单介绍 (二)线程函数参数 (三…...
前端面试:【性能优化】前端缓存、CDN、懒加载和预加载
亲爱的前端开发者,Web性能对用户体验至关重要。如果你想让你的网站更快、更具吸引力,就需要关注前端性能优化。在这篇文章中,我们将深入探讨四个关键的性能优化策略:前端缓存、CDN(内容分发网络)、懒加载和…...

民族传统文化分享系统uniapp 微信小程序
管理员、用户可通过Android系统手机打开系统,注册登录后可进行管理员后端;首页、个人中心、用户管理、知识分类管理、知识资源管理、用户分享管理、意见反馈、系统管理,用户前端;首页、知识资源、用户分享、我的等。 本系统的使用…...
netty(二):NIO——处理可写事件
处理可写事件 什么情况下需要注册可写事件? 在服务端一次性无法把数据发送完的情况下,需要注册可写事件 服务端一次性是否能够把数据全部发送完成取决于服务端的缓冲区大小,该缓冲区不受程序控制 注册可写事件的步骤 判断ByteBuffer是否仍…...
PHP基本语法解析与应用指南
PHP(Hypertext Preprocessor)是一种广泛应用的开源脚本语言,特别适用于Web开发。本文将深入探讨PHP的基本语法,包括变量、数据类型、运算符、控制流等方面的内容。我们将详细介绍每个主题的基本概念、语法规则和常见应用ÿ…...

ICS PA1
ICS PA1 init.shmake 编译加速ISA计算机是个状态机程序是个状态机准备第一个客户程序parse_argsinit_randinit_loginit_meminit_isa load_img剩余的初始化工作运行第一个客户程序调试:零断点TUI 基础设施单步执行打印寄存器状态扫描内存 表达式求值词法分析递归求值…...

Java学数据结构(4)——散列表Hash table 散列函数 哈希冲突
目录 引出散列表Hash table关键字Key和散列函数(hash function)散列函数解决collision哈希冲突(碰撞)分离链接法(separate chaining)探测散列表(probing hash table)双散列(double hashing) Java标准库中的散列表总结 引出 1.散列表,key&…...

OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 论文阅读
论文信息 题目:OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 作者:Karmesh Yadav, Arjun Majumdar, Ram Ramrakhya 来源:arxiv 时间:2023 代码地址: https://github.com/ykarmesh…...

【安全】原型链污染 - Hackit2018
目录 准备工作 解题 代码审计 Payload 准备工作 将这道题所需依赖模块都安装好后 运行一下,然后可以试着访问一下,报错是因为里面没内容而已,不影响,准备工作就做好了 解题 代码审计 const express require(express) var hbs require…...
net.ipv4.ip_forward=0导致docker容器无法与外部通信
在启动一个docker容器时报错: WARNING: IPv4 forwarding is disabled. Networking will not work. 并且,此时本机上的其他容器的网络服务,只能在本机上访问,其他机器上访问不到。 原因: sysctl net.ipv4.ip_forward …...
软考高级系统架构设计师系列论文九十八:论软件开发平台的选择与应用
软考高级系统架构设计师系列论文九十八:论软件开发平台的选择与应用 一、相关知识点二、摘要三、正文四、总结一、相关知识点 软考高级系统架构设计师系列之:面向构件的软件设计,构件平台与典型架构二、摘要 本文讨论选择新软件开发平台用于重新开发银行中间业务系统。银行中…...
Springboot整合WebFlux
一、使用WebFlux入门 WebFlux整合MysqlWebFlux整合ESWebFlus整合MongdbWebFlus整合Redis 1、添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId><version>2.2.1.…...

uniapp 实现地图距离计算
在uniapp中实现地图距离计算可以借助第三方地图服务API来实现。以下是一种基本的实现方式: 注册地图服务API账号:你可以选择使用高德地图、百度地图等提供地图服务的厂商,注册一个开发者账号并获取API密钥。 安装相关插件或SDK:根…...

破除“中台化”误区,两大新原则考核中后台
近年来,“中台化”已成为许多企业追求的目标,旨在通过打通前后台数据和业务流程,提升运营效率和创新能力。然而,在实施过程中,一些误解可能导致“中台化”未能如预期般发挥作用。本文将探讨这些误解,并提出…...

基于YOLOV8模型和Kitti数据集的人工智能驾驶目标检测系统(PyTorch+Pyside6+YOLOv8模型)
摘要:基于YOLOV8模型和Kitti数据集的人工智能驾驶目标检测系统可用于日常生活中检测与定位车辆、汽车等目标,利用深度学习算法可实现图片、视频、摄像头等方式的目标检测,另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用…...

基于Android的课程教学互动系统 微信小程序uniapp
教学互动是学校针对学生必不可少的一个部分。在学校发展的整个过程中,教学互动担负着最重要的角色。为满足如今日益复杂的管理需求,各类教学互动程序也在不断改进。本课题所设计的springboot基于Android的教学互动系统,使用SpringBoot框架&am…...

OpenCV基础知识(9)— 视频处理(读取并显示摄像头视频、播放视频文件、保存视频文件等)
前言:Hello大家好,我是小哥谈。OpenCV不仅能够处理图像,还能够处理视频。视频是由大量的图像构成的,这些图像是以固定的时间间隔从视频中获取的。这样,就能够使用图像处理的方法对这些图像进行处理,进而达到…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...