websocket实现

由于安卓资源管理器展示的路径不尽相同,各种软件保存文件的位置也不一定一样.对于普通用户上传文件时,查找文件可能是一个麻烦的事情.后来想到了一个办法,使用pc端进行辅助上传.

文章目录

  • 实现思路
  • 1.0 实现
    • 定义web与客户端通信数据类型和数据格式
    • web端websocket实现
      • web端对客户端数据的管理
    • pc端实现
    • OkHttp3建立websocket连接
  • 2.0版本
    • spring-boot放到nginx后面
    • spring-boot 放到gateway后面
    • spring-boot 放到nginx gateway后面
    • ws升级为wss
  • 其他
    • springboot打包

实现思路

  1. pc端与服务器建立websocket连接;
  2. 服务器将sessionId传递到pc端;
  3. pc端生成二维码;
  4. 手机端扫描二维码,读取pc端sessionId;
  5. 手机端与服务器建立websocket连接;
  6. 手机端将fileId(后面再解释)、pc端sessionId、token等参数传递给服务器;
  7. 服务器更新pc端session 对应的fileId;
  8. 服务器将fileId、token等发送到pc端;
  9. pc使用token、fileId等请求文件列表并进行展示;
  10. 手机端、pc端进行文件修改后,向服务器发送给更新信号,服务器将更新信号转发到对端。

1.0 实现

定义web与客户端通信数据类型和数据格式

  1. 定义web与客户端通信数据类型
public class MsgType {
    public static final int UPDATE = 0; //提示客户端数据发生更新
    public static final int REQ = 1; //发送/接受fileId等字段
    public static final int SELF = 3; //建立连接后,web端发送client其sessionId
    public static final int ERR_SESSION = 4; //提示session不存在或已close 
    public static final int HEART_BEAT = 100; //心跳包
}
  1. 定义web与客户端通信数据格式
@Data
public class MsgData {
    private int type;  //对应 MsgType
    private String sessionId; //SELF 对应自身sessionId; REQ 对应pc端sessionId;
    private String fileId; //建立连接后,向pc端发送fileId等字段

web端websocket实现

创建spring-boot项目,添加web\websocket相关依赖

使用maven引入websocket依赖;

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

配置websocket和访问路径的映射关系

@Configuration   //配置websocket和访问路径的映射关系
@EnableWebSocket // 全局开启WebSocket支持
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new WebSocketServer(), "/websocket").setAllowedOrigins("*");
    }
}

web端对客户端数据的管理

  1. 定义web管理session的数据结构
@Data
public class SessionData {
    private int sessionType; // 1 master(app). 0 pc
    private String fileId; //pc会话ID
    private WebSocketSession session;
    private String sessionId;
    //虽然可以通过session.getId()获取到sessionId,但session关闭后,读取就会报错

web端对session的管理逻辑

  1. 新创建的连接添加到链表上,web向客户端发送SELF,告知其对应的sessionId;

  2. 断开连接时,如果是pc端session直接从链表中删除,如果是app端session,将其他相同fileId的session全部关闭并从链表删除;

  3. 接收到新消息后,根据消息类型进行分类处理:

    1. 心跳包,则直接返回;
    2. REQ app发送的fileId\pc端sessionId等字段,修改sessions上app连接和pc端SessionData内的fileId字段;
      并将fileId等字段发送给pc端;
    3. UPDATE 给所有相同fileId的session发送更新信号;

注意: sessions遍历\删除\添加必须添加synchronized,否则ConcurrentModificationException

package com.example.im.ws;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @ClassName WebSocketServer
 * @Description 处理websocket 连接
 * @Author guchuanhang
 * @date 2025/1/25 14:01
 * @Version 1.0
 **/

@Slf4j
public class WebSocketServer extends TextWebSocketHandler {

