WebSocket+Http实现功能加成

WebSocket+Http实现功能加成

在这里插入图片描述

前言

首先,WebSocket和HTTP是两种不同的协议,它们在设计和用途上有一些显著的区别。以下是它们的主要特点和区别:

HTTP (HyperText Transfer Protocol):

  1. 请求-响应模型: HTTP 是基于请求-响应模型的协议,客户端发送请求,服务器返回响应。这是一个单向通信的模式。

  2. 无状态: HTTP 是无状态的协议,每个请求都是独立的,服务器不会记住之前的请求状态。为了维护状态,通常使用会话(Session)和 Cookie。

  3. 连接短暂: 每个请求都需要建立一个新的连接,然后在响应后关闭。这种短暂的连接模型适用于传统的网页浏览,但对于实时通信则显得不够高效。

  4. 端口: 默认使用端口80进行通信(HTTPS使用端口443)。

WebSocket:

  1. 双向通信: WebSocket 提供全双工通信,允许在客户端和服务器之间建立持久性的双向连接,实现实时数据传输。

  2. 状态: WebSocket 连接是持久性的,服务器和客户端之间可以保持状态。这减少了每次通信时的开销,使其适用于需要频繁交换数据的实时应用。

  3. 协议升级: WebSocket 连接通常通过HTTP协议的升级头部进行初始化,然后升级到WebSocket连接。这样的设计允许在HTTP和WebSocket之间平稳切换,同时在需要时实现更高级的协议升级。

  4. 端口: 默认使用端口80进行非安全通信,使用端口443进行安全通信。

  5. 低开销: 相比于HTTP轮询或长轮询,WebSocket 通信的开销较低,因为它减少了头部信息的传输,同时避免了不必要的连接和断开。

HTTP适用于传统的请求-响应模型,而WebSocket适用于需要实时、双向通信的应用场景,如在线游戏、聊天应用和实时协作工具。在某些情况下,它们可以结合使用,以充分发挥各自的优势。

代码实现

在这里,我们首先确定一个事情,就是这个我们以若依为主要的参考,在若依的基础上进行演示和操作,所以,对于若依这套架子有成见的同志请移步!!我们先创建一个信号量相关处理的工具类,作为辅助工具!

这段 Java 代码是一个用于处理信号量(Semaphore)的工具类,其中包含了获取信号量和释放信号量的方法。信号量是一种用于控制同时访问特定资源的线程数的同步工具。

package com.ruoyi.framework.websocket;

import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 信号量相关处理
 * 
 * @author ruoyi
 */
public class SemaphoreUtils
{
    /**
     * SemaphoreUtils 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);

    /**
     * 获取信号量
     * 
     * @param semaphore
     * @return
     */
    public static boolean tryAcquire(Semaphore semaphore)
    {
        boolean flag = false;

        try
        {
            // 尝试获取信号量,如果成功返回 true,否则返回 false
            flag = semaphore.tryAcquire();
        }
        catch (Exception e)
        {
            // 捕获异常并记录日志
            LOGGER.error("获取信号量异常", e);
        }

        return flag;
    }

    /**
     * 释放信号量
     * 
     * @param semaphore
     */
    public static void release(Semaphore semaphore)
    {
        try
        {
            // 释放信号量,增加可用的许可数
            semaphore.release();
        }
        catch (Exception e)
        {
            // 捕获异常并记录日志
            LOGGER.error("释放信号量异常", e);
        }
    }
}

解释每个部分的功能:

  1. tryAcquire(Semaphore semaphore) 方法:该方法尝试从信号量中获取一个许可。如果成功获取许可,返回 true;如果无法获取许可,返回 false。在获取信号量时,可能会抛出异常,例如在等待获取信号量的过程中发生中断或超时,因此在方法内捕获异常并记录日志。

  2. release(Semaphore semaphore) 方法:该方法释放一个许可到信号量中,增加信号量的可用许可数。同样,可能会在释放信号量时发生异常,捕获异常并记录日志。

