如何用WebSocket打造Web端IM即时通讯聊天

WebSocket如今在Web端即时通讯技术应用里使用广泛,不仅用于传统PC端的网页里,也被很多移动端开发者用于基于HTML5的混合APP里。对于想要在基于Web的应用里添加IM、推送等实时通信功能,WebSocket几乎是必须要掌握的技术。

相比 HTTP 协议来说,WebSocket 协议对大多数后端开发者是比较陌生的。

相对而言:WebSocket 协议重点是提供了服务端主动向客户端发送数据的能力,这样我们就可以完成实时性较高的需求。例如:聊天 IM 即使通讯功能、消息订阅服务、网页游戏等等。

同时:因为 WebSocket 使用 TCP 通信,可以避免重复创建连接,提升通信质量和效率。

友情提示:

这里有个误区,WebSocket 相比普通的 Socket 来说,仅仅是借助 HTTP 协议完成握手,创建连接。后续的所有通信,都和 HTTP 协议无关。

要想使用WebSocket,一般有如下几种解决方案可选:

    1)方案一:Spring WebSocket;

    2)方案二:Tomcat WebSocket;

    3)方案三:Netty WebSocket。

目前笔者手头有个涉及到 IM 即使通讯的项目,采用的是方案三。

主要原因是:我们对 Netty 框架的实战、原理与源码,都相对熟悉一些,所以就考虑了它。并且,除了需要支持 WebSocket 协议,我们还想提供原生的 Socket 协议。

如果仅仅是仅仅提供 WebSocket 协议的支持,可以考虑采用方案一或者方案二,在使用上,两个方案是比较接近的。相比来说,方案一 Spring WebSocket 内置了对 STOMP 协议的支持。

不过:本文还是采用方案二“Tomcat WebSocket”来作为入门示例。咳咳咳,没有特殊的原因,主要是开始写本文之前,已经花了 2 小时使用它写了一个示例。实在是有点懒,不想改。如果能重来,我要选李白,哈哈哈哈~

当然,不要慌,方案一和方案二的实现代码,真心没啥差别。

在开始搭建 Tomcat WebSocket 入门示例之前,我们先来了解下 JSR-356 规范,定义了 Java 针对 WebSocket 的 API :即 Javax WebSocket 。规范是大哥,打死不会提供实现,所以 JSR-356 也是如此。目前,主流的 Web 容器都已经提供了 JSR-356 的实现,例如说 Tomcat、Jetty、Undertow 等等。

消息

在 HTTP 协议中,是基于 Request/Response 请求响应的同步模型,进行交互。在 Websocket 协议中,是基于 Message 消息的异步模型,进行交互。这一点,是很大的不同的,等会看到具体的消息类,感受会更明显。

因为 WebSocket 协议,不像 HTTP 协议有 URI 可以区分不同的 API 请求操作,所以我们需要在 WebSocket 的 Message 里,增加能够标识消息类型,这里我们采用 type 字段。

所以在这个示例中,我们采用的 Message 采用 JSON 格式编码。

格式如下:

{

    type: "", // 消息类型

    body: {} // 消息体

}

解释一下:

    1)type 字段,消息类型。通过该字段,我们知道使用哪个 MessageHandler 消息处理器(关于 MessageHandler ,我们在下一节中,详细解析);

    2)body 字段,消息体。不同的消息类型,会有不同的消息体;

    3)Message 采用 JSON 格式编码,主要考虑便捷性,实际项目下,也可以考虑 Protobuf 等更加高效且节省流量的编码格式。即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询

实际上:我们在该示例中,body 字段对应的 Message 相关的接口和类,实在想不到名字了。所有的 Message 们,我们都放在 cn.iocoder.springboot.lab25.springwebsocket.message 包路径下。

Message

创建 Message 接口,基础消息体,所有消息体都要实现该接口。

代码如下:

// Message.java

public interface Message {

}

目前作为一个标记接口,未定义任何操作。

解释一下:

    1)TYPE 静态属性,消息类型为 AUTH_REQUEST 。

    2)accessToken 属性,认证 Token 。