    private final Object syncObject = new Object();
    private final List<SessionData> sessions =
            new ArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("New connection established: " + session.getId());
        SessionData sessionData = new SessionData(session);
        synchronized (syncObject) {
            sessions.add(sessionData);
        }
        MsgData msgData = new MsgData();
        msgData.setType(MsgType.SELF);
        msgData.setSessionId(session.getId());
        session.sendMessage(new TextMessage(new Gson().toJson(msgData)));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message)
            throws Exception {
        String payload = message.getPayload();
        log.info("handleTextMessage: " + session.getId());
        log.info("Received message: " + payload);
        final MsgData msgData = new Gson().fromJson(payload, MsgData.class);
        //master 发来的需求.
        switch (msgData.getType()) {

            case MsgType.HEART_BEAT: {
                //heart beat
                break;
            }

            case MsgType.REQ: {
                //set master
                {
                    SessionData sessionData = null;
                    synchronized (syncObject) {
                        final Optional<SessionData> any = sessions.stream().
                                filter(s -> s.getSessionId()
                                        .equals(session.getId())).findAny();
                        if (any.isPresent()) {
                            sessionData = any.get();
                        }
                    }
                    if (null != sessionData) {
                        //set master.
                        sessionData.setSessionType(ClientType.MASTER);
                        sessionData.setFileId(msgData.getFileId());
                    }
                }
                //set slave
                {

                    SessionData sessionData = null;
                    synchronized (syncObject) {
                        final Optional<SessionData> any = sessions.stream().
                                filter(s -> s.getSessionId().equals(msgData.getSessionId())).findAny();
                        if (any.isPresent()) {
                            sessionData = any.get();
                        }
                    }

                    if (null != sessionData) {
                        sessionData.setSessionType(ClientType.SALVER);
                        sessionData.setFileId(msgData.getFileId());

                        MsgData msgData1 = new MsgData();
                        msgData1.setType(MsgType.REQ);
                        msgData1.setFileId(msgData.getFileId());
                        sessionData.getSession().sendMessage(new TextMessage(new Gson().toJson(msgData1)));
                    } else {
                        //pc session error.
                        MsgData msgData1 = new MsgData();
                        msgData1.setType(MsgType.ERR_SESSION);
                        session.sendMessage(new TextMessage(new Gson().toJson(msgData1)));
                    }
                }
                break;
            }
            case MsgType.UPDATE: {
                //slf

                SessionData sessionData = null;
                synchronized (syncObject) {
                    final Optional<SessionData> any = sessions.stream().
                            filter(s -> s.getSessionId().equals(session.getId())).findAny();
                    if (any.isPresent()) {
                        sessionData = any.get();
                    }
                }
                if (null != sessionData) {
                    final String fileId = sessionData.getFileId();
                    List<SessionData> collect;
                    synchronized (syncObject) {
                        collect =
                                sessions.stream().filter(s -> (null != s.getFileId() && s.getFileId().
                                        equals(fileId)) || (null == s.getSession() || !s.getSession().isOpen())).collect(Collectors.toList());
                    }
                    if (collect.isEmpty()) {
                        return;
                    }
                    List<SessionData> errList = new ArrayList<>();
                    for (SessionData s : collect) {
                        if (null == s.getSession() || !s.getSession().isOpen()) {
                            errList.add(s);
                            continue;
                        }
                        //不需要给自己发送了
                        if (s.getSessionId().equals(session.getId())) {
                            continue;
                        }

                        MsgData msgData1 = new MsgData();
                        msgData1.setType(MsgType.UPDATE);
                        try {
                            s.getSession().sendMessage(new TextMessage(new Gson().toJson(msgData1)));
                        } catch (Exception e) {
                            e.printStackTrace();
                            errList.add(s);
                        }
                    }
                    synchronized (syncObject) {
                        sessions.removeAll(errList);
                    }

                }
                break;
            }
        }

    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("Connection closed: " + session.getId() + ", Status: " + status);
        SessionData sessionData = null;
        synchronized (syncObject) {
            Optional<SessionData> any = sessions.stream().
                    filter(s -> s.getSessionId().equals(session.getId())).findAny();
            if (any.isPresent()) {
                sessionData = any.get();
            }
        }
        if (null == sessionData) {
            return;
        }
        final String fileId = sessionData.getFileId();
        //slave just ignore and delete.
        if (ClientType.SALVER == sessionData.getSessionType()) {
            sessions.remove(sessionData);
            return;
        }
        if (ClientType.MASTER == sessionData.getSessionType()) {
            List<SessionData> collect;
            synchronized (syncObject) {
                collect =
                        sessions.stream().filter(s ->
                                (null != s.getFileId()
                                        && s.getFileId().equals(fileId)) ||
                                        (null == s.getSession() || !s.getSession().isOpen())).collect(Collectors.toList());
            }
            if (collect.isEmpty()) {
                return;
            }
            for (SessionData s : collect) {
                final WebSocketSession session1 = s.getSession();
                if (null == session1 || !session1.isOpen()) {
                    continue;
                }
                session1.close();
            }
            synchronized (syncObject) {
                sessions.removeAll(collect);
            }
        }
    }
}

