线上实时通讯项目 - 项目实战(已开源)

项目目的:实现多用户角色(在主项目中为:政府监控管理员、气体量化分析员、一线设备操作员)通过线上注册登录,在同一个聊天平台内针对已上线用户进行实时沟通,未上线用户在上线后能够查看未上线时聊天室内历史消息。

后端逻辑

聊天场景:(系统内的所有用户都在一个群里)

  1. 一个用户发送的消息,在线的所有用户都能收到(核心);
  2. 针对离线的用户(已注册,暂时未登录),等该用户再次登录时,补发未看到的历史消息;
  3. 首次注册的用户,看不到之前的历史消息。

效果图

未登录主页

弹出提示框,跳转登陆界面。

 登录界面

登录成功后会提示点击跳转聊天室主页,登录失败会提示失败并点击跳转重新登陆或注册。

 注册界面

根据注册框内容进行注册,注册成功后点击跳转聊天室主页。

 登陆后主页


技术点知识预备:

1.什么是WebSocket?

        Websocket协议基于TCP协议,可以在浏览器和服务器之间打开交互式会话。可以向服务器发送消息并接收到服务器的驱动,实现了真正意义上的全双工通信,也就是双方都可以发送和接收消息,类似于拨打电话双方,可以在同一时间进行发送和接收。

        Websocket中的web就是HTTP协议,socket就是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

2.为什么选WebSocket?

        因为项目需要实现即时通讯,那么实现即时通讯大致可以分为两种模型,一种是基于HTTP的请求响应模型,主要实现方式是有轮询和长连接,还有一种是项目中使用的基于websocket的订阅通知模型,类似于我在b站关注一个up主,当这个up主更新就会推送给我。

        为什么不选择HTTP而选择websocket是因为HTTP是非持久化、单向的协议,建立连接后,只允许浏览器向服务器发送请求后,服务器才能返回相应的数据,这就会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答可能都会浪费在相同的头部信息上。而websocket只需要浏览器和服务器通过HTTP协议进行一个握手,然后单独建立一条tcp的通信通道进行数据的传送就可以实现全双工通信。

3.Servlet和Endpoint

        相同点:Endpoint这个类的对象,是由Tomcat给我们创建的,这个和Servlet类的对象创建是类似的。

        区别:

  •         Servlet对象,在整体的Tomcat运行过程中,只会实例化一次(全局的、单例的)
  •         Endpoint对象,是每次有新的webSocket建立,就会实例化一个对象

        servlet对象的属性,在每次请求 /XXX 时,都是同一个数据(因为只有一个对象,所以属性是同一个)共享的

        Endpoint对象的属性,是每次建立 /XXX-ws 时,都是各自对象的数据,互相不是同一个数据

 

一、简单分析

        需要一个在线用户管理机制(用来记录当前在线的用户信息),可以去动态、正确维护该信息,即有 用户登录添加到在线列表,有 用户退出从在线列表删除

用户发送消息的数据流转流程:

用户发送消息的数据流转流程

初期架构图:

初期架构图

从上向下解释: 

最上方的是 websocket 和前端的相互通信;

再往下,只要建立一条通信通道,就会生成一个 websocket 连接,就会生成一个 Endpoint 对象,所以会有多个 Endpoint 对象,每个 Endpoint 对象维护自己的在线用户信息,并去维护当时的session;

黑色路线:当有用户要发送消息时,前端把数据提供给对应的 Endpoint ,对应到Endpoint 对象上,然后消息会被发送到后端程序的后端(期间通过dao层保存消息到数据库),找到所有在线用户,针对每个在线用户再将消息发送回去;

蓝色路线:只要有用户上线,首先将上线用户记录并维护,其次从数据库中查询历史消息,针对该用户,将历史消息发送回去;

红色路线:用户下线,首先需要先从在线用户列表将其删除,其次在数据库更新其下线时间,这样是为了下次该用户上线时,根据下线时间发送这段时间内的历史消息;

通过初步分析,可能会对 WebSocket 产生一个疑问,那么接下来,简单来认识一下。

WebSocket 中事件驱动如何做到处理  1.用户上线;2.用户下线;3.发送消息 三种不同的业务事件?