这样的工具类通常用于协调多个线程对共享资源的访问,通过信号量可以限制同时访问某个资源的线程数量,以避免竞争和提高系统的稳定性。在这个类中,异常处理的目的是确保即使在获取或释放信号量的过程中发生异常,也能够进行适当的处理并记录相关信息。

然后呢,我们来编写一个websocket的配置文件,内容很简单,如下:

package com.ruoyi.framework.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * websocket 配置
 * 
 * @author ruoyi
 */
@Configuration
public class WebSocketConfig
{
    @Bean
    public ServerEndpointExporter serverEndpointExporter()
    {
        return new ServerEndpointExporter();
    }
/************************************************自己增加的内容*******************************************************************/
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // 在此处设置bufferSize
        container.setMaxTextMessageBufferSize(5120000);
        container.setMaxBinaryMessageBufferSize(5120000);
        container.setMaxSessionIdleTimeout(15 * 60000L);
        return container;
    }
}

这是一个WebSocket配置类,用于配置WebSocket相关的参数和组件。以下是对每个部分的详细介绍:

  1. @Configuration 注解:

    • 表示这是一个Spring配置类,用于配置WebSocket相关的组件。
  2. @Bean 方法 - serverEndpointExporter:

    • 创建并返回一个ServerEndpointExporter对象。
    • ServerEndpointExporter是Spring提供的用于注册和管理WebSocket端点的Bean,它会在Spring容器中查找所有使用@ServerEndpoint注解的WebSocket端点并注册它们。
    • 这个Bean的存在使得WebSocket端点能够被正确地注册并且可以被使用。
  3. @Bean 方法 - createWebSocketContainer:

    • 创建并返回一个ServletServerContainerFactoryBean对象。
    • ServletServerContainerFactoryBean是Spring提供的用于配置WebSocket容器的工厂Bean。通过配置它,可以设置WebSocket容器的一些参数。
    • 在这个方法中,设置了以下WebSocket容器的参数:
      • setMaxTextMessageBufferSize(5120000): 设置文本消息的最大缓冲区大小为5 MB。
      • setMaxBinaryMessageBufferSize(5120000): 设置二进制消息的最大缓冲区大小为5 MB。
      • setMaxSessionIdleTimeout(15 * 60000L): 设置会话的最大空闲时间为15分钟。

简而言之,这个WebSocket配置类使用Spring的Java配置方式,通过@Bean注解创建了两个Bean,分别是ServerEndpointExporterServletServerContainerFactoryBean。前者用于注册WebSocket端点,后者用于配置WebSocket容器的一些参数。这样,你的Spring Boot应用就能够正确地支持WebSocket了。

然后呢,就到了最主要的内容,我们需要写一个websocket服务类,里面有众多方法

package com.ruoyi.framework.websocket;

import java.util.concurrent.Semaphore;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * websocket 消息处理
 * 
 * @author ruoyi
 */
@Component
@ServerEndpoint("/websocket/message")
public class WebSocketServer
{
    /**
     * WebSocketServer 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 默认最多允许同时在线人数100
     */
    public static int socketMaxOnlineCount = 100;

    private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) throws Exception
    {
        boolean semaphoreFlag = false;
        // 尝试获取信号量
        semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);
        if (!semaphoreFlag)
        {
            // 未获取到信号量
            LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);
            WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);
            session.close();
        }
        else
        {
            // 添加用户
            WebSocketUsers.put(session.getId(), session);
            LOGGER.info("\n 建立连接 - {}", session);
            LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());
            WebSocketUsers.sendMessageToUserByText(session, "连接成功");
        }
    }

    /**
     * 连接关闭时处理
     */
    @OnClose
    public void onClose(Session session)
    {
        LOGGER.info("\n 关闭连接 - {}", session);
        // 移除用户
        WebSocketUsers.remove(session.getId());
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 抛出异常时处理
     */
    @OnError
    public void onError(Session session, Throwable exception) throws Exception
    {
        if (session.isOpen())
        {
            // 关闭连接
            session.close();
        }
        String sessionId = session.getId();
        LOGGER.info("\n 连接异常 - {}", sessionId);
        LOGGER.info("\n 异常信息 - {}", exception);
        // 移出用户
        WebSocketUsers.remove(sessionId);
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session)
    {
        String msg = message.replace("你", "我").replace("吗", "");
        WebSocketUsers.sendMessageToUserByText(session, msg);
    }
}

