当前位置: 首页 > news >正文

【项目实战】redis实现websocket分布式消息推送服务

由于redis并非专业的MQ中间件,消息的防丢失策略并不完整,存在丢失消息的可能。该方案为在再pc web管理平台的右下角弹出,显示新接收到的消息数,哪怕没有收到这个通知,也可以自己在消息中心看看。所以对可靠性要求不高。如果业务场景要求可靠性高,还是请使用专业的MQ中间件。该方案已在多个实际项目中运行。

流程架构

websocket实现同一账户多点登录、websocket服务多节点部署推送方案。

简单架构图

假设用户A在两个地方登录,连接到两个websocketServer服务节点1和2,用户B连接到2节点。

websocketServer将websocket session保存在各自的Map<String,Session>中,key为userid,value为websocket Session。节点1保存了用户A的websocket session,节点2保存了用户A、B的websocket session。

消息生产者发布消息的时候为json格式,如:[{"receive"="userid_a","msg"="您有1个未读消息"},{"receive"="userid_b","msg"="您有3个未读消息"}],将消息发到redis的一个Channel,如showNewestMsg。

websocketServer中订阅redis的channel=showNewestMsg,收到消息后根据消息中receive冲map中找到对应的websocket session,发消息给客户端。

核心代码

1.该项目为springboot项目,先引入jar包,由于是从实际项目中抽出来写的记录,可能还缺jar请自行导入。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<!--websocket-->

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

<!-- redis -->

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<!-- 工具类 -->

<dependency>

    <groupId>cn.hutool</groupId>

    <artifactId>hutool-all</artifactId>

    <version>5.3.6</version>

</dependency>

<dependency>

    <groupId>net.sf.json-lib</groupId>

    <artifactId>json-lib</artifactId>

    <version>2.4</version>

    <classifier>jdk15</classifier>

</dependency>

2.websocket配置

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**

 * spring websocket组件初始化

 * @author csf

 *

 */

//war包启动tomcat7及以下版本要关闭@Configuration注解,否则将无法启动websocket服务

@Configuration

public class WebSocketConfig

{

    @Bean

    public ServerEndpointExporter serverEndpointExporter()

    {

        return new ServerEndpointExporter();

    }

}

注意:war包启动tomcat7及以下版本要关闭@Configuration注解,否则将无法启动websocket服务。

3.websocket服务端实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import javax.annotation.Resource;

import javax.websocket.OnClose;

import javax.websocket.OnError;

import javax.websocket.OnMessage;

import javax.websocket.OnOpen;

import javax.websocket.Session;

import javax.websocket.server.PathParam;

import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

import com.kingengine.plug.service.MessageService;

import cn.hutool.core.util.StrUtil;

import net.sf.json.JSONArray;

import net.sf.json.JSONObject;

/**

 * WebSocket服务类

 * @author csf

 * @date 2020年8月10日

 */

@ServerEndpoint("/websocket/{custId}")

@Component

public class WebSocketServer

{

    @Resource

    private MessageService messageService;

     

    Logger log = LoggerFactory.getLogger(this.getClass());

     

    // 当前在线连接数

    private static int onlineCount = 0;

     

    // 存放每个用户对应的WebSocket连接对象,key为custId_HHmmss,确保一个登录用户只建立一个连接

    private static Map<String, Session> webSocketSessionMap = new ConcurrentHashMap<String, Session>();

     

    // 与某个客户端的连接会话,需要通过它来给客户端发送数据

    private Session session;

     

    // 接收用户id

    private String custId = "";

     

    private static WebSocketServer webSocketServer;

     

    // 通过@PostConstruct实现初始化bean之前进行的操作

    @PostConstruct

    public void init()

    {

        // 初使化时将已静态化的webSocketServer实例化

        webSocketServer = this;

        webSocketServer.messageService = this.messageService;

    }

     

    /**

     * 连接建立成功调用的方法

     * @param session 连接会话,由框架创建

     * @param custId 用户id, 为处理用户多点登录都能收到消息,需传该格式custId_HHmmss

     * @author csf

     * @date 2020年8月10日

     */

    @OnOpen

    public void onOpen(Session session, @PathParam("custId") String custId)