WebSockets - Web API 接口参考 | MDN

用户上线 ---- onOpen ---- new WebScoket(...)

用户下线 ---- onClose ---- ws.close() 或者 前端关闭网页(浏览器调用ws.close(),浏览器自动清理资源)

发送消息 ---- onMessage ---- ws.send(...) 前端发送消息过来

二、数据库建表

用户表users

uid、用户名username(不允许重复)、昵称nickname、密码password、上次登出时间logout_at

users

消息表messages

mid、uid(谁发的)、发送内容content、发送时间published_at

messages

关联:

查找未发送过的历史消息

注册用户时,insert 用户时将上次登录时间录成 null

if (user.上次登录的时间 != null) {
    //不是新用户
    SELECT * FROM 消息表 WHERE 发送时间 >= user.上次登录时间;
}

三、代码实现(详细注释)

代码大体划分层次

endpoint ——(负责 WebSocket接入)                 每次连接一个

service ——(业务工作)                                       可以单例

dao ——(数据库相关)                                         可以单例

usercenter —— (管理在线用户)                         必须单例

接口

用户管理接口

  • online(用户上线)
  • offline(用户下线)

发布消息接口

  • publish(消息)

整体代码已上传到Gitee,代码内添加详细注释助理解。

心怀若谷/聊天室项目v2

四、项目中遇到的部分问题汇总

1.如何从WebSocket获取HttpSession

        根据WebSocket原理我们可以知道,WebSocket在建立握手阶段是使用Http协议的,那么就从Http这次握手中拿去HttpSession.

        根据我对文档进行查询,找到了下面的方法(这里都是由Tomcat实例化的)

//configurator = HttpSessionConfigurator.class
//webSocket 在建立连接时,会构造 HttpSessionConfigurator 对象,调用其 modifyHandshake 方法
//先调用 modifyHandshake 后执行 onOpen
@ServerEndpoint(value = "/message", configurator = HttpSessionConfigurator.class)
public class MessageEndpoint {
    @onOpen
    @onClose
    @onMessage
    @onError
}
//读取是通过 ServerEndpointConfig.Configurator
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //websocket建立握手阶段(建立连接阶段)
        Object httpSession = request.getHttpSession();
        //httpSession可能是null的
        if (httpSession != null) {
            sec.getUserProperties().put("httpSession", httpSession);
        }
    }
}

通过 Debug 可以看到执行过程(反射)

Tomcat 启动阶段,都是由 Tomcat 内部的代码触发的:

   Configurator 的构造器方法                                   (执行一次

  • 每次 js 执行 new WebSocket(...)                                 
  • Configurator.modifyHandshake(...)                              
  • Endpoint 的构造方法                                                    
  • endpoint.onOpen                                                          

 补充知识点:反射

        对于任意一个类或对象,都能够获取到这个类的所有属性和方法(包括私有属性和方法),这种动态获取信息以及动态调用对象方法的功能就称为反射机制。简单来讲,通过反射,类对我们是完全透明的,想要获取任何东西都可以。

五、部署并发布项目

前提:

  • 拥有一台云服务器并安装有Linux系统(这里我安装的是CentOS 7)
  • 云服务器连接本地主机(我这里用的是Xshell和Xftp)
  • 已安装git、jdk、maven、Tomcat、MySQL(我的本地MySQL Workbench已连接远程云服务器的数据库)

1.拉取 Gitee 上的项目

放在一个自己安排好的文件夹内

git pull [gitee地址]

2. maven 打包项目

要到有 pom.xml 文件的根目录下打包,生成 target 文件夹

mvn package [文件名]

将 target 目录下的 .war 文件复制到 tomcat 的 webapp 目录下,tomcat 会自动将 .war 文件生成项目文件夹

cp [.war文件名] [tomcat的webapp文件夹地址]

3.重启 tomcat

进入到 tomcat 的 bin 目录下,通过 sh 命令重启 tomcat(这里注意该文件夹内文件已获得 sh 权限)

sh shutdown.sh
sh startup.sh

4.通过外网访问聊天室

【云服务器外网ip地址】:【端口号,不写默认是8080端口】/【访问路径,不写默认是ROOT文件夹下的项目】

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值