这段代码是一个WebSocket服务器端的实现,用于处理与客户端的WebSocket通信。让我们逐步解释其关键部分:

  1. @Component 注解:

    • 表示将该类作为Spring组件进行管理,可以通过Spring的扫描机制自动识别并注册为bean。
  2. @ServerEndpoint("/websocket/message") 注解:

    • 将该类标记为WebSocket的端点,客户端可以通过访问 “/websocket/message” 路径来与服务器建立WebSocket连接,我们如果需要连接WebSocket,那么连接地址就是:ws:localhost:端口号/websocket/message,类似于http请求!
  3. @OnOpen 注解:

    • 标注一个方法,在WebSocket连接建立时触发。在这里,它用于处理连接建立成功后的逻辑。
    • 尝试获取信号量,如果成功,表示连接建立成功,会将该连接加入到用户列表中;否则,如果在线人数超过限制,会发送错误信息并关闭连接。
  4. @OnClose 注解:

    • 标注一个方法,在WebSocket连接关闭时触发。在这里,它用于处理连接关闭后的清理逻辑,包括移除用户并释放信号量。
  5. @OnError 注解:

    • 标注一个方法,在WebSocket发生异常时触发。在这里,它用于处理异常情况,关闭连接并进行相应的清理工作。
  6. @OnMessage 注解:

    • 标注一个方法,在服务器接收到客户端消息时触发。在这里,它将接收到的消息进行处理,例如将消息中的 “你” 替换为 “我”,去掉 “吗”,然后通过 WebSocketUsers.sendMessageToUserByText 方法将处理后的消息发送回客户端。

总体而言,这个类实现了WebSocket服务器的基本功能,包括连接的建立、关闭、异常处理以及消息的处理。同时,它通过信号量控制了最大允许同时在线人数,确保连接数不超过设定的限制。

代码也很简单,大家在网上也会找到类似的工具类,只不过若依是多加了一些封装

最后,就到了服务升级了,这也是若依特有的,我们一起看看:

package com.ruoyi.framework.websocket;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.websocket.Session;

import com.alibaba.fastjson2.JSON;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * websocket 客户端用户集
 * 
 * @author ruoyi
 */
public class WebSocketUsers
{
    /**
     * WebSocketUsers 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);

    /**
     * 用户集
     */
    private static Map<String, Session> USERS = new ConcurrentHashMap<String, Session>();

    /**
     * 存储用户
     *
     * @param key 唯一键
     * @param session 用户信息
     */
    public static void put(String key, Session session)
    {
        USERS.put(key, session);
    }

    /**
     * 移除用户
     *
     * @param session 用户信息
     *
     * @return 移除结果
     */
    public static boolean remove(Session session)
    {
        String key = null;
        boolean flag = USERS.containsValue(session);
        if (flag)
        {
            Set<Map.Entry<String, Session>> entries = USERS.entrySet();
            for (Map.Entry<String, Session> entry : entries)
            {
                Session value = entry.getValue();
                if (value.equals(session))
                {
                    key = entry.getKey();
                    break;
                }
            }
        }
        else
        {
            return true;
        }
        return remove(key);
    }

    /**
     * 移出用户
     *
     * @param key 键
     */
    public static boolean remove(String key)
    {
        LOGGER.info("\n 正在移出用户 - {}", key);
        Session remove = USERS.remove(key);
        if (remove != null)
        {
            boolean containsValue = USERS.containsValue(remove);
            LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");
            return containsValue;
        }
        else
        {
            return true;
        }
    }

    /**
     * 获取在线用户列表
     *
     * @return 返回用户集合
     */
    public static Map<String, Session> getUsers()
    {
        return USERS;
    }