pc端实现

  1. 页面创建时创建websocket,销毁时关闭websocket
  2. 根据和服务器约定的消息格式 在websocket回调函数onmessage接受数据类型进行二维码生成\文件列表查询等操作
  3. 添加心跳机制,让websocket更健壮

fileId是一个key,通过fileId可以查询最新的数据. pc端接受到刷新信号后,请求获取最新数据; pc端更新数据后,发送数据已更新信号.

<template>

  <div v-if="fileId">
    <div>{{ fileId }}</div>
    <el-button @click="updateData" type="primary">更新数据</el-button>
    <div>发送给服务端更新信号时间: {{ sndUpdateSignalTime }}</div>
    <div>收到服务端更新信号时间: {{ rcvUpdateSignalTime }}</div>
    <div>心跳最新时间: {{ heartBeatSignalTime }}</div>
    <div>服务器返回最新内容: {{ serverContent }}</div>
  </div>

  <div v-else-if="sessionId">
    <div>sessionId:{{ sessionId }}</div>
    <img width="200px" height="200px" :src="qrCode" alt="QR Code"/>
  </div>


</template>

<script>

import QRCode from "qrcode";

export default {
  name: "HelloWorld",
  data() {
    return {
      wsuri: "ws://192.168.0.110:7890/websocket",
      ws: null,
      sessionId: '',
      qrCode: null,
      fileId: '',
      rcvUpdateSignalTime: '',
      sndUpdateSignalTime: '',
      heartBeatSignalTime: '',
      serverContent: '',
      heartbeatInterval: null,
      heartbeatIntervalTime: 3000, // 心跳间隔时间,单位为毫秒
    }
  },
  created() {
    //页面打开时,初始化WebSocket连接
    this.initWebSocket()
  },
  beforeDestroy() {
    // 页面销毁时,关闭WebSocket连接
    this.stopHeartbeat()
    this.fileId = ''
    try {
      this.ws.close()
    } catch (e) {

    }
    this.ws = null;
    this.sessionId = ''
  },
  methods: {
    // pc端更新附件数据后,向服务器端发送更新信号
    updateData() {
      console.error('snd update signal')
      this.ws.send(JSON.stringify({
        type: 0
      }))
      //格式化为  yyyy-MM-dd HH:mm:ss
      this.sndUpdateSignalTime = new Date().toLocaleTimeString()
      this.resetHeartbeat();
    },

    async generateQRCode() {
      try {
        this.qrCode = await QRCode.toDataURL(this.sessionId);
      } catch (error) {
        console.error('生成二维码时出错:', error);
      }
    },
    // 周期性发送心跳包
    startHeartbeat() {
      this.heartbeatInterval = setInterval(() => {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify({
            type: 100
          }))
          this.heartBeatSignalTime = new Date().toLocaleTimeString()
          console.error('snd heartbeat signal')
        } else {
          this.stopHeartbeat();
        }
      }, this.heartbeatIntervalTime);
    },
    //在发送或接受数据后,重置下一次发送心跳包的时间
    resetHeartbeat() {
      clearInterval(this.heartbeatInterval);
      this.startHeartbeat();
    },
    // 停止发送心跳包
    stopHeartbeat() {
      clearInterval(this.heartbeatInterval);
    },

    initWebSocket() {
      let that = this;
      this.ws = new WebSocket(this.wsuri);

      this.ws.onopen = () => {
        this.startHeartbeat();
      };
      // 接收后端消息
      this.ws.onmessage = function (event) {
        console.error('RECV:' + event.data)
        that.serverContent = event.data;
        let parse = JSON.parse(event.data);
        that.resetHeartbeat();

        switch (parse.type) {
          case 0: {
            console.error('update')
            that.rcvUpdateSignalTime = new Date().toLocaleTimeString()
            //TODO. 请求最新数据
            break;
          }
          case 1: {   //fileId list. 接受数据,进行路径跳转
            console.error('REQ:' + event.data)
            that.fileId = parse.fileId;
            //记录并请求最新数据
            break;
          }
          case 3: {
            that.sessionId = parse.sessionId;
            that.generateQRCode();
            break;
          }
        }
      };
      // 关闭连接时调用
      this.ws.onclose = function (event) {
        alert('连接已关闭');
        that.stopHeartbeat()
        // 强制刷新页面(created 会调用)
        location.reload(true)
      };
    }

  }
}
</script>