    {

        if (!webSocketSessionMap.containsKey(custId))

        {

            this.session = session;

            webSocketSessionMap.put(custId, session);

            addOnlineCount(); // 在线数加1

            log.info("有新连接[{}]接入,当前websocket连接数为:{}", custId, getOnlineCount());

        }

         

        this.custId = custId;

        try

        {

            // 第一次建立连接,推送消息给客户端,只会执行一次。后续的新消息由com.kingengine.plug.redis.RedisReceiver接收到redis订阅消息推送

            // 获取未读消息数

            // 由于前端传进来的custId是有时间后缀的,查询时需要去掉后缀。

            String qryCustId = custId.split("_")[0];

            JSONObject unreadMsg = webSocketServer.messageService.getUnreadCount(qryCustId);

             

            // 获取最新消息

            /*  JSONObject newMsg = webSocketServer.messageService.getNewestMsg(qryCustId);

            // 发送消息

            JSONArray msgArr = new JSONArray();

            if (newMsg!=null)

            {

                msgArr.add(newMsg);

            }*/

            JSONArray msgArr = new JSONArray();

            msgArr.add(unreadMsg);

            sendMessage(custId, msgArr.toString());

        }

        catch (Exception e)

        {

            log.error("客户端连接websocket服务异常");

            e.printStackTrace();

        }

    }

     

    /**

     * 连接关闭调用的方法

     */

    @OnClose

    public void onClose(@PathParam("custId") String sessionKey)

    {

        if (webSocketSessionMap.containsKey(sessionKey))

        {

            try

            {

                webSocketSessionMap.get(sessionKey).close();

                webSocketSessionMap.remove(sessionKey);

            }

            catch (IOException e)

            {

                log.error("连接[{}]关闭失败。", sessionKey);

                e.printStackTrace();

            }

            subOnlineCount();

            log.info("连接[{}]关闭,当前websocket连接数:{}", sessionKey, onlineCount);

        }

    }

     

    /**

     * 接收客户端发送的消息

     * @param message 客户端发送过来的消息

     * @param session websocket会话

     */

    @OnMessage

    public void onMessage(String message, Session session)

    {

        log.info("收到来自客户端" + custId + "的信息:" + message);

    }

     

    /**

     * 连接错误时触发

     * @param session

     * @param error

     */

    @OnError

    public void onError(Session session, Throwable error)

    {

        try

        {

            session.close();

        }

        catch (IOException e)

        {

            log.error("发生错误,连接[{}]关闭失败。");

            e.printStackTrace();

        }

        // log.error("websocket发生错误");

        // error.printStackTrace();

    }

     

    /**

     * 给指定的客户端推送消息,可单发和群发

     * @param sessionKeys 发送消息给目标客户端sessionKey,多个逗号“,”隔开1234,2345...

     * @param message

     * @throws IOException

     * @author csf

     * @date 2020年8月11日

     */

    public void sendMessage(String sessionKeys, String message)

    {

        if (StrUtil.isNotBlank(sessionKeys))

        {

            String[] sessionKeyArr = sessionKeys.split(",");

            for (String key : sessionKeyArr)

            {

                try

                {

                    // 可能存在一个账号多点登录

                    List<Session> sessionList = getLikeByMap(webSocketSessionMap, key);

                    for (Session session : sessionList)

                    {

                        session.getBasicRemote().sendText(message);

                    }

                }

                catch (IOException e)

                {

                    e.printStackTrace();

                    continue;// 某个客户端发送异常,不影响其他客户端发送

                }

            }

        }

        else

        {

            log.info("sessionKeys为空,没有目标客户端");

        }

    }

     

    /**

     * 给当前客户端推送消息,首次建立连接时调用

     */

    public void sendMessage(String message)

        throws IOException

    {

        this.session.getBasicRemote().sendText(message);

    }

     

    /**

     * 检查webSocket连接是否在线

     * @param sesstionKey webSocketMap中维护的key

     * @return 是否在线

     */

    public static boolean checkOnline(String sesstionKey)

    {

        if (webSocketSessionMap.containsKey(sesstionKey))

        {

            return true;

        }

        else

        {

            return false;

        }

    }

     

    /**

     * 获取包含key的所有map值

     * @param map

     * @param keyLike

     * @return

     * @author csf

     * @date 2020年8月13日

     */

    private List<Session> getLikeByMap(Map<String, Session> map, String keyLike)

