网络通信协议

WebSocket通信

  1. WebSocket是一种基于TCP的网络通信协议,提供了浏览器和服务器之间的全双工通信(full-duplex)能力。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。这使得数据可以更快地从服务器传到浏览器,而且减少了数据传输的数据量,因为头信息比较小。在WebSocket API中,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
  2. HTTP协议和WebSocket协议的主要区别如下:
    1. 连接方式:HTTP协议是无短连接的。每次请求都需要建立新的连接,请求结束后连接就断开。而WebSocket协议是长连接的,客户端和服务器建立连接后,直到其中一方主动断开,连接才会断开。
    2. 数据传输:HTTP协议只能由客户端向服务器发起请求,服务器返回响应数据。而WebSocket协议是全双工通信,服务器和客户端都可以主动发送数据。
    3. 性能开销:由于HTTP协议每次请求都需要建立新的连接,所以开销较大。而WebSocket协议建立连接后,可以进行多次数据传输,开销较小。
    4. 数据格式:HTTP协议传输的数据格式比较复杂,包含了请求行、请求头、消息体等。而WebSocket协议传输的数据通过帧来传输,数据格式比较简单。
    5. 实时性:HTTP协议的实时性不强,需要客户端定时轮询服务器获取新的数据。而WebSocket协议可以实现服务器主动推送数据,实时性较强。
  3. WebSocket主要适用于以下几种场景:
    1. 实时应用:聊天应用、多人协作应用、在线游戏、实时购物等。
    2. 实时数据推送:股票、新闻、天气、设备状态等实时信息的推送。
    3. IOT物联网:实时获取设备状态,实时控制设备等。
    4. 实时分析:实时数据分析、实时监控系统等。