    /**
     * 群发消息文本消息
     *
     * @param message 消息内容
     */
    public static void sendMessageToUsersByText(String message)
    {
        Collection<Session> values = USERS.values();
        for (Session value : values)
        {
            sendMessageToUserByText(value, message);
        }
    }

    /**
     * 发送文本消息
     *
     * @param userName 自己的用户名
     * @param message 消息内容
     */
    public static void sendMessageToUserByText(Session session, String message)
    {
        if (session != null)
        {
            try
            {
                session.getBasicRemote().sendText(message);
            }
            catch (IOException e)
            {
                LOGGER.error("\n[发送消息异常]", e);
            }
        }
        else
        {
            LOGGER.info("\n[你已离线]");
        }
    }
/************************************************自己增加的内容***************************************************************/
    /**
     * 群发消息文本消息
     *
     * @param messages 消息内容
     */
    public static void sendMessageToUsers(List<String> messages, Collection<Session> values) {
        for (String message : messages) {
            for (Session value : values) {
                sendMessageToUserByText(value, message);
            }
        }
    }

    public static void sendMessageToUsersMap(List<Map<String, Object>> li, Collection<Session> values) {
        for (Map<String, Object> map : li) {
            for (Session value : values) {
                sendMessageToUserByText(value, JSON.toJSONString(map));
            }
        }
    }

    /**
     * 获取指定用户
     *
     * @param userIds 用户id
     * @return
     */
    public static Collection<Session> getSessionUserMap(List<Long> userIds) {
        Map<String, Session> sessionUserMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(userIds)) {
            List<String> newUserIds = userIds.stream().map(String::valueOf).collect(Collectors.toList());
            Map<String, Session> users = USERS;
            for (Iterator<Map.Entry<String, Session>> it = users.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry<String, Session> item = it.next();
                String key = item.getKey();
                // 发送用户处理
                if (newUserIds.contains(key.substring(0, key.indexOf("_")))) {
                    sessionUserMap.put(key, item.getValue());
                }
            }
        }
        return sessionUserMap.values();
    }

    /**
     * 给用户发消息
     *
     * @param message 消息内容
     * @param userId  用户id
     */
    public static void sendMessageToUser(String message, Long userId) {
        List<Long> userList = new ArrayList<>();
        List<String> msgs = new ArrayList<>();
        // 添加发送对象
        userList.add(userId);
        // 添加发送内容
        msgs.add(message);
        Collection<Session> sessionUserMap = WebSocketUsers.getSessionUserMap(userList);
        WebSocketUsers.sendMessageToUsers(msgs, sessionUserMap);
    }
}

这段代码定义了一个用于管理WebSocket用户的工具类 WebSocketUsers

  1. put 方法:

    • 用于将用户信息存储到USERS集合中,其中key是用户的唯一标识,session是与用户连接关联的Session对象。
  2. remove 方法 (两个重载):

    • USERS集合中移除用户。一个版本是通过Session对象移除,另一个版本是通过用户的唯一键移除。
    • 首先检查是否包含给定的Session,然后根据情况找到对应的键,最后移除用户。
  3. getUsers 方法:

    • 返回当前在线用户的集合,即USERS集合。
  4. sendMessageToUsersByText 方法:

    • 用于向所有在线用户发送文本消息。遍历USERS集合中的每个用户的Session对象,并调用sendMessageToUserByText方法发送消息。
  5. sendMessageToUserByText 方法:

    • 向指定用户的Session发送文本消息。
    • 如果session不为null,使用session.getBasicRemote().sendText(message)发送文本消息;否则,记录用户已离线的信息。
  6. sendMessageToUsers 方法:

    • 向一组用户发送消息,接收一个包含消息的列表和一个包含Session对象的集合。
    • 遍历消息列表和Session集合,为每个用户的Session对象发送相应的消息。
  7. sendMessageToUsersMap 方法:

    • 向一组用户发送包含Map<String, Object>消息的列表。
    • 将每个Map对象转换为JSON格式的字符串,并使用sendMessageToUserByText方法发送给每个用户。
  8. getSessionUserMap 方法:

    • 根据给定的用户id列表,返回相应用户的Session对象集合。
    • 使用用户id列表过滤出匹配的Session对象,并将其存储在sessionUserMap中返回。
  9. sendMessageToUser 方法:

    • 向指定用户发送消息,接收消息内容和目标用户的id。
    • 创建包含目标用户id和消息内容的列表,然后通过getSessionUserMap方法获取匹配的Session对象集合,并使用sendMessageToUsers方法发送消息。

