一、概述
1.websocket是什么?
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket与HTTP协议不同,它使用了独立的端口,并且在建立连接后,不需要在每次数据传输时重新建立连接。这使得它特别适合于实时应用程序,例如聊天,在线游戏和股票交易等,这些应用程序需要高速,双向的数据传输。
WebSocket协议是HTML5标准的一部分,因此它可以在现代浏览器中使用。WebSocket API可以在JavaScript中使用,这样就可以在网页上直接使用WebSocket进行通信。
2.websocket和HTTP对比
WebSocket是一种与HTTP不同的协议。两者都位于OSI模型的应用层,并且都依赖于传输层的TCP协议。 虽然它们不同,但是RFC 6455中规定:it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries(WebSocket通过HTTP端口80和443进行工作,并支持HTTP代理和中介),从而使其与HTTP协议兼容。 为了实现兼容性,WebSocket握手使用HTTP Upgrade头[1]从HTTP协议更改为WebSocket协议。
其具体的区别如下:
3.websocket的优势和劣势
4.websocket应用场景
这些场景中,WebSocket的实时通信特性使得WebSocket成为了一种理想的选择。
利用websocket处理在线聊天业务的常见流程:
二、原理
1.WebSocket通信原理和机制
HTTP通信方式是请求-响应的单工通信,他的通信模式是一问一答式的,如果需要服务端主动发送消息给客户端,他是做不到的。
Websocket的通信方式是全双工模式,无论客户端还是服务端,都能够自主发起通信。但是WebSocket 是独立的、建立在TCP上的协议。Websocket 通过 HTTP/1.1 协议的101状态码进行握手。为了建立Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(Handshaking)。握手是通过HTTP协议完成的,但是一旦建立连接后,数据传输将使用WebSocket协议。
WebSocket通信的流程如下:
与 HTTP 通信不同的是,WebSocket 通信是基于TCP的,所以它是一个持久连接。它允许服务器主动发送信息给客户端,而不是等待客户端的请求。这使得 WebSocket 通信成为了实现实时应用的理想选择。
建立了websocket连接,请求的URL会以ws://xx开始,在请求头中会有upgrade:websocket标志,说明该请求是websocket请求。
后续客户端和服务器的消息通信会在同一个回话中展示。
2.Java Websocket API介绍
与HTTP不同,websocket的通信具有生命周期,此生命周期由websocket协议本身支撑,具体实现websocket的语言都需要支持。
建立连接事件:此事件发生在端点上建立新连接时并且在任何其他事件发生之前
发送消息事件:此事件接收WebSocket对话中另一端发送的消息。它可以发生在WebSocket端点接收了打开事件之后且在接收关闭事件关闭连接之前的任意时刻
出现错误事件:此事件在WebSocket连接或者端点发生错误时产生
连接关闭事件:此事件表示WebSocket端点的连接或者端点目前正在部分的关闭,它可以由参与连接的任意一个端点发出
针对Java来说,websocket的事件都会有对应的方法处理,其主要通过编程式端点服务来处理:
- @OnOpen:处理建立连接的事件;
@OnOpen
public void init(Session session, EndpointConfig config) {
}
- @OnMessage:处理建立连接后收发消息的事件;
@OnMessage
public void handleTextMessage(String textMessage) {
}
-----------
@OnMessage
public void handleBinaryMessage(byte[] messageData, Session session) {
}
- @OnError:处理消息时候发生错误的处理;
- @OnClose:处理连接关闭的事件;
三、实践
1.Springboot集成Websocket
通过Springboot集成Websocket有两种实现,分别是基于注解的实现和实现接口的方式,下面分别展开:
1.1.基于注解
添加pom依赖
在pom配置文件中引入spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-websocket
在配置类中增加websocket配置Bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author yangnk
* @desc
* @date 2023/08/15 00:30
**/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
实现websocketServer
实现一个websocketserver,需要添加@ServerEndpoint("/websocket/{name}")注解,该注解需要加在类上,表示请求websocket的url地址,再实现websocket各个声明周期的注解的方法。
声明ConcurrentHashMap webSocketMap用来保存websocket连接的session。
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author yangnk
* @desc
* @date 2023/08/15 00:31
**/
/**
* @ServerEndpoint 注解的作用
*
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{name}")
public class WebSocketServer {
/**
* 与某个客户端的连接对话,需要通过它来给客户端发送消息
*/
private Session session;
/**
* 标识当前连接客户端的用户名
*/
private String name;
/**
* 用于存所有的连接服务的客户端,这个对象存储是安全的
* 注意这里的kv,设计的很巧妙,v刚好是本类 WebSocket (用来存放每个客户端对应的MyWebSocket对象)
*/
private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap();
/**
* 连接建立成功调用的方法
* session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void OnOpen(Session session, @PathParam(value = "name") String name){
log.info("----------------------------------");
this.session = session;
this.name = name;
// name是用来表示唯一客户端,如果需要指定发送,需要指定发送通过name来区分
webSocketMap.put(name,this);
log.info("[WebSocket] 连接成功,当前连接人数为:={}", webSocketMap.size());
GroupSending(name+" 来了");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void OnClose(){
webSocketMap.remove(this.name);
log.info("[WebSocket] 退出成功,当前连接人数为:={}", webSocketMap.size());
GroupSending(name+" 走了");
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void OnMessage(String message_str){
log.info("[WebSocket] 收到消息:{}",message_str);
//判断是否需要指定发送,具体规则自定义
//message_str的格式 TOUSER:user2;message:aaaaaaaaaaaaaaaaaa;
if(message_str.indexOf("TOUSER") == 0){
//取出 name和message的值
String[] split = message_str.split(";");
String[] split1 = split[0].split(":");
String[] split2 = split[1].split(":");
String name = split1[1];
String message = split2[1];
//指定发送
AppointSending(name,message);
}else{
//群发
GroupSending(message_str);
}
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
log.info("发生错误");
error.printStackTrace();
}
/**
* 群发
* @param message
*/
public void GroupSending(String message){
for (String name : webSocketMap.keySet()){
try {
webSocketMap.get(name).session.getBasicRemote().sendText(name + ":" + message);
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 指定发送
* @param name
* @param message
*/
public void AppointSending(String name,String message){
try {
webSocketMap.get(name).session.getBasicRemote().sendText(name + ":" + message);
}catch (Exception e){
e.printStackTrace();
}
}
}
控制器Controller调用方法
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author yangnk
* @desc
* @date 2023/08/15 00:32
**/
@RestController("web_Scoket_system")
@RequestMapping("/api/socket")
public class SystemController {
@Autowired
WebSocketServer socketServer;
//推送数据接口
@ResponseBody
@RequestMapping("/socket/push/{name}")
public String pushToWeb(@PathVariable String name, @RequestHeader String message) {
Map result = new HashMap();
socketServer.AppointSending(name, message);
result.put("name", name);
result.put("msg", message);
return result.toString();
}
}
验证结果
可以在在线websocket测试网站:www.bejson.com/httputil/we… 进行验证。
1.2.实现接口
TODO
2.完整代码
github.com/yangnk/Spri…
3.常见问题
TODO
参考资料
本文由博客一文多发平台 OpenWrite 发布!