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不仅能够处理图像,还能够处理视频。视频是由大量的图像构成的,这些图像是以固定的时间间隔从视频中获取的。这样,就能够使用图像处理的方法对这些图像进行处理,进而达到…...
PostgreSQL命令行工具psql常用命令
1. 概述 通常情况下操作数据库使用图形化客户端工具,在实际工作中,生产环境是不允许直接连接数据库主机,只能在跳板机上登录到Linux服务器才能连接数据库服务器,此时就需要使用到命令行工具。psql是PostgreSQL中的一个命令行交互…...
【CSS 画个梯形】
使用clip-path: polygon画梯形 clip-path: polygon使用方式如下: 效果实现 clip-path: polygon 是CSS的属性之一,用于裁剪元素的形状。它可以通过定义一个具有多边形顶点坐标的值来创建一个多边形的裁剪区域,从而实现元素的非矩形裁剪效果。…...
Spring Data Redis
文章目录 Redis各种Java客户端Spring Data Redis使用方式操作字符串类型的数据操作哈希类型数据列表类型集合类型有序集合类型通用类型 Redis各种Java客户端 Java中如何操作redis,这里主讲IDEA中的框架Spring Data Redis来操作redis Jedis是官方推出的,…...
软件测试的方法有哪些?
软件测试 根据利用的被测对象信息的不同,可以将软件测试方法分为:黑盒测试、灰盒测试、白盒测试。 1、白盒测试 1)概念:是依据被测软件分析程序内部构造,并根据内部构造分析用例,来对内部控制流程进行测试…...
Python Qt学习(二)Qt Designer
一开始以为Designer是个IDE,多番尝试之后,发现,是个UI设计工具,并不能在其中直接添加代码。保存之后,会生成一个后缀是UI的文件,再用pyuic5.exe将ui文件转化成py文件。pyuic5 -o 目标py文件 源ui文件...
我的数据上传类操作(以webDAV为例)
在登录处进行初始化: 1.读取配置 GModel.ServerSetin JsonToIni.GetClass<ServerSet>(ConfigFiles.ConfigFile);if (!string.IsNullOrWhiteSpace(GModel.ServerSetin.FTPUser)){OPCommon.NetControls.NetworkShareConnect.connectToShare(GModel.ServerSeti…...
move与函数指针的简单使用
std::move() C11的标准库 提供了一个非常有用的函数 std::move(),std::move() 函数将一个左值强制转化为右值引用,以用于移动语义。 就是说 std::move(str); 之后原来的值因为变成了右值失效了 但是这样赋值可以避免出现拷贝 #include <iostream&g…...
第五章 树与二叉树 二、二叉树的定义和常考考点
一、定义 二叉树可以用以下方式详细定义: 二叉树是由节点构成的树形结构,每个节点最多可以有两个子节点。每个节点有以下几个属性: 值:存储该节点的数据。左子节点:有一个左子节点,如果没有则为空。右子节…...
算法笔记/USACO Guide GOLD金组DP 1. Introduction to DP
USACO Guide中金组的内容分为一下六个章节 DP数学图论数据结构树一些附加主题 今天学习DP,以下内容: 初入DP背包DP图表中的路线最长递增序列状态压缩DP区间DP数位DP 初入DP Dynamic Programming (DP) is an important algorithmic technique in Comp…...
天锐绿盾安全U盘系统
安全U盘系统 01 简介 天锐绿盾安全U盘系统,是一款致力于保障U盘数据内容安全的产品。通过严格身份认证、便捷安全的密保机制、智能的U盘锁定或自毁设置、详细的文件操作日志、文件粉碎、设置还原等,天锐绿盾安全U盘系统为您U盘的数据保驾护航࿰…...
做网站的域名/张家港seo建站
WPF 程序出现: 参数计数不匹配,未处理System.Reflection.TargetParameterCountException解决方法引用http://www.cnblogs.com/wene/p/4668830.html根据调试的实际情况显示,委托出现问题,此异常是在使用Invoke调用时,没…...
网站打开出现建设中/搜索引擎优化的简称是
虚拟化由于其带来的维护费用的大幅降低而受到追捧,如能减少服务器占用空间,降低购买软硬件设备的成本,大幅度提高系统的利用率。然而对其安全问题,人们也一直在争论不休,一方观点认为虚拟化技术能有效提升系统的安全性…...
产品外观工业设计公司/小吴seo博客
读书笔记--第5篇--《公司绝不会告诉你的50大秘密》 0.法律解救不了您。 1.聪明过头并非明智之举。 2.年龄和性别歧视是活生生的现实。 3.公司并非畅所欲言的好地方。 4.如果你与老板作对,必然会被逐出公司大门。 5.与人力部门的闲谈,会使你面临失业的危险…...
做美食网站的意义/互动营销经典案例
Scrum一直以来争论不断。虽然创始人Ken在演讲中曾说过即使是白痴也可以用Scrum,但是依然有很多人认为Scrum对团队成员的素质要求非常高。另据统计,75%以上的Scrum都可以称得上失败。 去年十月,有幸参加了Outsofting鲍央舟老师的Scrum培训。培…...
那些公司做网站比较厉害/网上销售渠道
为什么要做持久化存储?持久化存储是将 Redis 存储在内存中的数据存储在硬盘中,实现数据的永久保存。我们都知道 Redis 是一个基于内存的 nosql 数据库,内存存储很容易造成数据的丢失,因为当服务器关机等一些异常情况都会导致存储在内存中的数…...
更改各网站企业信息怎么做/手机优化软件哪个好
win32-mysql配置将win32-mysql放到某个目录下在配置环境变量C:\mysql\lib;C:\mysql\bin加入环境变量 远程连接ubuntu下mysql:1、vim /etc/mysql/my.cnf找到bind-address 127.0.0.1注释掉这行,如:#bind-address 127.0.0.1或者改为࿱…...