这些方法共同构成了一个WebSocket用户管理类,用于存储用户信息、发送消息,以及一些特定需求的消息发送,如向指定用户发送消息、向特定用户组发送消息等。

这样,我们就大概完成了整个代码逻辑,也不是非常复杂!

然后,我们就需要进行测试一波----------

ApiPost测试

在这里插入图片描述

在线工具测试 https://wstool.js.org/

在这里插入图片描述

控制台日志

在这里插入图片描述
然后,大家会问,怎么实现http功能加成,其实大家可以看到在WebSocketUsers有需要业务层的处理方法,其实这些就是供http请求调用的

如:
在这里插入图片描述
这就是专门提供给http调用的,实现给指定用户发送消息,如消息通知,或者是公告,审批消息提醒等————
完毕,感谢大家阅读!

所谓好运,所谓幸福,显然不是一种客观的程序,而是完全心灵的感受,是强烈的幸福感罢了。
——史铁生

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

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

相关文章

Three.js学习8:基础贴图

一、贴图 贴图&#xff08;Texture Mapping&#xff09;&#xff0c;也翻译为纹理映射&#xff0c;“贴图”这个翻译更直观。 贴图&#xff0c;就是把图片贴在 3D 物体材质的表面&#xff0c;让它具有一定的纹理&#xff0c;来为 3D 物体添加细节的一种方法。这使我们能够添加…

TCP和UDP相关问题(重点)——7.TCP的流量控制怎么实现的?

流量控制就是在双方通信时&#xff0c;发送方的速率和接收方的速率不一定是相等的&#xff0c;如果发送方发送的太快&#xff0c;接收方就只能把数据先放到接收缓冲区中&#xff0c;如果缓冲区都满了&#xff0c;那么处理不过来就只能丢弃&#xff0c;所以需要控制发送方的速率…

【Go】三、Go并发编程

并发编程 我们主流的并发编程思路一般有&#xff1a;多进程、多线程 但这两种方式都需要操作系统介入&#xff0c;进入内核态&#xff0c;是十分大的时间开销 由此而来&#xff0c;一个解决该需求的技术出现了&#xff1a;用户级线程&#xff0c;也叫做 绿程、轻量级线程、协…

猫头虎分享已解决Bug || Spring Error: Request method ‘POST‘ not supported

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

06-OpenFeign-使用HtppClient连接池

默认下OpenFeign使用URLConnection 请求连接&#xff0c;每次都需要创建、销毁连接 1、添加ApacheHttpClient依赖 <!-- 使用Apache HttpClient替换Feign原生httpclient--><dependency><groupId>org.apache.httpcomponents</groupId><artifact…

springboo冬奥会科普平台源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理平台应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

EMC学习笔记(二十二)降低EMI的PCB设计指南(二)

降低EMI的PCB设计指南&#xff08;二&#xff09; 1.电源和地概述2.电感量3.两层板和四层板4.单层和双层设计中的微控制器接地5.信号返回地6.模拟、数字信号与大功率电源7.模拟电源引脚和模拟参考电源8.四层板电源设计参考注意事项 tips&#xff1a;资料主要来自网络&#xff0…

Apache网站部署

站点添加及linux防火墙和selinux启动和停止 apache站点添加 linux系统防火墙和selinux起停 1、防火墙firewall操作 查看防火墙的状态&#xff0c;如下&#xff08;默认开启&#xff09;&#xff1a; systemctl status firewalld 关闭服务 systemctl stop firewalld 关闭…