对于第2)点,在 WebSocket 协议中,我们也需要认证当前连接,用户身份是什么。一般情况下,我们采用用户调用 HTTP 登录接口,登录成功后返回的访问令牌 accessToken 。

虽然说,WebSocket 协议是基于 Message 模型,进行交互。但是,这并不意味着它的操作,不需要响应结果。例如说,用户认证请求,是需要用户认证响应的。所以,我们创建 AuthResponse 类,作为用户认证响应。

解释一下:

1)TYPE 静态属性,消息类型为 AUTH_REQUEST ;

2)code 属性,响应状态码;

3)message 属性,响应提示。

对于第1)点,实际上,我们在每个 Message 实现类上,都增加了 TYPE 静态属性,作为消息类型。下面,我们就不重复赘述了。

在本示例中,用户成功认证之后,会广播用户加入群聊的通知 Message ,使用 UserJoinNoticeRequest 。

实际上,我们可以在需要使用到 Request/Response 模型的地方,将 Message 进行拓展:

1)Request 抽象类,增加 requestId 字段,表示请求编号;

2)Response 抽象类,增加 requestId 字段,和每一个 Request 请求映射上(同时,里面统一定义 code 和 message 属性,表示响应状态码和响应提示)。

这样,在使用到同步模型的业务场景下,Message 实现类使用 Request/Reponse 作为后缀。例如说,用户认证请求、删除一个好友请求等等。

而在使用到异步模型能的业务场景下,Message 实现类还是继续 Message 作为后缀。例如说,发送一条消息,用户操作完后,无需阻塞等待结果

消息处理器

每个客户端发起的 Message 消息类型,我们会声明对应的 MessageHandler 消息处理器。这个就类似在 SpringMVC 中,每个 API 接口对应一个 Controller 的 Method 方法。

所有的 MessageHandler 们,我们都放在 cn.iocoder.springboot.lab25.springwebsocket.handler 包路径下。

代码在 cn.iocoder.springboot.lab25.springwebsocket.util 包路径下。

创建 WebSocketUtil 工具类,主要提供两方面的功能:

1)Session 会话的管理;

2)多种发送消息的方式。

整体代码比较简单,自己瞅瞅哟。

onMessage

重新实现#onMessage(Session session, String message) 方法,实现不同的消息,转发给不同的 MessageHandler 消息处理器。

这是参考 rocketmq-spring 项目的 DefaultRocketMQListenerContainer#getMessageType() 方法,进行略微修改。

如果大家对 Java 的泛型机制没有做过一点了解,可能略微有点硬核。可以先暂时跳过,知道意图即可。

<4> 处,调用 MessageHandler#execute(session, message) 方法,执行处理请求。

另外:这里增加了 try-catch 代码,避免整个执行的过程中,发生异常。如果在 onMessage 事件的处理中,发生异常,该消息对应的 Session 会话会被自动关闭。显然,这个不符合我们的要求。例如说,在 MessageHandler 处理消息的过程中,发生一些异常是无法避免的。

继续基于上述创建的三个浏览器,我们先点击「清空消息」按钮,清空下消息,打扫下上次测试展示出来的接收得到的 Message 。当然,WebSocket 的连接,不需要去断开。

在第一个浏览器中,分别发送两种聊天消息。

WebSocketUtil

因为 Tomcat WebSocket 使用的是 Session 作为会话,而 Spring WebSocket 使用的是 WebSocketSession 作为会话,导致我们需要略微修改下 WebSocketUtil 工具类。改动非常略微,点击 WebSocketUtil.java 查看下,秒懂的噢。

主要有两点:

1)将所有使用 Session 类的地方,调整成 WebSocketSession 类;

2)将发送消息,从 Session 修改成 WebSocketSession 。

消息处理器

cn.iocoder.springboot.lab25.springwebsocket.handler包路径下的消息处理器们,使用到 Session 类的地方,调整成 WebSocketSession 类。

DemoWebSocketShakeInterceptor

cn.iocoder.springboot.lab25.springwebsocket.websocket包路径下,创建 DemoWebSocketShakeInterceptor 拦截器。因为 WebSocketSession 无法获得 ws 地址上的请求参数,所以只好通过该拦截器,获得 accessToken 请求参数,设置到 attributes 中。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/126224095