    {

        List<Session> list = new ArrayList<>();

        for (String key : map.keySet())

        {

            if (key.contains(keyLike))

            {

                list.add(map.get(key));

            }

        }

        return list;

    }

     

    public static synchronized int getOnlineCount()

    {

        return onlineCount;

    }

     

    public static synchronized void addOnlineCount()

    {

        WebSocketServer.onlineCount++;

    }

     

    public static synchronized void subOnlineCount()

    {

        WebSocketServer.onlineCount--;

    }

}

4.redis消息订阅配置

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

import org.springframework.cache.annotation.EnableCaching;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.data.redis.listener.PatternTopic;

import org.springframework.data.redis.listener.RedisMessageListenerContainer;

import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

@Configuration

@EnableCaching

public class RedisCacheConfig

{

    @Bean

    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter)

    {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();

        container.setConnectionFactory(connectionFactory);

        // 可以添加多个 messageListener,配置不同的交换机

        container.addMessageListener(listenerAdapter, new PatternTopic("showNewestMsg"));// 订阅最新消息频道

        return container;

    }

     

    @Bean

    MessageListenerAdapter listenerAdapter(RedisReceiver receiver)

    {

        // 消息监听适配器

        return new MessageListenerAdapter(receiver, "onMessage");

    }

     

    @Bean

    StringRedisTemplate template(RedisConnectionFactory connectionFactory)

    {

        return new StringRedisTemplate(connectionFactory);

    }

}

5.redis配置,直接放在springboot项目application.properties或application.yml中

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# 数据库索引(默认为0)

spring.redis.database=0 

spring.redis.host=192.168.1.100

spring.redis.port=6379

spring.redis.password=123456

# 连接池最大连接数(使用负值表示没有限制)

spring.redis.pool.max-active=8 

# 连接池最大阻塞等待时间(使用负值表示没有限制)

spring.redis.pool.max-wait=-1 

# 连接池中的最大空闲连接

spring.redis.pool.max-idle=8 

# 连接池中的最小空闲连接

spring.redis.pool.min-idle=0 

# 连接超时时间(毫秒)

spring.redis.timeout=5000

6.接收消息生产者发布的消息,推送给对应的客户端

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

import java.io.UnsupportedEncodingException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.connection.Message;

import org.springframework.data.redis.connection.MessageListener;

import org.springframework.stereotype.Component;

import com.kingengine.plug.websocket.WebSocketServer;

import cn.hutool.core.codec.Base64;

import cn.hutool.core.util.StrUtil;

import net.sf.json.JSONArray;

import net.sf.json.JSONObject;

/**

 * 消息监听对象,接收订阅消息

 * @author csf

 * @date 2020年8月13日

 */

@Component

public class RedisReceiver implements MessageListener

{

    Logger log = LoggerFactory.getLogger(this.getClass());

     

    @Autowired

    WebSocketServer webSocketServer;

     

    /**

     * 处理接收到的订阅消息

     */

    @Override

    public void onMessage(Message message, byte[] pattern)

    {

        String channel = new String(message.getChannel());// 订阅的频道名称

        String msg = "";

        try

        {

            msg = new String(message.getBody(), "GBK");//注意与发布消息编码一致,否则会乱码

            if (StrUtil.isNotBlank(msg)){

                if ("showNewestMsg".endsWith(channel))// 最新消息

                {

                    JSONObject json = JSONObject.fromObject(msg);

                    webSocketServer.sendMessage(json.get("receive"),json.get("msg"));

                }else{

                    //TODO 其他订阅的消息处理

                }

                

            }else{

                log.info("消息内容为空,不处理。");

            }

        }

        catch (Exception e)

        {

            log.error("处理消息异常:"+e.toString())

            e.printStackTrace();

        }

    }

}

7.消息发布测试

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

import java.io.UnsupportedEncodingException;

import java.util.HashMap;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import net.sf.json.JSONObject;

@RequestMapping("redis")

@RestController

public class RedisTestController

{

    @Autowired

    StringRedisTemplate template;

     

    /**

     * 发布消息测试

     *@param userid

     * @param msg

     * @return

     */

    @PostMapping("sendMessage")

    public String sendMessage(String userid,String msg)