《向量数据库指南》——Milvus Cloud「删除」:眼见未必为实

“执行 Collection 中的 delete 操作后,再次调用 num_entities 检查集合中的数据的条数,和删除前一致, delete 不能从物理层面上删除数据吗?”“删除的数据还能被查到是为什么?”“请问下删除 collection 后,磁盘大小没有恢复,该怎么处理?”社区中关于“删除”讨论最多…

EMC学习笔记(二十一)降低EMI的PCB设计指南(一)

降低EMI的PCB设计指南&#xff08;一&#xff09; 1.概述2.射频3.连接器与过孔元件4.静态引脚和动态引脚和输入5.基本回路6.差模与共模 tips&#xff1a;资料主要来自网络&#xff0c;仅供学习使用。 1.概述 印刷电路板(PCB)的一般布局准则&#xff0c;基本上都有相对的文件进…

2024.2.5

#include<stdio.h> #include<string.h> #include<math.h> #include<stdlib.h> typedef int datatype; //定义结点结构体 typedef struct Node {datatype data;struct Node *next; }*node; //创建结点 node creat_node() {node s(node)malloc(sizeof(st…

基于SSM的网络在线考试系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的网络在线考试系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring …

(十八)springboot实战——spring securtity注解方式的授权流程源码解析

前言 在上一节内容中&#xff0c;我们介绍了如何在FilterSecurityInterceptor过滤器中处理用户的授权流程&#xff0c;并分析了其源码&#xff0c;spring security还提供了方法级别的授权方式&#xff0c;通过EnableMethodSecurity注解启用权限认证流程&#xff0c;只需要在方…

【数据结构】链表OJ面试题4(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 给定一个链表&#xff0c;判断链表中是否有环。http://t.csdnimg.cn/Rcdyc 记录每天的刷题&#xff0c;继续坚持&#xff01; 2.OJ题目训练 10. 给定一个链表&#xff0c;返回链表开始…

【doghead】uv_loop_t的创建及线程执行

worker测试程序,类似mediasoup对uv的使用,是one loop per thread 。创建一个UVLoop 就可以创建一个uv_loop_t Transport 创建一个: 试验配置创建一个: UvLoop 封装了libuv的uv_loop_t ,作为共享指针提供 对uv_loop_t 创建并初始化

【CV论文精读】【MVDet】Multiview Detection with Feature Perspective Transformation

0.论文摘要 合并多个摄像机视图进行检测减轻了拥挤场景中遮挡的影响。在多视图检测系统中&#xff0c;我们需要回答两个重要问题。首先&#xff0c;我们应该如何从多个视图中聚合线索&#xff1f;第二&#xff0c;我们应该如何从空间上相邻的位置聚集信息&#xff1f;为了解决…

【机器学习】数据清洗之识别缺失点

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步…

Android Studio 安装Flutter插件但是没法创建项目

Android Studio 安装Flutter插件但是没法创建项目 如果你在Android Studio已经安装了Dart、Flutter插件&#xff0c;但是不能创建Flutter项目。 原因是因为Android Studio的版本更新&#xff0c;Android APK Support这个插件没被选中。 一旦勾选这个插件之后&#xff0c;就能…

超级干货:ArcGIS的那些花样技巧

本篇是工作过程中收集的一些ArcGIS相关的技巧和问题解决思路。总有一些坑是你也踩过的&#xff0c;希望可以帮到你。 1、筛选工具中的SQL语句用法 DLMC IN (水田,水浇地) 筛选DLMC字段值为水田或水浇地的图斑 DLMC IS NOT NULL 筛选DLMC字段值不为空的图斑 DLMC LIKE(%水%…

【Linux】线程池线程安全的单例模式和STL读者写者问题

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;优惠多多。&#xff08;联系我有折扣哦&#xff09; 文章目录 1. 线程池1.1 线程池是什么1.2 为什么要有线程池1.3 线程池的应用场景1.4 线程池的任…