即时通讯代码优化
在线用户逻辑修复
在进行测试时,发现当前代码有个问题,如果test1在服务器进行连接,本地的test2给test1发消息,虽然test1能收到服务器上的信息,但是本地服务日志中会报teset1不在线,需要对该种情况进行修复
修复方案:使用redis存储在线用户
- WebSocketEndpoint
利用setBit记录登录用户,key为用户名的hashcode,即便有可能冲突,但是概率较小,可以接受。
package com.example.im.endpoint;import com.example.im.app.service.WebSocketMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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.ServerEndpoint;
import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;/*** @author PC*/
@Component
@ServerEndpoint("/ws")
public class WebSocketEndpoint {private final static Logger logger = LoggerFactory.getLogger(WebSocketEndpoint.class);public static final ConcurrentHashMap<Integer, WebSocketEndpoint> WEB_SOCKET_ENDPOINT_MAP = new ConcurrentHashMap<>();/*** redis中用户SessionMap*/public static final String ONLINE_USER = "cus:ws:online-user";private Session session;private static WebSocketMessageService webSocketMessageService;private static RedisTemplate<String, String> redisTemplate;@Autowiredpublic void setWebSocketMessageService(WebSocketMessageService webSocketMessageService) {WebSocketEndpoint.webSocketMessageService = webSocketMessageService;}@Autowiredpublic void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {WebSocketEndpoint.redisTemplate = redisTemplate;}/*** 打开ws连接** @param session 会话*/@OnOpenpublic void onOpen(Session session) {//连接成功String userName = getUserName(session);logger.info("The connection is successful:" + getUserName(session));this.session = session;//是有hash冲突的可能性的,不过触发概率很低,可以忽略int hashCode = userName.hashCode();redisTemplate.opsForValue().setBit(ONLINE_USER, hashCode, true);WEB_SOCKET_ENDPOINT_MAP.put(hashCode, this);}/*** 断开ws连接** @param session 会话*/@OnClosepublic void onClose(Session session) {String userName = getUserName(session);int hashCode = userName.hashCode();redisTemplate.opsForValue().setBit(ONLINE_USER, hashCode, false);WEB_SOCKET_ENDPOINT_MAP.remove(hashCode);//断开连接logger.info("Disconnect:" + userName);}/*** 接收到的消息** @param message 消息内容*/@OnMessagepublic void onMessage(String message, Session session) {//接收消息String sendUserName = getUserName(session);webSocketMessageService.sendMessage(sendUserName, message);}private String getUserName(Session session) {return Optional.ofNullable(session.getRequestParameterMap().get("userName")).orElse(new ArrayList<>()).stream().findFirst().orElse("anonymous_users");}public Session getSession() {return session;}public void setSession(Session session) {this.session = session;}
}
- DefaultSendExecutor
适配改变类型后的WEB_SOCKET_ENDPOINT_MAP,并调整代码结构
package com.example.im.endpoint;import com.example.im.app.service.WebSocketMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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.ServerEndpoint;
import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;/*** @author PC*/
@Component
@ServerEndpoint("/ws")
public class WebSocketEndpoint {private final static Logger logger = LoggerFactory.getLogger(WebSocketEndpoint.class);public static final ConcurrentHashMap<Integer, WebSocketEndpoint> WEB_SOCKET_ENDPOINT_MAP = new ConcurrentHashMap<>();/*** redis中用户SessionMap*/public static final String ONLINE_USER = "cus:ws:online-user";private Session session;private static WebSocketMessageService webSocketMessageService;private static RedisTemplate<String, String> redisTemplate;@Autowiredpublic void setWebSocketMessageService(WebSocketMessageService webSocketMessageService) {WebSocketEndpoint.webSocketMessageService = webSocketMessageService;}@Autowiredpublic void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {WebSocketEndpoint.redisTemplate = redisTemplate;}/*** 打开ws连接** @param session 会话*/@OnOpenpublic void onOpen(Session session) {//连接成功String userName = getUserName(session);logger.info("The connection is successful:" + getUserName(session));this.session = session;//是有hash冲突的可能性的,不过触发概率很低,可以忽略int hashCode = userName.hashCode();redisTemplate.opsForValue().setBit(ONLINE_USER, hashCode, true);WEB_SOCKET_ENDPOINT_MAP.put(hashCode, this);}/*** 断开ws连接** @param session 会话*/@OnClosepublic void onClose(Session session) {String userName = getUserName(session);int hashCode = userName.hashCode();redisTemplate.opsForValue().setBit(ONLINE_USER, hashCode, false);WEB_SOCKET_ENDPOINT_MAP.remove(hashCode);//断开连接logger.info("Disconnect:" + userName);}/*** 接收到的消息** @param message 消息内容*/@OnMessagepublic void onMessage(String message, Session session) {//接收消息String sendUserName = getUserName(session);webSocketMessageService.sendMessage(sendUserName, message);}private String getUserName(Session session) {return Optional.ofNullable(session.getRequestParameterMap().get("userName")).orElse(new ArrayList<>()).stream().findFirst().orElse("anonymous_users");}public Session getSession() {return session;}public void setSession(Session session) {this.session = session;}
}
重复代码提取
在增加了stream和redis渠道后,消息监听处的代码有大段重复,可以进行提取处理
- MessageInfoUtil
package com.example.im.infra.executor.send.util;import com.example.im.infra.executor.send.DefaultSendExecutor;
import com.example.im.infra.executor.send.dto.MessageInfo;
import com.example.im.infra.util.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;/*** @author PC* MessageInfo工具类*/
@Component
public class MessageInfoUtil {private final static Logger logger = LoggerFactory.getLogger(MessageInfoUtil.class);private DefaultSendExecutor defaultSendExecutor;@Autowiredpublic void setDefaultSendExecutor(DefaultSendExecutor defaultSendExecutor) {this.defaultSendExecutor = defaultSendExecutor;}public MessageInfo sendMessageByMessageInfoByte(byte[] messageInfoByte) {String messageJson = new String(messageInfoByte, StandardCharsets.UTF_8);MessageInfo messageInfo = JsonUtils.toObjectByTypeReference(messageJson, new TypeReference<MessageInfo>() {});switch (messageInfo.getScopeOfSending()) {case USER:defaultSendExecutor.sendToUser(messageInfo.getSendUserName(), messageInfo.getMessage());break;case ALL:defaultSendExecutor.sendToAll(messageInfo.getSendUserName(), messageInfo.getMessage());break;default://一般来说不会出现该情况,除非用户覆盖了ScopeOfSending,后续可以开个扩展发送范围的口子logger.warn("invalid sending range:" + messageInfo.getScopeOfSending().getScopeCode());break;}return messageInfo;}
}
- RedisMessageListener
package com.example.im.infra.executor.send.redis;import com.example.im.infra.executor.send.util.MessageInfoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;/*** @author PC* redis监听*/
@Component
public class RedisMessageListener implements MessageListener {private final static Logger logger = LoggerFactory.getLogger(RedisMessageListener.class);private MessageInfoUtil messageInfoUtil;@Autowiredpublic void setMessageInfoUtil(MessageInfoUtil messageInfoUtil) {this.messageInfoUtil = messageInfoUtil;}@Overridepublic void onMessage(Message message, byte[] pattern) {logger.debug("send redis info");messageInfoUtil.sendMessageByMessageInfoByte(message.getBody());}
}
- StreamMessageListener
package com.example.im.infra.executor.send.stream;import com.example.im.infra.executor.send.util.MessageInfoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.util.function.Function;/*** @author PC* 消息队列监听*/
@Component
public class StreamMessageListener {private final static Logger logger = LoggerFactory.getLogger(StreamSendExecutor.class);private MessageInfoUtil messageInfoUtil;@Autowiredpublic void setMessageInfoUtil(MessageInfoUtil messageInfoUtil) {this.messageInfoUtil = messageInfoUtil;}@Beanpublic Function<Flux<Message<byte[]>>, Mono<Void>> listener() {logger.debug("send stream info");return messageInfoFlux -> messageInfoFlux.map(message -> messageInfoUtil.sendMessageByMessageInfoByte(message.getPayload())).then();}
}
未用到的bean不加载
当未用到某些渠道时,无需进行相关配置,如使用了redis渠道,yml中就无需配置kafka信息
Redis渠道
调整config文件,当渠道非redis时对redis渠道相关bean不进行加载
- WebSocketConfig
package com.example.im.config;import com.example.im.infra.handle.ImRejectExecutionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;import javax.annotation.Resource;/*** @author PC*/
@Configuration
@EnableWebSocket
public class WebSocketConfig {@Resourceprivate WebSocketProperties webSocketProperties;@Beanpublic ServerEndpointExporter serverEndpoint() {return new ServerEndpointExporter();}/**** 配置线程池* @return 线程池*/@Beanpublic TaskExecutor taskExecutor() {WebSocketProperties.ExecutorProperties executorProperties = webSocketProperties.getExecutorProperties();ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数executor.setCorePoolSize(executorProperties.getCorePoolSize());// 设置最大线程数executor.setMaxPoolSize(executorProperties.getMaxPoolSize());// 设置队列容量executor.setQueueCapacity(executorProperties.getQueueCapacity());// 设置线程活跃时间(秒)executor.setKeepAliveSeconds(executorProperties.getKeepAliveSeconds());// 设置默认线程名称executor.setThreadNamePrefix("im-");// 设置拒绝策略executor.setRejectedExecutionHandler(new ImRejectExecutionHandler());// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);return executor;}
}
- RedisConfiguration
缓存用到了redis,RedisTemplate需要加载
package com.example.im.config;import com.example.im.infra.executor.send.redis.RedisMessageListener;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author PC*/
@Configuration
public class RedisConfiguration {/*** redisTemplate配置** @return RedisTemplate*/@Beanpublic RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringRedisSerializer);redisTemplate.setValueSerializer(stringRedisSerializer);redisTemplate.setStringSerializer(stringRedisSerializer);redisTemplate.setDefaultSerializer(stringRedisSerializer);redisTemplate.setHashKeySerializer(stringRedisSerializer);redisTemplate.setHashValueSerializer(stringRedisSerializer);redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;}@Bean@ConditionalOnProperty(name = "cus.ws.communication-type", havingValue = "redis")RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,MessageListenerAdapter listenerAdapter) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);// 订阅一个或多个频道container.addMessageListener(listenerAdapter, new PatternTopic("redis-websocket-*"));return container;}@Bean@ConditionalOnProperty(name = "cus.ws.communication-type", havingValue = "redis")MessageListenerAdapter listenerAdapter(RedisMessageListener redisMessageListener) {return new MessageListenerAdapter(redisMessageListener);}
}
- RedisMessageListener
@Component
@ConditionalOnProperty(name = "cus.ws.communication-type", havingValue = "redis")
public class RedisMessageListener implements MessageListener
- RedisSendExecutor
@Component
@ConditionalOnProperty(name = "cus.ws.communication-type", havingValue = "redis")
public class RedisSendExecutor extends AbstractBaseSendExecutor
Kafka渠道
- StreamMessageListener
@Component
@ConditionalOnProperty(name = "cus.ws.communication-type", havingValue = "stream")
public class StreamMessageListener
- StreamSendExecutor
@Component
@ConditionalOnProperty(name = "cus.ws.communication-type", havingValue = "stream")
public class StreamSendExecutor extends AbstractBaseSendExecutor
参考资料
[1].im项目
相关文章:

即时通讯代码优化
在线用户逻辑修复 在进行测试时,发现当前代码有个问题,如果test1在服务器进行连接,本地的test2给test1发消息,虽然test1能收到服务器上的信息,但是本地服务日志中会报teset1不在线,需要对该种情况进行修复…...

jmeter学习(8)界面的使用
1、新建test plan 3、 打开文件 4、保存 5、剪切 6、复制 7、粘贴 8、所有线程组展开 9、所有线程组收缩 10、置灰,操作后无法使用 11、执行 13、清空当前线程组结果 14、清空所有线程组结果 15、函数助手 搜索,可以用于搜索某个请求&#x…...

记录一次hiveserver2卡死(假死)问题
问题描述 给开发人员开通了个账号,连接hive进行查询,后来发现,hive服务有时候会卡死,查询不了,连不上(所有账号/客户端都连不上hive),但在chd里面看监控,服务器资源状态…...

【ios】在 SwiftUI 中实现可随时调用的加载框
在 SwiftUI 项目中实现一个自定义的加载框(loading)功能,可以在任意位置调用,以便显示加载动画或者进度条。下面的教程将详细讲解如何创建一个可复用的 Loading 组件,并通过通知机制控制其显示和隐藏。 先上效果&…...

字符、解释型语言、编程语言的互操作、输出
字符 同样是1,有人看到的是数字,有人看到的是字符,还有人看到的是一个小目标。 不同语言的字符 正则表达式把字符分成普通字符和元字符,元字符为了搭配匹配。比如.代表任意非换行字符,这对于通配很简便,用\…...

基于Python的自然语言处理系列(39):Huggingface中的解码策略
在自然语言生成任务中,如何选择下一步的单词或者词语对生成的文本质量影响巨大。Huggingface 提供了多种解码策略,可以在不同的场景下平衡流畅度、创造力以及生成效率。在这篇文章中,我们将逐步介绍 Huggingface 中的几种常见解码策略&#x…...

如何将视频格式转为mp4?好好看看下面这几个方法
如何将视频格式转为mp4?在数字化时代,视频已成为信息传播与娱乐消遣的重要载体。无论是学习、工作还是日常生活,我们几乎每天都会接触到各式各样的视频内容。然而,不同设备、平台或软件生成的视频文件往往采用不同的编码格式&…...

景区智慧公厕系统,监测公厕异味,自动清洁除臭
随着旅游业的快速发展,景区的公共厕所管理成为提升游客体验的重要环节。传统的公厕管理方式存在诸多不足,如卫生条件差、异味严重等问题。为了改善这些问题,许多景区开始采用智慧公厕系统。这种系统能够实时监测公厕内的异味,并自…...

GitLab CVE-2024-6389、CVE-2024-4472 漏洞解决方案
极狐GitLab 近日发布安全补丁版本17.3.2, 17.2.5, 17.1.7,修复了17个安全漏洞,本分分享其中两个漏洞 CVE-2024-6389、CVE-2024-4472 两个漏洞详情及解决方案。 极狐GitLab 正式推出面向 GitLab 老旧版本免费用户的专业升级服务,为 GitLab 老…...

hashCode的底层原理
HashCode是计算机科学中一个广泛使用的概念,特别是在Java等编程语言中,它扮演着重要的角色。为了详细解释hashCode的底层原理,以下从几个方面进行阐述: 一、hashCode的基本概念 HashCode,即哈希码,是一个将…...

hadoop_hdfs详解
HDFS秒懂 HDFS定义HDFS优缺点优点缺点 HDFS组成架构NameNodeDataNodeSecondary NameNodeClient NameNode工作机制元数据的存储启动流程工作流程 Secondary NameNode工作机制checkpoint工作流程 DataNode工作机制工作流程数据完整性 文件块大小块太小的缺点块太大的缺点 文件写入…...

【Linux】Linux命令行与环境变量
1.命令行 前⾯写C语⾔时,很少关注过 main 函数的参数,也没有考虑过 main 为什么会有参 数。 实际上在C语⾔中, main 函数⼀共有三个参数,在命令⾏部分先关注前两个参数: 1. argc:表示 main 函数接收到参…...

改变函数调用上下文:apply与call方法详解及实例
目录 改变函数调用上下文:apply与call方法详解及实例 一、什么是 apply 方法? 1、apply 语法 2、apply 示例 二、什么是 call 方法? 1、call 语法 2、call 示例 三、apply 和 call 的共同与差异 1、apply 和 call 的共同点 2、apply…...

k8s中的微服务
一、什么是微服务 用控制器来完成集群的工作负载,那么应用如何暴漏出去?需要通过微服务暴漏出去后才能被访问 Service是一组提供相同服务的Pod对外开放的接口。 借助Service,应用可以实现服务发现和负载均衡。 service默认只支持4层负载均…...

树莓派--AI视觉小车智能机器人--1.树莓派系统烧入及WiFi设置并进入jupyterlab
一、Raspberry Pi 系统烧入 使用树莓派,我们是需要有操作系统的。默认情况下,树莓派会在插入的SD卡上查找操作系统。这需要一台电脑将存储设备映像为引导设备,并将存储设备插入该电脑。大多数树莓派用户选择microSD卡作为引导设备。 1.1 下载…...

MacOS安装BurpSuite
文章目录 一、下载地址二、下载注册机三、安装教程四、启动burpsuit五、免责声明 一、下载地址 https://portswigger-cdn.net/burp/releases/download?productpro&version2024.7.1&typeMacOsx二、下载注册机 https://github.com/NepoloHebo/BurpSuite-BurpLoaderKey…...

【AI工具大全】《史上最全的AI工具合集》
一.AI编程类工具 1.CodeArts Snap CodeArts Snap是华为云研发的智能开发助手,覆盖软件开发全生命周期,提供代码生成、研发知识问答、智能协同等功能。通过自然语言编程,它能自动生成代码、解释代码逻辑、提供调试与检查,提升开发效率和软件质量。 2.ModelArts ModelArt…...

qt继承结构
一、 继承结构 所有的窗口类均继承自QWidget类,因此QWidget类本身包含窗口的特性。QWidget对象本身既可以作为独立窗口,又可以作为组件(子窗口)。 通过构造函数可以创建以上两种形态的QWidget: // 参数1:使…...

【HCIA复习作业】综合拓扑实验(已施工完)
一、实验要求 1.学校内部的HTTP客户端可以正常通过域名www.baidu.com访问到百度网络中的HTTP服务器 2.学校网络内部网段基于192.168.1.0/24划分,PC1可以正常访问3.3.3.0/24网段,但是Pc2不允许 3.学校内部路由使用静态路由,R1和R2之间两条链路…...

网络基础知识:交换机关键知识解析
了解交换机的关键知识对网络工程师至关重要。 以下是交换机的基础知识解析,包括其基本概念、工作原理和关键技术点: 01-交换机的基本概念 交换机是一种网络设备,用于在局域网(LAN)中连接多个设备,如计算机…...

基于System.js的微前端实现(插件化)
目录 写在前面 一、微前端相关知识 (一)概念 (二) 优势 (三) 缺点 (四)应用场景 (五)现有框架 1. qiankun 2. single-spa 3. SystemJ…...

MedSAM2调试安装与使用记录
目录 前言一、环境准备多版本cuda切换切换cuda版本二 安装CUDNN2.1 检查cudnn 二、使用步骤1.安装虚拟环境2.测试Gradio3.推理 总结 前言 我们在解读完MedSAM之后,迫不及待想尝尝这个技术带来的福音,因此验证下是否真的那么6。这不,新鲜的使…...

Linux 进程终止和进程等待
目录 0.前言 1. 进程终止 1.1 进程退出的场景 1.2 进程常见退出方法 1.2.1 正常退出 1.2.2 异常退出 2. 进程等待 2.1 进程等待的重要性 2.2 进程等待的方法 2.2.1 wait() 方法 2.2.2 waitpid() 方法 2.3 获取子进程 status 2.4 阻塞等待和非阻塞等待 2.4.1 阻塞等待 2.4.2 非阻…...

如何查看默认网关地址:详细步骤
在日常的网络配置与故障排查中,了解并正确查看默认网关地址是一项基础且至关重要的技能。默认网关是连接本地网络与外部网络(如互联网)的关键节点,它扮演着数据包转发的重要角色。无论是家庭网络、办公室网络还是更复杂的网络环境…...

什么是方法的返回值?方法有哪几种类型?静态方法为什么不能调用非静态成员?静态方法和实例方法有何不同?
什么是方法的返回值?方法有哪几种类型? 方法的返回值 是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用是接收出结果,使得它可以用于其他的操作! 我们可以…...

Qt开发——Qt项目打包、整合以及生成安装包保姆级教程(Windows系统)
目录 Windows下打包Qt项目 1.Qt系统环境变量的配置 2.打包 3.打包整合为一个.exe文件 4.生成安装包 做完了一个Qt项目之后,要干嘛呢,很显然要打包给别人,让别人也能使用这个软件,本期我们就来学习Qt打包,本期内容分…...

大数据-180 Elasticsearch - 原理剖析 索引写入与近实时搜索
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...

大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...

【Java后端】之 ThreadLocal 详解
想象一下,你有一个工具箱,里面放着各种工具。在多人共用这个工具箱的时候,很容易出现混乱,比如有人拿走了你的锤子,或者你找不到合适的螺丝刀。为了避免这种情况,最好的办法就是每个人都有自己独立的工具箱…...

2.链表(代码随想录——python版本)
2.链表(代码随想录——python版本) 链表的概念: 链表是由指针串联在一起的线性结构,一个节点(node)由两部分组成: 数据域——用来存储数据;指针域——用来指向下一个节点…...