    {

        try

        {

            String newMessge=new String(msg.getBytes("GBK"),"GBK");

            Map<String,String> map = new HashMap<String, String>();

            map.put("receive", userid);

            map.put("msg", newMessge);

            template.convertAndSend("showNewestMsg",        

          JSONObject.fromObject(map).toString());

        }

        catch (UnsupportedEncodingException e)

        {

            e.printStackTrace();

        }

        return "消息发布成功!";

    }

}

8.客户端代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

<!DOCTYPE html>

<html>

<head>

    <title>WebSocket测试</title>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

</head>

<body>

<div>

    来自服务端消息:

    <p id="message"></p>

</div>

</body>

<script src="http://apps.bdimg.com/libs/jquery/1.6.4/jquery.min.js"></script>

<script>

    let webSocketClient;

    if (window.WebSocket)

    {

       let custid="132456_" + Math.random();//该参数会作为websocketServer中存储session的key,要保证唯一。

        webSocketClient = new WebSocket("ws://127.0.0.1:8082/bootapp/websocket/" + custid);

        //连通之后的回调事件

        webSocketClient.onopen = function () {

            webSocketClient.send("这里是地球,收到请回答。。。");

            //  webSocket.send('{"type":"1","data":"121"}');

        };

        //接收后台服务端的消息

        webSocketClient.onmessage = function (evt) {

            console.log("数据已接收:" + evt.data);

            showMessage("未读消息:" + evt.data);

        };

        //连接关闭的回调事件

        webSocketClient.onclose = function () {

            alert("连接已关闭...");

        };

    }else{

        alert("浏览器不支持websocket");

    }

    function showMessage(message) {

        $("#message").html(message);

    }

</script>

</html>

相关文章:

【项目实战】redis实现websocket分布式消息推送服务

由于redis并非专业的MQ中间件&#xff0c;消息的防丢失策略并不完整&#xff0c;存在丢失消息的可能。该方案为在再pc web管理平台的右下角弹出&#xff0c;显示新接收到的消息数&#xff0c;哪怕没有收到这个通知&#xff0c;也可以自己在消息中心看看。所以对可靠性要求不高。…...

(自用)配置文件优先级、SpringBoot原理、Maven私服

配置优先级 之前介绍过SpringBoot中支持三类配置文件.properties、.yml和.yaml&#xff0c;他们三者之间也是有着优先级顺序的&#xff0c;为.properties➡.yml➡.yaml。 同时SpringBoot为了增强程序的拓展性&#xff0c;除了支持配置文件属性配置&#xff0c;还支持Java系统属…...

在windows系统中使用labelimg对图片进行标注之工具安装及简单使用

一.背景 还是之前的主题&#xff0c;使用开源软件为公司搭建安全管理平台&#xff0c;从视觉模型识别安全帽开始。我是从运行、训练、标注倒过来学习的。本次主要是学习标注工具labelimg的安装及简单使用。 二.下载 LabelImg是一款广受欢迎的开源图像标注工具&#xff0c;为计…...

数字图像处理技术期末复习

1. 已知图像的分辨率和深度&#xff0c;怎么求图像的存储空间&#xff08;位&#xff0c;字节&#xff0c;KB&#xff09;&#xff1f; 题目&#xff1a; 已知图像的分辨率和深度&#xff0c;怎么求图像的存储空间&#xff08;位&#xff0c;字节&#xff0c;KB&#xff09;&a…...

点云空洞的边界识别提取 pso-bp 神经网络的模型来修复点云空洞 附python代码

代码是一个Python程序,用于处理3D点云数据,特别是检测和修复点云中的孔洞区域。 1. **导入库**: - `numpy`:用于数学运算。 - `open3d`:用于处理3D数据和可视化。 - `torch`:PyTorch库,用于深度学习。 - `torch.nn`和`torch.optim`:PyTorch的神经网络和优…...

【AutoDL】通过【SSH远程连接】【vscode】

小帅碎碎念 0. 起因1. SSH信息获取2. 给你的vscode安装支持SSH远程连接的插件3. SSH远程连接入口4. 输入密码登陆5. 总结 0. 起因 之前使用AutoDL和Jupyter进行代码编辑和执行确实很方便&#xff0c;尤其是对于交互式数据分析项目。然而&#xff0c;也存在一些限制和不便之处&…...

ubuntu22.04编译安装Opencv4.8.0+Opencv-contrib4.8.0教程

