springboot基于netty加websocket实现高性能IM系统案例(2)

前言

上一节我们简单介绍了一下im的一些基础概念和技术,这一节我们来说一下聊天流程的具体实现和相关代码

服务端与客户端的概念

上次我们讲了websocket和它的一些应用,那为什么我们要用websocket呢?这就要说一下聊天背后的逻辑了,聊天涉及到的过程是 客户端->服务端->客户端的一个流程,也就是说,需要客户端与服务端的双向通信;而上期说过,websocket的一大特点就是双向通信,这意味着客户端和服务器可以同时独立地向对方发送数据,而不需要等待对方的响应。

服务端客户端连接过程

这里涉及到两个地方,服务端向客户端推送消息,客户端向服务端推送消息;这里首先是连接时候的握手,要进行一个双向绑定,代码如下

双向绑定分别是建立用户唯一标识和channel的绑定,确保消息推送时能够找到相关channel,channel在这里就是一个客户端与服务端连接的一个桥梁,服务端可以通过channel推送消息到指定客户端,一次连接对应一个channel;同时还要绑定channel和用户唯一标识的关系,这样可以通过channel将用户移除在线列表;具体代码如下

可能这样看一点懵,channelGroup和User_channel的作用是干什么的呢?会在哪里用到呢?为什么channel要关联user的信息?

这还需要结合开启连接、关闭连接、推送消息等来说;

websocket事件处理

为什么要维护一个user和channel的对应Map?

通过消息推送流程我们可以看到,当我后端要给指定的人推送消息的时候,我就会用到这个关系,比如我要给a推送消息,但我没法直接根据a的信息做到精准推送,我要先拿到他的channel,根据channel去推送消息,才能做到精准推送;

那为什么要维护channel和user信息的关系(channel为主动)?

因为当一个用户的连接关闭时候,我需要移除他在Map中的数据,而我关闭的时候并不能拿到用户信息,只能拿到一个channel,但我Map中userId为键,总不能每次移除的时候在遍历一遍整个map数据吧,毕竟大家也都清楚,聊天人数是很多的,这样做即对性能有影响,也不合理,因此我们将每一个channel都携带上用户信息,这样拿到channel的时候,就可以取出这个userId,然后根据键移除对应Map中的数据;

为什么要每次移除呢?一个原因是性能的考虑,如果一直堆积,可能最后会报内存溢出,每次添加的和查询的时候也会造成效率的降低;另一个原因就是,这一条数据是用户在线的一个体现,我想判断有多少人在线,谁在线的时候,就可以通过这个map的数据拿到对应结果,只要根据userId找到对应channel,就说明对方在线;

消息的收发

消息的收发应该从服务端和客户端两个方面来看,因为这是一个双向通信的过程,如果只单看其中一端,无法了解整个流程和逻辑,下面先说服务端的消息收发:

服务端

在netty中,这个方法是对读到的消息进行操作,业务上因为客户端发消息过来是json类型,所以第一步要做一个类型转化,然后进入到我自定义的处理器工厂,这个作用是用来处理不同类型的消息,比如登陆、登出、单聊、群聊、心跳包等不同类型的消息,由于不同类型的消息处理逻辑不同,因此用工厂模式加策略模式来解决这个问题,下面是工厂和自定义处理器的部分代码:

这里的逻辑大概就是消息进入channelRead方法,解析消息,然后根据消息类型在工厂中进入对应的过滤器中执行对应逻辑;设计模式不展开说了,后面会单独说一下常用的设计模式;

然后以私聊消息为例子说一下消息发送的流程:

首先要根据redis自增序列,获得当前消息的一个会话序列号,用来进行一个消息的排序和记录会话的进度,然后这个时候首先向发送方的客户端推送一个回执,表示消息成功发送,然后向接受方的客户端发送对应消息(前提对方在线),对方的id在消息类的字段中有体现,因此可以通过id拿到其对应channel,回执的一个好处就是保证了消息投递的可靠性,参考了tcp连接的3次握手机制,最后mq异步存储消息到数据库;

客户端

这里以vue3代码为例子,python同样可以作为websocket的客户端

这里的逻辑和服务端一一对应,当打开这个页面的时候,websocket会进入onopen方法,然后到服务端进行握手操作,进行双向绑定,当点击发送消息时,消息会到服务端的channelRead方法,然后由服务端对当前消息进行分发,确定服务端收到消息后,再将消息添加到消息列表,同时websocket是实时更新,不需要手动刷新页面,然后客户端执行onclose时候,服务端会断开连接,执行remove操作;