<style scoped>

</style>

OkHttp3建立websocket连接

  1. 使用okhttp3建立websocket连接,监听onMessage根据消息类型进行不同的处理;
  2. 使用handler 管理心跳包

扫码后, 如果已经建立连接了

package com.example.im.ws;


import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.example.im.R;
import com.google.gson.Gson;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

public class HelloActivity extends AppCompatActivity {
    public static final int MSG_HEART = 0x123;
    public static final int MSG_INTERVAL = 3000;
    private WebSocket webSocket;
    public static final String URL = "ws://192.168.0.110:7890/websocket";
    private TextView msgView;
    private List<String> sessionIds = new ArrayList<>();
    Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (MSG_HEART == msg.what) {
                MsgData msgData = new MsgData();
                msgData.setType(MsgType.HEART_BEAT);
                webSocket.send(new Gson().toJson(msgData));
                msgView.append(getNowDate() + ":发送消息 heart beat\n");

                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgView = findViewById(R.id.tv_msg);

        findViewById(R.id.btn_scan).setOnClickListener(v -> {
            scanQRCode();
        });
        findViewById(R.id.btn_update).setOnClickListener(v -> {
            MsgData msgData = new MsgData();
            msgData.setType(MsgType.UPDATE);
            webSocket.send(new Gson().toJson(msgData));
        });
    }

    @Override
    protected void onDestroy() {
        mHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }

    private void scanQRCode() {
        IntentIntegrator integrator = new IntentIntegrator(this);
        integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE);
        integrator.setPrompt("提示");
        integrator.setCameraId(0);  // 使用后置摄像头
        integrator.setBeepEnabled(false);
        integrator.setBarcodeImageEnabled(true);
        integrator.initiateScan();
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null && !TextUtils.isEmpty(result.getContents())) {
            String sessionId = result.getContents();
            if (sessionIds.contains(sessionId)) {
                return;
            }
            sessionIds.add(sessionId);
            //start
            if (null == webSocket) {
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder().url(URL).build();
                webSocket = client.newWebSocket(request, new MyWebSocketListener());
            } else {
                //这样可以实现扫多个pc端. 同时与多个pc端通信
                MsgData msgData = new MsgData();
                msgData.setSessionId(sessionId);
                msgData.setType(MsgType.REQ);
                msgData.setFileId("123");
                webSocket.send(new Gson().toJson(msgData));
            }

        }
        super.onActivityResult(requestCode, resultCode, data);
    }


    private String getNowDate() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
                Locale.getDefault());
        return simpleDateFormat.format(new java.util.Date());
    }

    private class MyWebSocketListener extends WebSocketListener {
        @Override
        public void onOpen(WebSocket webSocket, okhttp3.Response response) {
            // 连接成功
            msgView.append(getNowDate() + ":连接成功\n");
            MsgData msgData = new MsgData();
            msgData.setSessionId(sessionIds.get(sessionIds.size() - 1));
            msgData.setType(MsgType.REQ);
            msgData.setFileId("123");
            webSocket.send(new Gson().toJson(msgData));
            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            msgView.append(getNowDate() + ":接受消息" + text + "\n");
            mHandler.removeMessages(MSG_HEART);
            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_HEART), MSG_INTERVAL);
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, okhttp3.Response response) {
            // 连接失败
            msgView.append(getNowDate() + ":失败" + t.getMessage() + "\n");
        }
    }
}

