当前位置:   article > 正文

WebSocket - 底层原理、协议、编程式基础架构搭建_qq聊天是怎么实现的

qq聊天是怎么实现的

目录

前言

一、分析

1.1、qq聊天功能分析

1.2、WebSocket介绍

1.2.1、什么是消息推送呢?

1.2.2、websocket 和 http 长连接的区别

1.2.2、原理解析

二、简易demo

2.1、后端实现

2.1.1、引入依赖

2.1.2、继承TextWebSocketHandler

2.1.3、实现 WebSocketConfigurer 接口

2.2、前端实现

2.3、执行效果


前言


基于 WebSocket、Spring Boot 下,用一个简易的小 demo 讲解一下“QQ聊天”功能原理~

一、分析


1.1、qq聊天功能分析

用户看到的实际效果:用户A 给用户B 发送了一条消息,用户B在聊天框看到了消息,接着用户B回复消息......

底层实现:主机A 给服务器发送了一个 HTTP 请求,这个请求中就包含了用户A 要给用户B 发送的消息,服务器将收到的请求进行解析,在将响应发送给 主机B,这个响应中就包含了用户A 要发送的消息~

对于以上场景,我们应该如何实现呢?接着往下看!

1.2、WebSocket介绍

WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的 消息推送机制。

1.2.1、什么是消息推送呢?

传统的 web 程序都是 “一问一答” 的形式,客户端给服务器发送了个 HTTP 请求,服务器根据请求计算响应返回客户端。显然,这种传统的 web 程序如果客户端不主动发起请求,服务器就无法主动给客户端发送响应~ 

Ps:若想使用原生的 HTTP 协议实现消息推送,需要进行“轮询”,反复询问服务器是否收到请求,成本太高

而 WebSocket 则是更接近 TCP 这种级别的通信方式——一旦连接建立完成,客户端或服务器都可以主动向对方发送数据。

1.2.2、websocket 和 http 长连接的区别

相同点:

  • WebSocket 和 HTTP 都是需要先通过 TCP 建立连接,连接成功之后才能相互通信.

不同点:

  • 通讯方式:WebSocket 连接建立完成之后,客户端和服务器都可以主动向对方发起请求.  而 HTTP 传统的一问一答的形式,只能由客户端向服务器发起请求.
  • 通信信息:HTTP 协议中存在大量的 Header,通讯效率低.  WebSocket 除了第一个 HTTP 请求建立 TCP 连接之后,进行通讯都不需要发送 Header 就能交换数据.
  • 报文格式:整个和原理有关,下面着重讲解.

1.2.2、原理解析

WebSocket 协议本质上是基于 TCP 协议的,为了建立一个 WebSocket 连接,客户端先创建 WebSocket 对象,然后向服务器发起一个 HTTP 请求建立连接(类似 ws://cyk.com/aaa ).

第一次客户端请求报文格式类似于:

  1. GET /cyk/websocket/ HTTP/1.1
  2. Host: localhost
  3. Upgrade: websocket
  4. Connection: Upgrade
  5. Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
  6. Origin: http://localhost:8080
  7. Sec-WebSocket-Version: 13

  • Upgrade: websocket 参数值表明这是WebSocket类型请求.
  • Sec-WebSocket-Key 是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的 Sec-WebSocket-Accept 应答,否则客户端会抛出 Error during WebSocket handshake 错误,并关闭连接。

第一次服务器收到报文后返回的数据格式类似于:

  1. HTTP/1.1 101 Switching Protocols
  2. Upgrade: websocket
  3. Connection: Upgrade
  4. Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
  • HTTP/1.1 101 Switching Protocols:表示服务端接受WebSocket协议的客户端连接, 
  • Sec-WebSocket-Accept:值是服务端采用与客户端一致的密钥计算出来后返回客户端的.

经过这样的请求响应之后,两端的 WebSocket 连接握手完成,后续就可以进行 TCP 通讯了.

二、简易demo


2.1、后端实现

2.1.1、引入依赖

创建一个 Spring Boot 项目,接着我们需要引入 Spring Web 依赖,以及 Web Socket 依赖。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-websocket</artifactId>
  8. </dependency>

2.1.2、继承TextWebSocketHandler

这里我们先创建一个类,自定义名为 TestAPI ,继承 TextWebSocketHandler 类重写这个类下4个最关键的方法:

  • afterConnectionEstablishe:客户端与服务器成功建立连接以后会调用此方法。
  • handleTextMessage:客户端通过 WebSocket 类下的 send 方法发送给服务器数据时会调用此方法。
  • handleTransportError:客户端与服务器连接异常时会调用此方法。
  • afterConnectionClosed:客户端与服务器断开连接时会调用此方法。

这个类就是用来处理 WebSocket 相关请求的~

于是,我们就可这样写:

  1. import org.springframework.stereotype.Component;
  2. import org.springframework.web.socket.CloseStatus;
  3. import org.springframework.web.socket.TextMessage;
  4. import org.springframework.web.socket.WebSocketSession;
  5. import org.springframework.web.socket.handler.TextWebSocketHandler;
  6. @Component
  7. public class TestAPI extends TextWebSocketHandler {
  8. @Override
  9. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  10. System.out.println("连接成功");
  11. }
  12. @Override
  13. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  14. System.out.println("收到消息:" + message.getPayload());
  15. //收到消息后向客户端反馈信息(这里反馈相同数据)
  16. session.sendMessage(message);
  17. }
  18. @Override
  19. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  20. System.out.println("连接异常");
  21. }
  22. @Override
  23. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
  24. System.out.println("连接关闭");
  25. }
  26. }