整个过程就是由客户端进行连接,然后服务端双向绑定,客户端发送消息到服务端,服务端进行分发的一个流程;

websocket连接测试工具

如果说对vue或者说对python不熟悉,又想进行websocket测试怎么办?这里给大家推荐一个网站

WebSocket在线测试工具 (wstool.js.org)

在这里指定连接信息,可以拼接上个人信息,例如用户id,点击开启连接,如果两边都没问题,就会连接成功

由于我们发送消息是json格式,所以在这里发送消息,也要写成json格式,不然会在json转化时报错;

如图,我们消息成功发到服务端并且得到回调,那如何体现两个客户端之间的交互?

开两个一起就可以:

可以看到用户1111成功收到3333的消息,这也算是一个简易的聊天室了。

结语

从上面代码,大家可能感到疑惑,为什么remove时要加锁?为什么在websocket不能直接注解注入对象?这个我们放到下期来讲,同时下期会给大家补充一下心跳包和断线重连以及一些其它业务,等个人项目完成后,相关代码都会开源,敬请期待。

  • 17
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现局域网音视频通话可以用Spring Boot作为后端框架Netty作为网络通信框架WebSocket作为实现双向通信的协议。以下是一个简单的实现过程: 1. 首先需要搭建一个Spring Boot项目,可以使用Spring Initializr来快速生成项目。在pom.xml中添NettyWebSocket的依赖,例如: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.25.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建一个WebSocket处理器类,用来处理WebSocket的连接、关闭和消息收发等逻辑。例如: ```java @Component @ServerEndpoint("/video-chat") public class VideoChatHandler { private static final Logger LOGGER = LoggerFactory.getLogger(VideoChatHandler.class); @OnOpen public void onOpen(Session session) { LOGGER.info("WebSocket opened: {}", session.getId()); } @OnMessage public void onMessage(String message, Session session) { LOGGER.info("Received message: {}", message); // TODO: 处理收到的消息 } @OnClose public void onClose(Session session) { LOGGER.info("WebSocket closed: {}", session.getId()); } @OnError public void onError(Throwable error) { LOGGER.error("WebSocket error", error); } } ``` 3. 在Spring Boot的配置类中添WebSocket的配置,例如: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private VideoChatHandler videoChatHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(videoChatHandler, "/video-chat").setAllowedOrigins("*"); } } ``` 4. 使用Netty实现音视频的传输。可以使用Netty提供的UDP协议来实现多人音视频通话,也可以使用TCP协议来实现点对点的音视频通话。需要根据实际情况选择相应的协议,这里以TCP协议为例: ```java @Component public class VideoChatServer { private static final Logger LOGGER = LoggerFactory.getLogger(VideoChatServer.class); @Value("${server.video-chat.port}") private int port; @PostConstruct public void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // TODO: 添音视频相关的编解码器和处理器 } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture future = bootstrap.bind(port).sync(); LOGGER.info("Video chat server started on port {}", port); future.channel().closeFuture().sync(); } catch (InterruptedException e) { LOGGER.error("Video chat server interrupted", e); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } ``` 5. 在WebSocket处理器中实现音视频数据的收发逻辑。当收到音视频数据时,可以将数据转发给所有连接的WebSocket客户端。例如: ```java @Component @ServerEndpoint("/video-chat") public class VideoChatHandler { private static final Logger LOGGER = LoggerFactory.getLogger(VideoChatHandler.class); private List<Session> sessions = new CopyOnWriteArrayList<>(); @OnOpen public void onOpen(Session session) { LOGGER.info("WebSocket opened: {}", session.getId()); sessions.add(session); } @OnMessage public void onMessage(ByteBuffer buffer, Session session) throws IOException { LOGGER.info("Received video data from {}", session.getId()); byte[] data = new byte[buffer.remaining()]; buffer.get(data); for (Session s : sessions) { if (s.isOpen() && !s.getId().equals(session.getId())) { s.getBasicRemote().sendBinary(ByteBuffer.wrap(data)); } } } @OnClose public void onClose(Session session) { LOGGER.info("WebSocket closed: {}", session.getId()); sessions.remove(session); } @OnError public void onError(Throwable error) { LOGGER.error("WebSocket error", error); } } ``` 6. 在前端页面中使用WebSocket实现音视频通话。可以使用WebRTC等技术来实现音视频采集、编解码、传输等功能。这里不再赘述。 以上就是一个简单的局域网音视频通话的实现过程。需要注意的是,音视频通话涉及到的技术较多,需要根据实际情况进行选择和配置。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值