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>

新建单元测试类的步骤。

  1. 在要创建单元测试的功能类上,依次点Code > generate > Test
  2. 然后在弹出的窗口中,选择Junit版本为5,测试类名,测试方法等。然后点确定。
  3. 在这里插入图片描述
  4. IDEA会自动根据功能类的路径在test目录中创建相同路径但以Test结尾的测试类。并且会自动生成勾选方法的默认测试代码。
  5. 根据程序的输入和输出,编写单元测试代码。
  6. 点击方法左边的绿色三角形就可以执行单元测试用例了。

为什么要进行单元测试?

  1. 方法内部可以很复杂,如果靠肉眼观察,比较耗时间。单元测试可以根据入参和返回值测试方法是否达到要求。
  2. 代码是开发人员写的,最了解代码逻辑的还是开发人员。测试人员测试不到代码细节。
  3. 在一个大的功能中,可能会有很多方法,每个方法都要写Main方法来一个个测试比较复杂,而且也不知道测了哪些场景。

为什么有的公司不做单元测试。

  1. 代码业务可能比较简单,程序员读代码不是很费力。
  2. 写单元测试需要额外花时间,程序员工作比较忙,没时间写。

功能介绍

简易版聊天室程序。主要用于练习Netty的使用。聊天室功能如下:

  1. 聊天室支持多客户端,每个客户端都可以看到其他客户端的消息。
  2. 点击关闭按钮时,关闭当前客户端,同时在服务端的客户端列表中也删除。
  3. 系统UI非重点,一切从简。