2.0版本

上面的实现确实简单.下面结合实际的系统架构进行适配一下.

spring-boot放到nginx后面

nginx常用来进行负载均衡\防火墙\反向代理等等,这种情况比较常见.

	map $http_upgrade $connection_upgrade { 
	default upgrade; 
	'' close; 
	} 
		
    server {
        listen       7777;
        server_name  localhost;
		
		location / {
			proxy_pass http://127.0.0.1:7890;
			proxy_set_header Upgrade $http_upgrade; 
			proxy_set_header Connection $connection_upgrade; 
		}
	}
}

设置Upgrade\Connection请求头,将访问地址修改为nginx的地址,即可实现nginx代理到spring-boot.

spring-boot 放到gateway后面

也就是所谓的spring-cloud 微服务架构.

gateway添加ws协议的路由

        # IM
        - id: im
          uri: ws://localhost:7890
          predicates:
            - Path=/im/**
          filters:
            - StripPrefix=1

访问gateway代理之后的地址,即可实现nginx代理到spring-boot.

spring-boot 放到nginx gateway后面

将前面两者进行结合, nginx保证可以代理到gateway, gateway再路由到spring-boot.

ws升级为wss

网上的做法是, 给gateway\spring-boot都配置证书.

简单才能高效,既然gateway有防火墙验证证书等功能,应用不需要管理才对. nginx要屏蔽这种差异.

配置nginx 直接将wss的请求重写为ws.

nginx重写协议

map $http_upgrade $connection_upgrade { 
default upgrade; 
'' close; 
} 
server
{
	listen 443 ssl http2;
    server_name 

    #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
	ssl on;
    ssl_certificate   
    ssl_certificate_key 
    add_header Strict-Transport-Security "max-age=31536000";
    error_page 497  https://$host$request_uri;

  location /im/ { 
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection $connection_upgrade; 
  		proxy_pass http://127.0.0.1:18080/im/;
        rewrite ^(.*)wss://(.*)$ $1ws://$2 permanent;
    }
}

这样 wss://域名/im/websocket就可以进行访问了.

其他

源码下载地址: https://gitee.com/guchuanhang/imapplication.git

springboot打包

  1. 注释掉skip
<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.im.ImApplication</mainClass>
                    <!-- 注释掉,否则不能打包-->
<!--                    <skip>true</skip>-->
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
  1. springboot日志
    spring-boot 默认支持logback

@Slf4j
public class WebSocketServer extends TextWebSocketHandler {
  log.info("New connection established: " + session.getId());

  1. bootstrap.yml

bootstrap.yml 是 spring-cloud 配置文件.
application.yml applicaition.properties 是 spring-boot 的配置文件.

  1. wss测试工具 wscat
npm install -g wscat  # 安装方式

wscat -c wss://www.baidu.com/im/websocket   

图片

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

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

相关文章

(一)HTTP协议 :请求与响应

前言 爬虫需要基础知识&#xff0c;HTTP协议只是个开始&#xff0c;除此之外还有很多&#xff0c;我们慢慢来记录。 今天的HTTP协议&#xff0c;会有助于我们更好的了解网络。 一、什么是HTTP协议 &#xff08;1&#xff09;定义 HTTP&#xff08;超文本传输协议&#xff…

arcgis短整型变为长整型的处理方式

1.用QGIS的重构字段工具进行修改&#xff0c;亲测比arcgis的更改字段工具有用 2.更换低版本的arcgis10.2.2&#xff0c;亲测10.5和10.6都有这个毛病&#xff0c;虽然官方文档里面说的是10.6.1及以上 Arcgis10.2.2百度链接&#xff1a;https://pan.baidu.com/s/1HYTwgnBJsBug…

从音频到 PDF:AI 全流程打造完美英文绘本教案

今天把英文绘本的自学教案自动生成流程完成了&#xff0c;我分享一下整个实现思路&#xff0c;让你也轻松搞定英文绘本教案的产出&#xff0c;让孩子的学习之路更加顺畅。  从音频到 PDF&#xff1a;AI 全流程打造完美英文绘本教案 一、音频转文本&#xff1a;AI 助力第一步 …

C++ Qt练习项目 日期时间数据

个人学习笔记 代码仓库 GitCode - 全球开发者的开源社区,开源代码托管平台 新建项目 设计UI 实现组件功能 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }…

HarmonyOS DevEco Studio模拟器点击运行没有反应的解决方法

HarmonyOS DevEco Studio模拟器点击运行没有反应的解决方法 翻遍了CSDN&#xff0c;试了所有办法都没办法&#xff0c;最后偶然间竟然解决了 解决方法其实很简单&#xff1a;本地模拟器下载路径下面不能有中文。。。。。 切换正确路径以后&#xff0c;成功运行&#xff0c;哦…

开发环境搭建-1:配置 WSL (类 centos 的 oracle linux 官方镜像)

一些 Linux 基本概念 个人理解&#xff0c;并且为了便于理解&#xff0c;可能会存在一些问题&#xff0c;如果有根本上的错误希望大家及时指出 发行版 WSL 的系统是基于特定发行版的特定版本的 Linux 发行版 有固定组织维护的、开箱就能用的 Linux 发行版由固定的团队、社…

dm8在Linux环境安装精简步骤说明(2024年12月更新版dm8)

dm8在Linux环境安装详细步骤 - - 2025年1月之后dm8 环境介绍1 修改操作系统资源限制2 操作系统创建用户3 操作系统配置4 数据库安装5 初始化数据库6 实例参数优化7 登录数据库配置归档与备份8 配置审计9 创建用户10 屏蔽关键字与数据库兼容模式11 jdbc连接串配置12 更多达梦数据…

一文讲解Java中的重载、重写及里氏替换原则

提到重载和重写&#xff0c;Java小白应该都不陌生&#xff0c;接下来就通过这篇文章来一起回顾复习下吧&#xff01; 重载和重写有什么区别呢&#xff1f; 如果一个类有多个名字相同但参数不同的方法&#xff0c;我们通常称这些方法为方法重载Overload。如果方法的功能是一样…

chrome插件:网站视频下载

前置条件&#xff1a; 安装有chrome谷歌浏览器的电脑 使用步骤&#xff1a; 1.打开chrome扩展插件 2.点击管理扩展程序 3.加载已解压的扩展程序 4.选择对应文件夹 5.成功后会出现一个扩展小程序 6.点击对应小程序 7.输入对应网站&#xff0c;点击插件视频下载助手

【ComfyUI专栏】ComfyUI 部署Kolors

什么是Kolors?我相信一定会有朋友可能第一次听说这个生图的模型,开始我也很难想象,这竟然是快手推出的可灵AI的项目,我们可以直接利用模型来生成图片和视频。 大家可以通过直接访问可灵AI的网址获取到可灵的项目,但是对于我们来说我们需要基于ComfyUI来生成必要的图片和视…

Python Typing: 实战应用指南

文章目录 1. 什么是 Python Typing&#xff1f;2. 实战案例&#xff1a;构建一个用户管理系统2.1 项目描述2.2 代码实现 3. 类型检查工具&#xff1a;MyPy4. 常见的 typing 用法5. 总结 在 Python 中&#xff0c;静态类型检查越来越受到开发者的重视。typing 模块提供了一种方式…

250125-package

1. 定义 包就是文件夹&#xff0c;作用是在大型项目中&#xff0c;避免不同人的编写的java文件出现同名进而导致报错&#xff1b;想象一个场景&#xff0c;在一个根目录中&#xff0c;每一个人都有自己的一个java文件夹&#xff0c;他可以将自己编写的文件放在该文件夹里&…

1.24学习

misc buuctf-easycap 附件是一个wireshark流量分析文件&#xff0c;用wireshark打开全是TCP&#xff0c;那就追踪任意TCP数据流 crypto bugku-你喜欢下棋吗 下载附件后一个压缩包一个文本文件&#xff0c;文本文件为压缩包的密码看了这个文本再结合题意&#xff0c;应该是…

git gui 笔记

这里写目录标题 1. [下载安装git](https://blog.csdn.net/jiesunliu3215/article/details/111559125)2. [下载Git Gui](https://git-scm.com/downloads)3. 上传下载代码4. 创建版本5. 版本切换-checkout参考狂神说 git教程 -讲的是真的好gitee的git帮助 其他 1. 下载安装git 2…

ICSE‘25 LLM Assistance for Memory Safety

不知道从什么时候开始&#xff0c;各大技术社区&#xff0c;技术群聊流行着 “用Rust重写!” &#xff0c;放一张图(笑死… 这不, 随着大模型技术的流行&#xff0c;大家都在探索如何让大模型自动完成仓库级别(全程序)的代码重构&#xff0c;代码变换&#xff08;Refactor&…

BGP边界网关协议(Border Gateway Protocol)路由聚合详解

一、路由聚合 1、意义 在大规模的网络中&#xff0c;BGP路由表十分庞大&#xff0c;给设备造成了很大的负担&#xff0c;同时使发生路由振荡的几率也大大增加&#xff0c;影响网络的稳定性。 路由聚合是将多条路由合并的机制&#xff0c;它通过只向对等体发送聚合后的路由而…

蓝桥杯之c++入门(一)【第一个c++程序】

目录 前言一、第⼀个C程序1.1 基础程序1.2 main函数1.3 字符串1.4 头文件1.5 cin 和 cout 初识1.6 名字空间1.7 注释 二、四道简单习题&#xff08;点击跳转链接&#xff09;练习1&#xff1a;Hello,World!练习2&#xff1a;打印飞机练习3&#xff1a;第⼆个整数练习4&#xff…

【背包问题 】01背包

目录 一&#xff0c;01背包问题详解 问题描述&#xff1a; 问题分析&#xff1a; 代码&#xff1a; 空间优化&#xff1a; 二&#xff0c;典例 1&#xff0c;分割等和子集 题目解析&#xff1a; 算法解析&#xff1a; 代码&#xff1a; 空间优化&#xff1a; 2&am…

81,【5】BUUCTF WEB [b01lers2020]Life on Mars

进入靶场 怎莫颠颠的&#xff0c;一下子就想到展博了 先把左边的挨个点一遍 在最后一个有点收获 不过也没其他收获了 这种进去给个正常网页的题目&#xff0c;基本都靠url获取信息了 抓包看看有没有其他信息 竟然没有任何信息 自闭了 看别人的wp去咯 为什么别人抓到的包里…

80,【4】BUUCTF WEB [SUCTF 2018]MultiSQL

53&#xff0c;【3】BUUCTF WEB october 2019 Twice SQLinjection-CSDN博客 上面这个链接是我第一次接触二次注入 这道题也涉及了 对二次注入不熟悉的可以看看 BUUCTF出了点问题&#xff0c;打不开&#xff0c;以下面这两篇wp作为学习对象 [SUCTF 2018]MultiSQL-CSDN博客 …