本章教程,主要记录在Ubuntu22.04版本系统上编译安装安装Opencv4.8.0+Opencv-contrib4.8.0的具体过程。 一、下载opencv和opencv-contrib包 wget https://github.com/opencv/opencv/archive/refs/tags/4.8.0.zip wget https://github.com/opencv/opencv_contrib/archive/refs/…...

短链接服务

一 功能描述 1.短链接是将长连接转化为短连接使得链接变得美观清爽&#xff0c;让用户点击率更高&#xff0c;同时规避原始链接中一些关键词、域名屏蔽等问题&#xff0c;最终利用短链每次跳转都需要经过后端的特性&#xff0c;在跳转过程中做异步埋点&#xff0c;用于效果数据…...

【Vue3学习】setup语法糖中的ref,reactive,toRef,toRefs

在 Vue 3 的组合式 API&#xff08;Composition API&#xff09;中&#xff0c;ref、reactive、toRef 和 toRefs 是四个非常重要的工具函数&#xff0c;用于创建和管理响应式数据。 一、ref 用ref()包裹数据,返回的响应式引用对象&#xff0c;包含一个 .value 属性&#xff0…...

Halcon中dots_image(Operator)算子原理及应用详解

在HALCON中&#xff0c;dots_image算子是一个用于增强图像中圆点效果的强大工具&#xff0c;特别适合于点的分割&#xff0c;以及OCR&#xff08;光学字符识别&#xff09;应用程序中增强点状印刷字体。以下是对dots_image (ImageResult, DotImage, 5, ‘dark’, 2)算子原理及应…...

【C语言】库函数常见的陷阱与缺陷(四):内存内容操作函数[5]--memchr

C语言中的memchr函数用于在内存块中搜索一个特定的字符(实际上是unsigned char类型的值),并返回该字符第一次出现的指针。虽然这个函数在内存搜索中非常有用,但它也存在一些陷阱。 一、功能与用法 功能:memchr函数在指定的内存块中搜索第一次出现的特定字符,并返回一个…...

【P2P】【Go】采用go语言实现udp hole punching 打洞 传输速度测试 ping测试