开发步骤

  1. 首先写一个聊天室的界面(ChatFrame.java)

    1. 参考坦克大战的界面部分,设置好聊天室的长宽和坐标。

    2. 界面包含2个输入部分,中间文本域显示当前聊天室的所有聊天内容。底部文本框输入当前用户的聊天内容

    3. 聊天室窗口初始化时,需要与服务端建立连接。

    4. 当用户输入完聊天内容后回车,需要将聊天内容通过Netty客户端发送给服务端。

    5. 当用户关闭窗口时,关闭当前客户端,同时在服务端的客户端列表中也删除。

    6. /*** 聊天室客户端-界面<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);}
      }
      
  2. 编写Netty客户端与服务端进行消息通信(ChatClient.java)。

    1. 参考上面的描述,客户端需要实现如下方法。

      1. connect(),与服务端建立连接的方法
      2. send(),向服务端发送聊天消息的方法。
      3. channelRead,读取服务端信息更新客户端聊天内容方法
      4. 参考代码如下
    2. @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);}
      }
      
  3. 聊天室服务端(ChatServer.java)。

    1. 服务端需要记录所有的客户端。(可能有多个)

    2. 当某个客户端发来消息之后,需要将消息转发给所有客户端。

    3. 当接收到特殊消息时(客户端关闭),需要将客户端从列表中移除。

    4. @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();}
      }
      
    5. 补充服务端关闭的处理(仅思路,未实现)。

      1. 通知客户端,服务器准备关闭。
      2. 拒绝新的连接接入
      3. 等待所有客户端都处理完成。
      4. 开始关闭流程,发送消息给客户端,客户端自动处理。
      5. 确认所有客户端断开。
      6. server保存现有的工作数据。
      7. 停止线程组
      8. 退出。
    6. 服务端UI

      1. 为了可以方便的看到所有客户端的连接情况和消息,以及后续进一步实现服务端的关闭效果考虑在服务端实现UI

      2. 新增一个ServerFrame类,实现服务端UI,服务端左边显示消息,右边显示客户端的连接情况。

      3. ServerFrame类初始化时自动启动服务端。服务端接收消息时打印到消息窗口中。

      4. 有客户端连上或者关闭时显示到右边的窗口中。

      5. 实现效果如下图

      6. 在这里插入图片描述

      7. 参考代码如下。(只需要修改服务端代码,客户端不变)

      8. @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();}
        }
        
      9. 启动顺序。先启动ServerFrame,然后启动ChatFrame,ChatFrame可以启动多个。

      10. 多个客户端发送消息都会在服务端显示。

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

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

相关文章

elementUI moment 年月日转时间戳 时间限制

changeStartTime(val){debuggerthis.startT val// this.startTime parseInt(val.split(-).join())this.startTime moment(val).unix() * 1000 //开始时间毫秒if(this.endTime){this.endTime moment(this.endT).unix() * 1000 //结束时间毫秒if(this.startTime - this.endTi…

Git gui教程---第七篇 Git gui的使用 返回上一次提交

1&#xff0e; 查看历史&#xff0c;打开gitk程序 2&#xff0e; 选中需要返回的版本&#xff0c;右键&#xff0c;然后点击Rest master branch to here 3.出现弹窗 每个选项我们都试一下&#xff0c;从Hard开始 返回的选项 HardMixedSoft Hard 会丢失所有的修改【此处的…

AMBA总线协议(0)——目录与传送门

一、AMBA总线协议 Arm高级微控制器总线架构&#xff08;Advanced Microcontroller Bus Architecture&#xff0c;AMBA&#xff09;是一种开放式标准片上互联规范&#xff0c;用于连接和管理片上系统&#xff08;System on Chip,Soc&#xff09;中的功能块。 AMBA是一种广泛用于…

车联网技术介绍

上图是目前车联网架构图&#xff0c;基于“云-管-端”的车联网系统架构以支持车联网应用的实现&#xff0c; “云”是指 V2X 基础平台、高基于精度定位平台等基础能力&#xff0c;可实现车辆动态厘米级定位&#xff0c;这将满足现阶段以及未来车联网应用场景的定位精度需求。 “…

计算机竞赛 基于LSTM的天气预测 - 时间序列预测

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 机器学习大数据分析项目 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-senior/po…

Jenkins 详细安装流程及填坑记录「图文」

目录 一、前言 二、环境准备 三、安装步骤 1、安装jdk 2、安装jenkins 3、配置修改 4、jenkins启动 四、登录jenkins 一、前言 省流&#xff1a;本文仅记录Jenkins详细安装过程&#xff0c;以及安装过程中经常遇到的问题。 二、环境准备 Linux系统&#xff1a;CentOS…

mysql的登录与退出

mysql是c/s架构&#xff0c;意味着同时要有客户端和服务端 1 找到客户端。mysql.exe的安装目录 打开命令行 2 输入对应的服务器的ip&#xff0c;如果是本地&#xff0c;就是Localhost&#xff0c;如果是远程服务器&#xff0c;那就输入对应ip/域名。并且指定mysql监听的端口 …

大数据(一)定义、特性

大数据&#xff08;一&#xff09;定义、特性 本文目录&#xff1a; 一、写在前面的话 二、大数据定义 三、大数据特性 3.1、大数据的大量 (Volume) 特性 3.2、大数据的高速(Velocity)特性 3.3、大数据的多样化 (Variety) 特性 3.4、大数据的价值 (value) 特性 3.5、大…

axios 进阶

axios 进阶 接口传参方式 使用 xhr 原生技术或者是 axios 时&#xff0c;它的 post 传参方式是键值对的形式 keyvalue。但是在实际开发中一般是使用对象的形式定义数据&#xff0c;方便读取和赋值。所以当我们需要发起请求时可以通过 qs 这一款插件将对象转成键值对形式&…

js实现定时器

用原生js实现一个倒计时效果.最下面有全部源码,需要自取 js语法: setTimeout:定时器 document.getElementById:Document的方法 getElementById()返回一个匹配特定 ID的元素。由于元素的 ID 在大部分情况下要求是独一无二的&#xff0c;这个方法自然而然地成为了一个高效查找特…

Easy Rules规则引擎(2-细节篇)

目录 一、序言二、规则引擎参数配置实例1、skipOnFirstAppliedRules示例(1) FizzRule(2) BuzzRule(3) FizzBuzzRule(4) NonFizzBuzzRule(5) FizzBuzzRulesLauncher 2、skipOnFirstNonTriggeredRule示例3、skipOnFirstFailedRule示例 三、组合规则1、UnitRuleGroup组合规则2、Ac…

Apache Doris 入门教程34:Join 优化

Bucket Shuffle Join Bucket Shuffle Join 是在 Doris 0.14 版本中正式加入的新功能。旨在为某些 Join 查询提供本地性优化&#xff0c;来减少数据在节点间的传输耗时&#xff0c;来加速查询。 它的设计、实现和效果可以参阅 上面的图片展示了Bucket Shuffle Join的工作原理…

idea使用tomcat

1. 建立javaweb项目 2. /WEB-INF/web.xml项目配置文件 如果javaweb项目 先建立项目&#xff0c;然后在项目上添加框架支持&#xff0c;选择javaee 3. 项目结构 4.执行测试&#xff1a;

【Java转Go】快速上手学习笔记(五)之Gorm篇

目录 go get命令1、go get命令无响应问题2、Unresolved dependency错误 连接数据库连接.gomain.go 操作数据库创建表新增数据更新数据删除数据查询数据单表查询多表查询 用到的数据库表原生SQL 完整代码 go往期文章笔记&#xff1a; 【Java转Go】快速上手学习笔记&#xff08;…

接口经典题目

​ White graces&#xff1a;个人主页 &#x1f649;专栏推荐:《Java入门知识》&#x1f649; &#x1f649; 内容推荐:继承与组合&#xff1a;代码复用的两种策略&#x1f649; &#x1f439;今日诗词:人似秋鸿来有信&#xff0c;事如春梦了无痕。&#x1f439; 目录 &…

剪枝基础与实战(3): 模型剪枝和稀疏化训练流程

Model Pruning 相关论文:Learning Efficient Convolutional Networks through Network Slimming (ICCV 2017) 考虑一个问题,深度学习模型里面的卷积层出来之后的特征有非常多,这里面会不会存在一些没有价值的特征及其相关的连接?又如何去判断一个特征及其连接是否有价值? …

Packet_Tracer的使用

一、实验目的&#xff1a; 通过该实验了解Packet Tracer的使用方法&#xff0c;能够用Packet Tracer建立和模拟网络模型。 二、主要任务&#xff1a; 1.熟悉PT的界面&#xff0c;了解按键用途。 2.尝试自己建立一个小型网络&#xff0c;并测试连通性。 3.学习P…

5.6.webrtc三大线程

那今天呢&#xff1f;我们来介绍一下web rtc的三大线程&#xff0c;那为什么要介绍这三大线程呢&#xff1f;最关键的原因在于web rtc的所有其他线程都是由这三大线程所创建的。那当我们将这三个线程理解清楚之后呢&#xff1f;我们就知道其他线程与它们之间是怎样关系&#xf…

stm32 之20.HC-06蓝牙模块

原理图显示使用usart3串口使用的是PB10和PB11引脚 直接配置usart3串口协议 void usart3_init(uint32_t baud) {GPIO_InitTypeDef GPIO_InitStructureure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;//端口B硬件时钟打开RCC_AHB1PeriphClockC…

mysql my.ini、登录、用户相关操作、密码管理、权限管理、权限表

my.ini 配置文件格式 登录mysql mysql -h hostname | IP -P port -u username -p database -e “select 语句”&#xff1b; 创建用户、修改用户、删除用户 create user ‘zen’ identified by ‘密码’ ## host 默认是 % create user ‘zen’‘localhost’ identified by ‘密…