2.1.3、实现 WebSocketConfigurer 接口

第一步,创建一个类,自定义类名为WebSocketConfig,实现 WebSocketConfigurer接口,这个接口下只需要实现一个方法 registerWebSocketHandlers,这个类就是用来注册添加 WebSocket (当前项目要添加的就是我们刚刚写的 TestAPI 这个类)以及注册路由(这个路由就将客户端与服务器联系起来了:前端创建 WebSocket 实例时,就需要写下对应的路由)。

第二步、注入 TestAPI 类,最后使用 addHander方法添加注册这个类,基于这个类才能进一步的找到 TestAPI 这个类,去处理 WebSocket相关的请求~

代码实现如下:

  1. import com.example.demo.api.TestAPI;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  5. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  6. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
  7. @Configuration
  8. @EnableWebSocket //启动 WebSocket
  9. public class WebSocketConfig implements WebSocketConfigurer {
  10. @Autowired
  11. private TestAPI testAPI;
  12. @Override
  13. public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
  14. webSocketHandlerRegistry.addHandler(testAPI, "/test");
  15. }
  16. }

Ps:@EnableWebSocket 注解是用来让 Spring 框架认识这个类是用来配置 webSocket 的类,基于这个类才能进一步的找到 TestAPI 这个类,去处理 WebSocket相关的请求~

2.2、前端实现

前端需要创建 WebSocket 实例,并写下相应路由,这个路由就是与后端 webSocket 相对应的那个路由,如下:

接着前端也可以通过 WebSocket 的4个重要方法,根据响应打印对应日志:

  • onopen:客户端与服务器建立连接后会调用该方法。
  • onmessage:客户端收到服务器响应后会触发该方法。
  • onerror:客户端与服务器连接异常时会触发该请求。
  • conclose:客户端与服务器断开连接时会触发该请求。

最后通过按钮输入框的形式来发送相应的 HTTP 请求,代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <input type="text" id="message">
  11. <button id="submit">提交</button>
  12. <script>
  13. // 创建 websocket 示例
  14. let websocket = new WebSocket("ws://127.0.0.1:8080/test");
  15. websocket.onopen = function() {
  16. console.log("建立连接");
  17. }
  18. websocket.onmessage = function(e) {
  19. console.log("收到消息: " + e.data);
  20. }
  21. websocket.onerror = function() {
  22. console.log("连接异常");
  23. }
  24. websocket.onclose = function() {
  25. console.log("连接关闭");
  26. }
  27. let input = document.querySelector("#message");
  28. //点击按钮发送请求
  29. let button = document.querySelector("#submit");
  30. button.onclick = function() {
  31. console.log("发送消息:" + input.value);
  32. websocket.send(input.value);
  33. }
  34. </script>
  35. </body>
  36. </html>

2.3、执行效果

启动程序,输入对应URL,如下结果;

 发送消息,点击按钮(相当于主机A 给主机B 发送消息,这里实现的是一个简易版,是一个回显服务),如下结果:

最后关闭浏览器,断开连接:

这样一个简单 demo 就完成了~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/893972
推荐阅读
相关标签
  

闽ICP备14008679号