服务器端 udpserver/main.go package mainimport ("fmt""net""sync""sync/atomic" )var (clientCounter uint64 0 // 客户端连接计数器mu sync.Mutex )func main() {addr, err : net.ResolveUDPAddr("udp", &q…...

【附源码】Electron Windows桌面壁纸开发中的 CommonJS 和 ES Module 引入问题以及 Webpack 如何处理这种兼容

背景 在尝试让 ChatGPT 自动开发一个桌面壁纸更改的功能时&#xff0c;发现引入了一个 wallpaper 库&#xff0c;这个库的入口文件是 index.js&#xff0c;但是 package.json 文件下的 type:"module"&#xff0c;这样造成了无论你使用 import from 还是 require&…...

【SpringBoot 调度任务】

在 Spring Boot 中实现调度任务&#xff08;Scheduled Tasks&#xff09;&#xff0c;通过使用 EnableScheduling 和 Scheduled 注解来完成。 添加依赖启用调度任务支持创建调度任务运行应用程序 添加依赖 pom.xml 文件中有以下依赖项&#xff1a; <dependency><gro…...

Android v4和v7冲突

android.useAndroidXtrue android.enableJetifiertruev4转成AndroidX...

【HarmonyOS之旅】HarmonyOS开发基础知识(一)

目录 1 -> 应用基础知识 1.1 -> 用户应用程序 1.2 -> 用户应用程序包结构 1.3 -> Ability 1.4 -> 库文件 1.5 -> 资源文件 1.6 -> 配置文件 1.7 -> pack.info 1.8 -> HAR 2 -> 配置文件简介 2.1 -> 配置文件的组成 3 -> 配置文…...

【排序算法】——插入排序

目录 前言 简介 基本思想 1.直接插入排序 2.希尔排序 代码实现 1.直接插入排序 2.希尔排序 总结 1.时空复杂度 2.稳定性 尾声 前言 排序(Sorting) 是计算机程序设计中的一种重要操作&#xff0c;它的功能是将一个数据元素&#xff08;或记录&#xff09;的任意序列&…...

Vue todoList小项目记录

最初代码 简单搭一个vue2的小项目 App.vue <template><div id"app"><!-- 容器 --><div class"todo-container"><div class"todo-wrap"><!-- 头部 --><MyHeader :addTodo"addTodo"></…...

SQL题目笔记

一、根据需求创建表&#xff08;设计合理的数据类型、长度)...

电脑开机提示error loading operating system怎么修复?

前一天电脑还能正常运行&#xff0c;但今天启动时却显示“Error loading operating system”&#xff08;加载操作系统错误&#xff09;。我已经仔细检查了硬盘、接线、内存、CPU和电源&#xff0c;确认这些硬件都没有问题。硬盘在其他电脑上可以正常使用&#xff0c;说明不是硬…...

Nginx 在不同操作系统下的安装指南

Nginx 在不同操作系统下的安装指南 一、Linux 系统下 Nginx 的安装 &#xff08;一&#xff09;基于 Ubuntu 系统 更新软件包列表 打开终端&#xff0c;首先执行sudo apt-get update命令。这一步是为了确保系统的软件包列表是最新的&#xff0c;能够获取到最新版本的 Nginx 及…...

景联文科技入选中国信通院发布的“人工智能数据标注产业图谱”

近日&#xff0c;由中国信息通信研究院、中国人工智能产业发展联盟牵头&#xff0c;联合中国电信集团、沈阳市数据局、保定高新区等70多家单位编制完成并发布《人工智能数据标注产业图谱》。景联文科技作为人工智能产业关键环节的代表企业&#xff0c;入选图谱中技术服务板块。…...

Nginx - 负载均衡及其配置(Balance)

一、概述 定义&#xff1a;在多个计算机&#xff08;计算机集群&#xff09;、网络连接、CPU、磁盘驱动器或其他资源中分配负载目标&#xff1a;最佳化资源使用、最大化吞吐率、最小化响应时间、避免过载功能&#xff1a;使用多台服务器提供单一服务&#xff08;服务器农场&am…...

MySQL存储引擎-存储结构

Innodb存储结构 Buffer Pool(缓冲池)&#xff1a;BP以Page页为单位&#xff0c;页默认大小16K&#xff0c;BP的底层采用链表数据结构管理Page。在InnoDB访问表记录和索引时会在Page页中缓存&#xff0c;以后使用可以减少磁盘IO操作&#xff0c;提升效率。 ○ Page根据状态可以分…...

数据资产入表 解锁智慧城市新潜力

在21世纪的科技浪潮中&#xff0c;智慧城市以信息技术为核心&#xff0c;以数据为血液&#xff0c;通过智能化、精细化的管理&#xff0c;让城市变得更加智慧、更加宜居。而数据资产入表&#xff0c;正是这一变革中的关键一环&#xff0c;它不仅推动了科技的进步&#xff0c;更…...

按类别调整目标检测标注框的写入顺序以优化人工审核效率

引言 在目标检测数据标注审核过程中&#xff0c;我们常常会遇到以下情况&#xff1a;某些小目标的检测框嵌套在大目标检测框内&#xff0c;而在模型进行预标注后&#xff0c;这些小目标的框可能被写入到了大目标框的下层。在人工审核阶段&#xff0c;标注审核人员需要手动移动…...

深入理解YOLO系列目标检测头的设定方式

目录 YOLOv1的检测头结构 1. 网络结构概述 2. 结构细节 3. 优缺点 YOLOv2的检测头结构 1. 网络结构概述 2. 结构细节 3. 优缺点 YOLOv3的检测头结构 1. 网络结构概述 2. 结构细节 3. 优缺点 总结&#xff1a;YOLO 系列检测头的结构演变 YOLOv1的检测头结构 1. 网络…...

智慧农业物联网解决方案:道品科技水肥一体化

在当今科技飞速发展的时代&#xff0c;农业也迎来了一场深刻的变革。智慧农业物联网解决方案中的水肥一体化技术&#xff0c;正逐渐成为现代农业发展的重要助推器。它不仅提高了农业生产效率&#xff0c;还实现了精准施肥和灌溉&#xff0c;为农业可持续发展带来了新的机遇。 …...

单片机上电后程序不运行怎么排查问题?

1.电源检查。使用电压表测量单片机的电源电压是否正常&#xff0c;确保电压在规定的范围内&#xff0c;如常见的5V。 2.复位检查。检查复位引脚的电压是否正常&#xff0c;在单片机接通电源时&#xff0c;复位引脚通常会有一个高电平&#xff0c;按下复位按钮时&#xff0c;复位…...

OceanBase 数据库分布式与集中式 能力

OceanBase分布式数据库与集中式数据库的差异 分布式数据库能解决金融行业最有挑战的高并发低延迟的核心交易系统的稳定性、扩展性、高性能问题。OB之所以一直强调分布式是说它具备很强的数据处理能力&#xff0c;当然从OB4.0开始也支持集中式了。 在实际业务场景中20%是分布式…...

C#多线程

C#中的多线程编程是开发高效并发应用程序的关键技术之一&#xff0c;它允许程序同时执行多个任务&#xff0c;从而提升应用程序的响应速度和性能。为了更好地理解C#中的多线程使用和定义&#xff0c;我们可以从以下几个方面来探讨&#xff1a;线程的基本概念、创建线程的方法、…...

Apache HTTP 服务器深度性能优化

引言 在前几篇文章中&#xff0c;我们讨论了基础和高级性能优化策略。现在&#xff0c;我们将深入探讨一些具体的优化实践&#xff0c;帮助您实现更精细的控制&#xff0c;并确保Apache服务器在各种复杂环境中都能保持最佳性能。 1. 细粒度的Apache配置调整 1.1 MPM参数微调…...

芯片级IO (Pad) Ring IP Checklist

SoC top顶层数字后端实现都会涉及到IO Ring &#xff08;PAD Ring&#xff09;的设计。这里面包括VDD IO,VDDIO IO, Signal IO, Corner IO&#xff0c;Filler IO&#xff0c;IO power cut cell等等。 数字后端零基础入门系列 | Innovus零基础LAB学习Day2 数字IC后端实现TOP F…...

无界wujie网址

文档网址&#xff1a;微前端是什么 | 无界 demo&#xff1a;https://wujie-micro.github.io/demo-main-vue/react17...

vulnhub靶场【DriftingBlues】之6

前言 靶机&#xff1a;DriftingBlues-6&#xff0c;IP地址192.168.1.63&#xff0c;因为重装靶机后期为192.168.1.64 攻击&#xff1a;kali&#xff0c;IP地址192.168.1.16 都采用虚拟机&#xff0c;网卡为桥接模式 主机发现 使用arp-scan -l或netdiscover -r 192.168.1.1…...

心情追忆- Nginx + OpenResty 构建高可用网关

之前&#xff0c;我独自一人开发了一个名为“心情追忆”的小程序&#xff0c;旨在帮助用户记录日常的心情变化及重要时刻。我从项目的构思、设计、前端&#xff08;小程序&#xff09;开发、后端搭建到最终部署。经过一个月的努力&#xff0c;通过群聊分享等方式&#xff0c;用…...

太速科技-527-基于3U VPX XCZU15EG+TMS320C6678的信号处理板

基于3U VPX XCZU15EGTMS320C6678的信号处理板 一、板卡概述 本板卡系我司自主研发的基于3U VPX风冷、导冷架构的信号处理板&#xff0c;适用于高速图像处理等。芯片采用工业级设计。 板卡采用标准3U VPX架构&#xff0c;板上集成一片Xilinx公司ZynqUltraScale系列F…...

Vue3源码笔记阅读1——Ref响应式原理

本专栏主要用于记录自己的阅读源码的过程,希望能够加深自己学习印象,也欢迎读者可以帮忙完善。接下来每一篇都会从定义、运用两个层面来进行解析 定义 运用 例子:模板中访问ref(1) <template><div>{{str}}</div> </template> <script> impo…...

多音轨视频使用FFmpeg删除不要音轨方法

近期给孩子找宫崎骏动画&#xff0c;但是有很多是多音轨视频但是默认的都是日语&#xff0c;电视上看没办法所以只能下载后删除音轨文件只保留中文。 方法分两步&#xff0c;先安装FFmpeg在转文件即可。 第一步FFmpeg安装 FFmpeg是一个开源项目&#xff0c;包含了处理视频的…...

AtomGit 开源生态应用开发赛报名开始啦

目录 1、赛项背景2、赛项信息3、报名链接4、赛题一&#xff1a;开发者原创声明&#xff08;DCO&#xff09;应用开发赛题要求目标核心功能 5、赛题二&#xff1a;基于 OpenHarmony 的开源社区应用开发简介赛题要求 6、参赛作品提交初赛阶段决赛阶段 7、参赛作品提交方式 1、赛项…...

使用 NVIDIA DALI 计算视频的光流

引言 光流&#xff08;Optical Flow&#xff09;是计算机视觉中的一种技术&#xff0c;主要用于估计视频中连续帧之间的运动信息。它通过分析像素在时间维度上的移动来预测运动场&#xff0c;广泛应用于目标跟踪、动作识别、视频稳定等领域。 光流的计算传统上依赖 CPU 或 GP…...

C语言学习day23:WriteProcessMemory函数/游戏内存数据修改工具开发

简言&#xff1a; 上一章我们说了获取应用进程的某数据&#xff08;data&#xff09;&#xff0c;这一章我们就说说修改内存地址的数据。想要修改内存&#xff0c;那么就需要我们另一个WinAPI函数&#xff1a;WriteProcessMemory()函数。 WriteProcessMemory()函数 函数原型…...

利用 html_table 函数轻松获取网页中的表格数据

背景/引言 在数据爬取的过程中&#xff0c;网页表格数据往往是研究人员和开发者的重要目标之一。无论是统计分析、商业调研还是信息整理&#xff0c;表格数据的结构化特性都使其具有较高的利用价值。然而&#xff0c;如何快速、准确地从网页中提取表格数据始终是爬虫技术的一个…...

Postman接口测试:全局变量/接口关联/加密/解密

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 全局变量和环境变量 全局变量&#xff1a;在postman全局生效的变量&#xff0c;全局唯一 环境变量&#xff1a;在特定环境下生效的变量&#xff0c;本环境内唯一 …...

手机银行模拟器,一款高仿真银行app的模拟器,可以修改姓名 卡号 余额 做转账记录 做流水

&#x1f4f1;手机银行模拟器让你自由定制你的金融生活。无论是流水账单、金额&#xff0c;还是个人信息&#xff0c;一切都可以按照你的意愿来模拟修改&#xff0c;让你体验模拟器带来的快乐&#xff01; 链接&#xff1a;https://pan.quark.cn/s/c2f614f3447f 提取码&#…...

HT7183:16V, 4.5A的DC-DC升压转换器,常用在数码相机里

HT7183描述&#xff1a; HT7183是一款高功率异步升压转换器&#xff0c;集成120mΩ功率开关管&#xff0c;为便携式系统提供高效的小尺寸解决方案。具有2.6V至5.5V输入电压范围&#xff0c;可为各类不同供电的应用提供支持。该器件具备3A开关电流能力&#xff0c;并且能够提供高…...

Cobalt Strike 4.8 用户指南-第十四节 Aggressor 脚本

14.1、什么是Aggressor脚本 Aggressor Script 是Cobalt Strike 3.0版及更高版本中内置的脚本语言。Aggressor 脚本允许你修改和扩展 Cobalt Strike 客户端。 历史 Aggressor Script 是 Armitage 中开源脚本引擎Cortana的精神继承者。Cortana 是通过与 DARPA 的网络快速跟踪计…...

【Qt】QWidget中的常见属性及其功能(二)

目录 六、windowOpacity 例子&#xff1a; 七、cursor 例子&#xff1a; 八、font 九、toolTip 例子&#xff1a; 十、focusPolicy 例子&#xff1a; 十一、styleSheet 计算机中的颜色表示 例子&#xff1a; 六、windowOpacity opacity是不透明度的意思。 用于设…...

对象的克隆 单例模式

1) 如何实现对象的克隆&#xff1f; 1、为什么需要实现对象的克隆&#xff1f; 在某些情况下&#xff0c;需要创建一个与现有对象完全相同的副本&#xff0c;这就是对象克隆。 例如&#xff0c;在需要对对象进行备份、在不同的上下文中使用相同的类型的对象或者实现某些设计…...

预处理内容

预处理是干什么的呢&#xff1f; 分为三点&#xff1a; 1.宏替换 2.头文件导入 3.删除注释 #ifdef #include <iostream> // 定义一个宏&#xff0c;表示当前处于调试模式&#xff0c;在实际调试时可以定义这个宏&#xff0c;发布时取消定义#define DEBUG MODE int ma…...