WebSocket入门案例

  1. 客户端:

    1. 创建WebSocket对象:在JavaScript中,我们可以创建一个WebSocket对象,指定要连接的服务器地址。
    var ws = new WebSocket("ws://localhost:8080/websocket");
    
    1. 监听事件:WebSocket对象提供了四个事件:onopen、onmessage、onerror、onclose,我们可以通过监听这些事件来处理WebSocket的各种情况。
    ws.onopen = function(event) {
      console.log("Connection open ..."); 
    };
    
    ws.onmessage = function(event) {
      console.log("Received Message: " + event.data);
    };
    
    ws.onclose = function(event) {
      console.log("Connection closed ..."); 
    };
    
    ws.onerror = function(event) {
      console.log("Error: " + event.data);
    };
    
    1. 发送数据:WebSocket对象提供了一个send方法,我们可以通过这个方法向服务器发送数据。
    ws.send("Hello Server!");
    
    1. 关闭连接:当我们不再需要WebSocket连接时,可以调用WebSocket对象的close方法来关闭连接。
    ws.close();
    

    可以直接使用js写个小页面

    <!DOCTYPE HTML>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>WebSocket Demo</title>
    </head>
    <body>
        <input id="text" type="text" />
        <button onclick="send()">Send Message</button>
        <button onclick="closeWebSocket()">Close</button>
        <div id="message">
        </div>
    </body>
    <script type="text/javascript">
        var websocket = null;
        var clientId = Math.random().toString(36).substr(2);
    
        //判断当前浏览器是否支持WebSocket
        if('WebSocket' in window){
            //连接WebSocket节点
            websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
        }
        else{
            alert('Not support websocket')
        }
    
        //连接发生错误的回调方法
        websocket.onerror = function(){
            setMessageInnerHTML("error");
        };
    
        //连接成功建立的回调方法
        websocket.onopen = function(){
            setMessageInnerHTML("连接成功");
        }
    
        //接收到消息的回调方法
        websocket.onmessage = function(event){
            setMessageInnerHTML(event.data);
        }
    
        //连接关闭的回调方法
        websocket.onclose = function(){
            setMessageInnerHTML("close");
        }
    
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function(){
            websocket.close();
        }
    
        //将消息显示在网页上
        function setMessageInnerHTML(innerHTML){
            document.getElementById('message').innerHTML += innerHTML + '<br/>';
        }
    
        //发送消息
        function send(){
            var message = document.getElementById('text').value;
            websocket.send(message);
        }
    	
    	//关闭连接
        function closeWebSocket() {
            websocket.close();
        }
    </script>
    </html>
    
    
  2. 服务端:

    1. 导入WebSocket的maven坐标

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>
      
    2. 导入WebSocket服务端组件WebSocketServer,用于与客户端通信

      package com.sky.websocket;
      
      import com.sky.handler.TurnoverReportVOEncoder;
      import org.springframework.stereotype.Component;
      import javax.websocket.OnClose;
      import javax.websocket.OnMessage;
      import javax.websocket.OnOpen;
      import javax.websocket.Session;
      import javax.websocket.server.PathParam;
      import javax.websocket.server.ServerEndpoint;
      import java.util.Collection;
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * WebSocket服务
       */
      @Component
      @ServerEndpoint(value = "/ws/{sid}",encoders = {TurnoverReportVOEncoder.class}) // 为对象指定编码器(目前是转成json发送给客户端)
      public class WebSocketServer {
      
          //存放会话对象
          private static Map<String, Session> sessionMap = new HashMap();
      
          /**
           * 连接建立成功调用的方法
           */
          @OnOpen
          public void onOpen(Session session, @PathParam("sid") String sid) {
              System.out.println("客户端:" + sid + "建立连接");
              sessionMap.put(sid, session);
          }
      
          /**
           * 收到客户端消息后调用的方法
           *
           * @param message 客户端发送过来的消息
           */
          @OnMessage
          public void onMessage(String message, @PathParam("sid") String sid) {
              System.out.println("收到来自客户端:" + sid + "的信息:" + message);
          }
      
          /**
           * 连接关闭调用的方法
           *
           * @param sid
           */
          @OnClose
          public void onClose(@PathParam("sid") String sid) {
              System.out.println("连接断开:" + sid);
              sessionMap.remove(sid);
          }
      
          /**
           * 群发
           *
           * @param message
           */
          public void sendToAllClient(String message) {
              Collection<Session> sessions = sessionMap.values();
              for (Session session : sessions) {
                  try {
                      //服务器向客户端发送消息
                      session.getBasicRemote().sendText(message);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public void sendObjToAllClient(Object object) {
              Collection<Session> sessions = sessionMap.values();
              for (Session session : sessions) {
                  try {
                      //服务器向客户端发送对象--注意第4步骤,需要为该对象指定一个编码器
                      session.getBasicRemote().sendObject(object);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      
      }
      
      
    3. 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件ServerEndpointExporter

      package com.sky.config;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.socket.server.standard.ServerEndpointExporter;
      
      /**
       * WebSocket配置类,用于注册WebSocket的Bean
       */
      @Configuration
      public class WebSocketConfiguration {
          @Bean
          public ServerEndpointExporter serverEndpointExporter() {
              return new ServerEndpointExporter();
          }
      }
      
    4. 如果想向客户端推送封装好的对象,在WebSocket中,需要提供一个编码器来将这个对象转换为可以通过网络传输的格式,通常是字符串或者二进制数据。

      package com.sky.handler;
      
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.sky.vo.TurnoverReportVO;
      
      import javax.websocket.EncodeException;
      import javax.websocket.Encoder;
      import javax.websocket.EndpointConfig;
      
      /**
       * @projectName: sky-take-out
       * @package: com.sky.handler
       * @className: TurnoverReportVOEncoder
       * @author: fangjiayueyuan
       * @description: TODO
       * @date: 2023/12/24 16:16
       * @version: 1.0
       */
      public class TurnoverReportVOEncoder implements Encoder.Text<TurnoverReportVO>{
          private static ObjectMapper objectMapper = new ObjectMapper();
      
          @Override
          public String encode(TurnoverReportVO turnoverReportVO) throws EncodeException {
              try {
                  // 使用Jackson库将对象转换为JSON字符串
                  return objectMapper.writeValueAsString(turnoverReportVO);
              } catch (Exception e) {
                  throw new EncodeException(turnoverReportVO, "对象转换为JSON字符串时发生错误", e);
              }
          }
      
          @Override
          public void init(EndpointConfig endpointConfig) {
              // 这里可以进行编码器的初始化操作,但在这个例子中我们不需要进行任何操作
          }
      
          @Override
          public void destroy() {
              // 这里可以进行编码器的清理操作,但在这个例子中我们不需要进行任何操作
          }
      }
      
      
    5. 导入定时任务类WebSocketTask,定时向客户端推送数据

      package com.sky.task;
      
      import com.sky.service.ReportService;
      import com.sky.vo.TurnoverReportVO;
      import com.sky.websocket.WebSocketServer;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.scheduling.annotation.Scheduled;
      import org.springframework.stereotype.Component;
      
      import java.time.LocalDate;
      import java.time.LocalDateTime;
      import java.time.format.DateTimeFormatter;
      
      @Component
      public class WebSocketTask {
          @Autowired
          private WebSocketServer webSocketServer;
      
          @Autowired
          private ReportService reportService;
      
          /**
           * 通过WebSocket每隔5秒向客户端发送消息
           */
          @Scheduled(cron = "0/5 * * * * ?")
          public void sendMessageToClient() {
              webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
          }
      
          /**
           * 通过WebSocket每隔5秒向客户端发送消息
           */
          @Scheduled(cron = "0/5 * * * * ?")
          public void sendObjMessageToClient() {
              TurnoverReportVO turnoverStatistics = reportService.getTurnoverStatistics(LocalDate.parse("2023-01-01"), LocalDate.now());
              webSocketServer.sendToAllClient("传个对象过去");
              webSocketServer.sendObjToAllClient(turnoverStatistics);
          }
      }
      
      

RPC通信

**RPC(Remote Procedure Call)**是一种通信协议,它允许运行在一台计算机上的程序调用另一台计算机上的程序中的函数或方法,就像调用本地函数一样,无需程序员显式处理底层的网络细节。

RPC的主要特征包括:

  1. 透明性:对于调用者来说,远程过程调用和本地过程调用是透明的,调用者无需关心过程调用的是本地过程还是远程过程。

  2. 语言无关性:RPC通常支持多种编程语言,只要两个通信的程序遵循同一RPC协议,它们就可以进行通信,无论它们是用什么编程语言编写的。

  3. 同步性:RPC通常是同步的,也就是说,当一个RPC调用发出后,调用者会停止执行,直到得到结果。然而,也有一些RPC系统支持异步调用。

为什么使用RPC

  1. 简化分布式系统的开发:RPC隐藏了底层的网络通信和数据传输的复杂性,使得开发分布式应用更加简单。

  2. 提高代码的可重用性:通过RPC,可以将一些通用的功能实现为服务,然后在多个应用中重用这些服务。

  3. 提高系统的可扩展性:通过RPC,可以将一个大的系统分解为多个可以独立开发和部署的小的服务。

RPC的替代方案

  1. RESTful API:RESTful API是一种基于HTTP协议的通信方式,它使用HTTP的方法(如GET、POST、PUT、DELETE等)来操作资源。RESTful API比RPC更简单,更易于使用,但它不如RPC灵活,因为它只能使用HTTP协议,而RPC可以使用任何传输协议。
  2. 消息队列:消息队列是一种异步的通信方式,它允许程序通过发送和接收消息来进行通信。消息队列可以解耦发送者和接收者,使得它们可以独立地扩展和失败。然而,消息队列的使用比RPC更复杂,因为它需要处理消息的发送、接收、存储和确认。

RPC入门案例

以Thrift为例:

  1. 定义数据类型和服务接口:使用Thrift的IDL(接口定义语言)定义数据类型和服务接口,然后通过Thrift的编译器生成对应语言的代码。

    namespace java com.sankuai.mdp.thrift
    
    struct User{
        1:i32 id
        2:string name
        3:i32 age=0
    }
    
    service UserService{
        User getById(1:i32 id)
        bool isExist(1:string name)
    }
    
  2. 通过Thrift编译器生成Java代码:会生成两个对象:User、UserService

    thrift --gen java HelloWorld.thrift
    
  3. 服务端代码,实现UserService.Iface接口;启动服务端.

    package com.sankuai.mdp.thriftserversnapshot.service.impl;
    
    import com.sankuai.mdp.thriftapisnapshot.entity.User;
    import com.sankuai.mdp.thriftapisnapshot.entity.UserService;
    import org.apache.thrift.TException;
    
    /**
     * @projectName: thrift-api-snapshot
     * @package: com.sankuai.mdp.thriftserversnapshot.service.impl
     * @className: UserServiceImpl
     * @author: fangjiayueyuan
     * @description: TODO
     * @date: 2023/12/17 21:33
     * @version: 1.0
     */
    public class UserServiceImpl implements UserService.Iface{
        @Override
        public User getById(int id) throws TException {
            System.out.println("-----调用getById-----");
            User user = new User();
            user.setId(id);
            user.setName("dog");
            user.setAge(18);
            return user;
        }
    
        @Override
        public boolean isExist(String name) throws TException {
            return false;
        }
    }
    
    
    package com.sankuai.mdp.thriftserversnapshot.service.impl;
    
    import com.sankuai.mdp.thriftapisnapshot.entity.UserService;
    import org.apache.thrift.protocol.TBinaryProtocol;
    import org.apache.thrift.server.TServer;
    import org.apache.thrift.server.TSimpleServer;
    import org.apache.thrift.transport.TServerSocket;
    import org.apache.thrift.transport.TServerTransport;
    import org.apache.thrift.transport.TTransportException;
    
    /**
     * @projectName: thrift-api-snapshot
     * @package: com.sankuai.mdp.thriftserversnapshot.service.impl
     * @className: SimpleService
     * @author: fangjiayueyuan
     * @description: TODO
     * @date: 2023/12/17 21:59
     * @version: 1.0
     */
    public class SimpleService {
        public static void main(String[] args) {
            try{
                TServerTransport serverTransport = new TServerSocket(9090);
                UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
                TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
                TSimpleServer.Args targs = new TSimpleServer.Args(serverTransport);
                targs.processor(processor);
                targs.protocolFactory(protocolFactory);
                TServer server = new TSimpleServer(targs);
                server.serve();
            } catch (TTransportException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  4. 客户端代码,调用服务端的方法,就像调用本地方法一样

    package com.sankuai.mdp.thriftclientsnapshot.service.impl;
    
    import com.sankuai.mdp.thriftapisnapshot.entity.User;
    import com.sankuai.mdp.thriftapisnapshot.entity.UserService;
    import org.apache.thrift.TException;
    import org.apache.thrift.protocol.TBinaryProtocol;
    import org.apache.thrift.transport.TSocket;
    import org.apache.thrift.transport.TTransport;
    import org.apache.thrift.transport.TTransportException;
    
    /**
     * @projectName: thrift-api-snapshot
     * @package: com.sankuai.mdp.thriftclientsnapshot.service.impl
     * @className: SimpleClient
     * @author: fangjiayueyuan
     * @description: TODO
     * @date: 2023/12/17 21:58
     * @version: 1.0
     */
    public class SimpleClient {
        public static void main(String[] args) {
            TTransport transport = null;
            try {
                transport = new TSocket("localhost", 9090);
                TBinaryProtocol protocol = new TBinaryProtocol(transport);
                UserService.Client client = new UserService.Client(protocol);
                transport.open();
                User result = client.getById(1);
                System.out.println("Result:" + result);
            } catch (TTransportException e) {
                e.printStackTrace();
            } catch (TException e) {
                throw new RuntimeException(e);
            } finally {
                if (transport != null) {
                    transport.close();
                }
            }
        }
    }
    
    
  5. 先后启动运行服务端SimpleService.java、客户端代码SimpleClient.java
    在这里插入图片描述
    在这里插入图片描述
    Git

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

定制TikTok引流脚本必备功能!

在TikTok的海洋中&#xff0c;如何让你的品牌或产品脱颖而出?除了内容创新&#xff0c;一个高效的TikTok引流脚本也是关键&#xff0c;本文将为你揭示定制TikTok引流脚本必备的四大功能&#xff0c;助你在这场流量大战中占得先机。 一、消息多发 在TikTok上&#xff0c;消息…

本地部署Jellyfin影音服务器并实现远程访问内网影音库

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

鸿蒙的基本入门理解

一、鸿蒙工具的安装&#xff1a; 1、安装&#xff1a;官网 按照官网的步骤&#xff0c;按照好后&#xff0c;可以直接使用previewer预览就可以了【刚入门&#xff0c;不建议大家搞得太多&#xff0c;容易晕】。 如果预览不了&#xff0c;再安装模拟器 2、新建项目&#xff…

企业门户平台全功能解析:从界面到集成,一站式管理与整合

引言 在当今信息时代&#xff0c;企业门户平台作为企业信息化的重要支柱&#xff0c;扮演着连接各项业务、整合数据、提升工作效率的关键角色。它不仅是一个信息集成的平台&#xff0c;更是促进团队协作、提高工作效率的利器。本文将探讨企业门户平台在信息整合和工作效率方面…

故障管理过程

故障管理 故障管理在故障生命周期中的位置 分维度统计分析规律&#xff0c;形成系统化的改进方向跟进每个case的改进方案&#xff0c;彻底消除隐患前事不忘后事之师&#xff0c;供后续参考 故障定级 事故级别服务级别一般事故严重事故重大事故特大事故对外完全停止服务时间一…

大模型工具:LangChain 原理与实战案例

LangChain 是什么&#xff1f; LangChain是一个用于开发由语言模型驱动的应用程序的框架。它使得可以构建以下类型的应用程序&#xff1a; 数据感知&#xff1a;将语言模型与其他数据源连接起来 智能&#xff1a;允许语言模型与其环境进行交互 LangChain的主要价值在于&…

亚信安慧AntDB数据库携手U8C共创未来

AntDB数据库生态负责人在近期举行的商业创新大会上引领着数字化时代的浪潮&#xff0c;推出了令业界瞩目的U8CAntDB联合产品。这一创新性的合作将AntDB数据库与U8C云ERP产品紧密结合&#xff0c;为成长型企业提供了一套全栈、安全可靠的保障&#xff0c;为企业的数智化转型升级…

新/旧版本 QT 下载,全攻略【省资源下载币专用】

看到好多朋友找不到指定版本的QT下载路径&#xff0c;特此更新一篇新/旧版本 QT 下载攻略 收藏一下吧&#xff0c;需要的时候方便查找&#xff0c;能为你省下好多资源下载币。 通过图示可以看出&#xff0c;新旧版本的界限并没有那么明晰&#xff0c;如果你需要的版本两个链接…

docker-compose 安装Sonar并集成gitlab

文章目录 1. 前置条件2. 编写docker-compose-sonar.yml文件3. 集成 gitlab4. Sonar Login with GitLab 1. 前置条件 安装docker-compose 安装docker 创建容器运行的特有网络 创建挂载目录 2. 编写docker-compose-sonar.yml文件 version: "3" services:sonar-postgre…

内网MSF--从入门到熟练

Metasploit就是一个漏洞框架。它的全称叫做The Metasploit Framework&#xff0c;简称叫做MSF。Metasploit 作为全球最受欢迎的工具&#xff0c;不仅仅是因为它的方便性和强大性&#xff0c;更重要的是它的框架。它允许使用者开 发自己的漏洞脚本&#xff0c;从而进行测试。 一…

python实现多层级复选框选中

pythonpyqt5实现多层级复选框选中 效果如何插入一段漂亮的代码片 效果 如何插入一段漂亮的代码片 去博客设置页面&#xff0c;选择一款你喜欢的代码片高亮样式&#xff0c;下面展示同样高亮的 代码片. // An highlighted block class filterWindow(QWidget):def __init__(sel…

路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)

路径规划综述 1. 背景介绍 路径规划是指在给定的环境中找到从起点到终点的最佳路径的过程。它在现实生活中有着广泛的应用&#xff0c;包括无人驾驶、物流配送、机器人导航等领域。随着人工智能和计算机技术的发展&#xff0c;路径规划技术也在不断地得到改进和应用。 路径规划…

【hacker送书第11期】Python数据分析从入门到精通

探索数据世界&#xff0c;揭示未来趋势 《Python数据分析从入门到精通》是你掌握Python数据分析的理想选择。本书深入讲解核心工具如pandas、matplotlib和numpy&#xff0c;助您轻松处理和理解复杂数据。 通过matplotlib、seaborn和创新的pyecharts&#xff0c;本书呈现生动直…

web前端游戏项目-辨色大比拼【附源码】

web前端游戏项目-辨色大比拼【附源码】 《辨色大比拼》是一个旨在测试和提升玩家颜色识别能力的在线游戏。在游戏中&#xff0c;玩家将通过辨识颜色来解谜并推进游戏进程。辨色大比拼也是一个寓教于乐的游戏&#xff0c;它不仅提供了一个有趣的辨色挑战&#xff0c;还能帮助玩…

[C/C++]数据结构: 链式二叉树的构建及遍历

一: &#x1f4ac;二叉树的概念 1.1:&#x1f6a9; 概念 二叉树是指树中节点的度不大于2的有序树,它是一种最简单且重要的树,二叉树的递归定义为:二叉树是一颗空树,或者是一颗由一个根节点和两颗互不相交的,分别称为跟的左孩子和右孩子树组成的非空树,其中左子树和右子树都是二…

Linux部署MeterSphere结合内网穿透实现远程访问服务管理界面

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

【网络编程】基于UDP数据报实现回显服务器程序

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 前言 我们如果…

Python基础入门第六节课笔记

while循环 for循环用于针对序列中的每个元素的一个代码块。 while循环是不断的运行&#xff0c;直到指定的条件不满足为止。 while 条件&#xff1a; 条件成立重复执行的代码1 条件成立重复执行的代码2 …….. 当条件成立时&#xff0c;执行下方缩…

【JavaEE初阶一】线程的概念与简单创建

1. 认识线程&#xff08;Thread&#xff09; 1.1 关于线程 1.1.1 线程是什么 由前一节的内容可知&#xff0c;进程在进行频繁的创建和销毁的时候&#xff0c;开销比较大&#xff08;主要体现在资源的申请和释放上&#xff09;&#xff0c;线程就是为了解决上述产生的问题而提…

软件测试 —— 如何测试图片上传功能?

作为一名专业的软件测试人员&#xff0c;测试图片上传功能是一个重要的任务&#xff0c;以下是一些测试该功能的常用方法&#xff1a; 1. 上传功能测试&#xff1a;确保图片上传功能正常工作&#xff0c;包括选择图片文件、点击上传按钮、上传进度显示、上传成功/失败的提示等。…