Netty简易聊天室

news2025/3/1 11:17:18

文章目录

    • 本文目的
    • 参考说明
    • 环境说明
      • 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 {
              //创建游戏的主Frame
              this.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() {
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      ChatClient.send(tf.getText());
                      tf.setText("");
                  }
              });
      
              this.addWindowListener(new WindowAdapter() {
                  @Override
                  public 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>() {
                      @Override
                      protected 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 服务端数据
           */
          @Override
          public 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);
          }
      
          /**
           * 连接刚建立时的事件处理
           */
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              log.info("connected to server.");
          }
      
          /**
           * 异常处理
           */
          @Override
          public 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的accept
              b.childHandler(new ChannelInitializer<SocketChannel>() {
                  @Override
                  protected 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 {
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              ChatServer.clients.add(ctx.channel());
          }
      
          /**
           * 读取客户端通道内的数据
           * @param msg 客户端消息
           * @throws Exception
           */
          @Override
          public 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
           */
          @Override
          public 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 {
                //创建游戏的主Frame
                this.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() {
                    @Override
                    public 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的accept
                    b.childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected 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 {
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                ServerFrame.INSTANCE.updateClient("client connected:"+ctx.channel().remoteAddress());
                ChatServer.clients.add(ctx.channel());
            }
        
            /**
             * 读取客户端通道内的数据
             *
             * @param msg 客户端消息
             * @throws Exception
             */
            @Override
            public 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
             */
            @Override
            public 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.coloradmin.cn/o/924844.html

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

相关文章

开源的经济影响:商业与社区的平衡

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Qt 解析XML文件 QXmlStreamReader

如何使用QXmlStreamReader来解析格式良好的XML&#xff0c;Qt的文档中指出&#xff0c;它是一种更快、更方便的Qt自己的SAX解析器&#xff08;QXmlSimpleReader&#xff09;的替代&#xff0c;它也较快&#xff0c;在某种情况下&#xff0c;比DOM&#xff08;QDomDocument&…

原生小案例:如何使用HTML5 Canvas构建画板应用程序

使用HTML5 Canvas构建绘图应用是在Web浏览器中创建交互式和动态绘图体验的绝佳方式。HTML5 Canvas元素提供了一个绘图表面&#xff0c;允许您操作像素并以编程方式创建各种形状和图形。本文将为您提供使用HTML5 Canvas创建绘图应用的概述和指导。此外&#xff0c;它还将通过解释…

密码学与加密通信: 解析密码学基础、加密算法、数字签名和安全通信协议,探讨保护数据传输的技术。

在数字化时代&#xff0c;数据的安全性和隐私保护变得至关重要。随着互联网的普及&#xff0c;人们的个人信息、商业机密以及敏感数据需要在网络传输中得到保护&#xff0c;这就是密码学及其在加密通信中的作用所在。本文将深入探讨密码学的基础知识、常见的加密算法、数字签名…

公网中Linux系统下Redis使用注意事项以及被pnscan病毒攻击的经过

一次惨痛的教训&#xff1a;被pnscan病毒攻击的经过&#xff08;公网中Linux系统下Redis使用注意事项&#xff09; 0.案发情况pnscan病毒感染惨状&#xff1a;>>提示<< 1.案发原因2.排查过程简单排查之后&#xff0c;发现啥都做不了。先百度到了如下文章&#xff1…

8086汇编test指令学习

Test指令将两个操作数进行逻辑与运算&#xff0c;并根据运算结果设置相关的标志位。Test的两个操作数不会被改变。运算结果在设置过相关标记位后会被丢弃。 TEST AX,BX 与 AND AX,BX 命令有相同效果&#xff0c;只是Test指令不改变AX和BX的内容&#xff0c;而AND指令会把结果保…

HTML番外篇(五)-移动端适配

一、媒体查询 1.认识媒体查询 媒体查询是一种提供给开发者针对不同设备需求进行定制化开发的一个接口。 你可以根据设备的类型&#xff08;比如屏幕设备、打印机设备&#xff09;或者特定的特性(比如屏幕的宽度)来修改你的页面。 媒体查询的使用方式主要有三种&#xff1a;…

CPU、MCU、MPU、SOC、SOCPC、概念解释之在嵌入式领域常听到的名词含义

CPU、MCU、MPU、SOC等几个在嵌入式领域学习过程中会涉及到的几个名词。我们来学习一下&#xff0c;资料从网上搜集的&#xff0c;有错的地方可以指出。。。 CPU、MCU、MPU、SOC、SOCPC、 1. CPU2. MPU3.MCUMPU和MCU的区别&#xff1a;4.SOC5. SoPC 1. CPU CPU&#xff0c;即中…

行业追踪,2023-08-24

自动复盘 2023-08-24 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

mysql--数据库的操作

数据库&#xff0c;是数据存储的最大单元。 1 创建数据库 create database mydatabase; 每次创建数据库的时候&#xff0c;都会多一个文件夹&#xff0c;关系型数据库是存储在磁盘当中的&#xff0c;所以这时候可以查看新建的数据库 2 指定字符集 MySQL中的字符集转换过程 制…

浅谈Python网络爬虫应对反爬虫的技术对抗

在当今信息时代&#xff0c;数据是非常宝贵的资源。而作为一名专业的 Python 网络爬虫程序猿&#xff0c;在进行网页数据采集时经常会遭遇到各种针对爬虫行为的阻碍和限制&#xff0c;这就需要我们掌握一些应对反爬机制的技术手段。本文将从不同层面介绍如何使用 Python 进行网…

【面试】线上 CPU 100% 问题排查

回答套路一般为&#xff1a;线上服务器没有排查过&#xff0c;线上服务器只有运维才有操作权限。在平时开发的时候&#xff0c;在测试服务器上排查过。 一、复现代码 public class Test {public static void main( String[] args ){int a 0;while (a < 100) {a * 10;}} }…

直流电机(2)

励磁损耗不大&#xff0c; 空载磁场是平顶波。 电枢磁势电枢电流*匝数 电刷不在几何中心线上时&#xff0c;分两个分量&#xff0c; 电枢反应具有一定的饱和去磁作用。 发电机 &#xff1a;输出电势&#xff0c; 电动机:输出转矩 电势和转矩是最重要的! 磁通越大&#xff0c…

C++快速回顾(三)

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。C/C是音视频必…

猜数游戏-Rust版

cargo new guessing_game 创建项目 输入任意内容&#xff0c;并打印出来 main.rs: use std::io; // 像String这些类型都在预先导入的prelude里&#xff0c;如果要使用的不在prelude里&#xff0c;则需要显式导入fn main() { println!("猜数"); println!("…

uniap记录一场问题

终于解决了一个问题 立即报名跳转到报名详情后&#xff0c;输入的信息可以跳转回来同时在首页显示&#xff0c;不断的增添。 中间卡在数据传输卡了很久。 问题一、处在tartab的传值不允许&#xff0c;onload不在非tabbar界面跳转tabbar界面的时候重新加载&#xff0c;搞了半天…

pyqt Pyton VTK 使用 滑块 改变 VTK Actor 颜色

使用 PyQt5 vtk vtk球体 使用滑块 RGB 改变 Actor 颜色 CODE import sys from PyQt5.QtWidgets import * from PyQt5.QtWidgets import (QApplication, QCheckBox, QGridLayout, QGroupBox,QMenu, QPushButton, QRadioButton, QVBoxLayout, QWidget, QSlider,QLineEdit,QLabe…

变压器绝缘油微量水分测试

试验目的 变压器注油前做绝缘油的含水量测试&#xff0c;是为了防止水分随油进入变压器内&#xff0c;导致油一一纸绝缘系统吸潮&#xff0c;使绝缘材料降解老化、介质损耗增加、绝缘电阻降低、局部起始电压下降&#xff0c;严重影响变压器运行寿命。纯净干燥的绝缘油很容易吸…

MySQL回表是什么?哪些情况下会回表

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责…

elment-ui中使用el-steps案例

el-steps案例 样式 代码 <div class"active-box"><div class"active-title">请完善</div><el-steps :active"active" finish-status"success" align-center><el-step title"第一步" /><…