WebSocket是一种网络通信协议.RFC6455定义了它的通信标准
WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双向通信的协议
HTTP协议是一种无状态的,无连接的,单向的应用层协议.它采用了请求,响应的模式.通信请求只能由客户端发起,服务端对请求做出应答处理.
这种模型有一个弊端:HTTP协议无法实现服务器主动向客户端发起消息
WebSocket的通信模式
1.客户端发送请求 ws://localhost:8080/websocket请求 与服务端建立连接 服务端给与响应(一次握手)
2.接着无论是客户端还是服务端,都可以主动与对方交互
WebSocket协议:
两部分:1.就是上面说的一次握手 2.握手之后无论客户端还是服务端都可以主动数据交互
握手是基于HTTP协议的.
客户端握手发送如下格式
服务端握手响应如下格式
请求头说明
客户端实现:
客户端创建WebSocket对象
var ws=new WebSocket(url); //url说明: ws://ip地址:端口号/资源名称
相关事件:
WebSocket相关方法 这里只关注send()
服务端实现
Tomcat 7.0.5版本开始支持WebSocket,并且实现了Java WebSocket规范(JSR356)
Java WebSocket应用由一系列的WebSocketEndpoint组成.Endpoint是一个java对象,代表WebSocket连接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口,就想Http请求后端时候的Servlet
Endpoint对象:例如有个张三连接服务端,那么服务端生成一个张三Endpoint对象;有个李四连接服务端,那么服务端就生成一个李四Endpoint对象
可以把Endpoint理解成WebSocket的资源
注意:抽象类Endpoint中的方法都有个参数:Session 这个不是HttpSession
我们可以通过两种方式定义Endpoint:
1.编程式,继承javax.websocket.Endpoint并实现其方法.
2.注解式,定义一个POJO,并添加@ServerEndpoint(配置资源路径)注解 及对应的@OnOpen,@OnClose,@OnError注解
Endpoint实例是WebSocket握手时创建,并在客户端与服务端连接过程中有效,最后在连接关闭时结束.在Endpoint接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法.生命周期方法如下:
服务端WebSocket如何接收客户端发送的数据:(注意下面的Session不是HttpSession)
通过Session添加MessageHandler消息处理器来接收消息,当采用注解方式定义Endpoint时,我们可以通过@OnMessage注解指定接收消息的方法
服务端如何推送数据给客户端?(注意下面的Session不是HttpSession)
发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过Session.getBasicRemote获取同步消息发送实例,然后调用其sendXxx()就可以发送消息,可以通过
Session.getAsyncRemote获取异步消息实例.
关键:可以将WebSocket中的Endpoint理解成 Http请求中后端的Servlet,每一个请求过来,都有一个servlet去处理这个请求
Endpoint可以理解成是每一个用户的实例, 一般现在都用注解,那么理解其来也很方便
用@ServerEndpoint注解定义Endpoint 每个Endpoint都是一个客户实例
用@OnMessage注解指定接收消息的方法
用@OnOpen指定开启一个全新会话时候调用的方法(该方法在客户端与服务端握手成功后调用)
用@OnClose指定会话关闭时的方法
用@OnError指定连接过程出现异常时候调用的方法
服务端代码简单示例:
package com.example.demo.websocket;
import com.example.demo.pojo.Chat;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* @author hrui
* @date 2024/4/28 5:06
*/
@ServerEndpoint("/chat")
public class ChatEndpoint {
private static Set<ChatEndpoint> webSocketSet=new HashSet<>();
private Session session;
@OnMessage
public void onMessage(String message,Session session) throws IOException{
System.out.println("接收到的消息是:"+message);
for(ChatEndpoint chat:webSocketSet){
//chat.session.getBasicRemote().sendText(message);//这样同时也发送给了自己
//将消息发送给其他用户
if(chat!=this){
chat.session.getBasicRemote().sendText(message);
}
}
}
@OnOpen
public void onOpen(Session session){
this.session=session;
webSocketSet.add(this);
}
@OnClose
public void onClose(Session session){
webSocketSet.remove(this);
}
@OnError
public void onError(Session session,Throwable error){
error.printStackTrace();
}
}
配置类
@Component
public class WebSocketConfig {
/**
* 使用内部容器,需要配置一个Bean
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
package com.example.demo.websocket;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author hrui
* @date 2024/4/28 5:50
*/
@ServerEndpoint("/websocket/{serverId}")//serverId是每个客户端的唯一标识
public class ChatEndpoint2 {
//与客户端通信
private Session session;
//客户端标识
private String serverId;
//所有连接服务的客户端,线程安全
private static ConcurrentHashMap<String, ChatEndpoint2> webSocketSet = new ConcurrentHashMap<>();
@OnOpen
public void OnOpen(@PathParam(value = "serverId") String serverId, Session session){
this.session = session;
this.serverId = serverId;
//存放所有的客户端连接,serverId唯一标识
webSocketSet.put(serverId,this);
System.out.println("客户端连接成功,websocket当前连接数为:"+webSocketSet.size());
}
@OnMessage
public void OnMessage(String message) throws IOException {
//message是接收到客户端发来的消息
System.out.println("websocket服务端接收到消息:"+message);
JSONObject json= JSON.parseObject(message);
String msg =json.getString("content"); //需要发送的内容
String receiverId = json.getString("receiverId"); //接收者
String senderId = json.getString("senderId"); //发送者
//这里可以写你的业务代码
//-------------------------
//根据receiverId 找到对应的客户端
ChatEndpoint2 service = webSocketSet.get(receiverId);
//判断接收者是否在线
if(service != null){
service.session.getBasicRemote().sendText("需要发送的内容");
}
}
@OnClose
public void OnClose(){
webSocketSet.remove(this.serverId);
System.out.println("客户端退出成功,websocket当前连接数为:"+webSocketSet.size());
}
}
vue示例
data() {
return {
ws: null, //定义websocket对象
}
}
//建立websocket服务
initWebSocket() {
//初始化websocket userId为会话标识
const wsuri = 'ws://127.0.0.1:8080/websocket/'+this.userId;
//连接服务端
this.ws = new WebSocket(wsuri);
//指定事件回调
this.ws.onmessage = this.websocketOnMessage;
this.ws.onopen = this.websocketOnOpen;
this.ws.onerror = this.websocketOnError;
this.ws.onclose = this.websocketClose;
},
//连接建立之后的回调
websocketOnOpen() {
this.ws.send("握手成功");
console.log("--------连接已建立!---------")
},
//数据接收
websocketOnMessage(e) {
console.log("收到了服务端发送的消息"+e)
},
//数据发送
websocketSend(Data) {
//需要把发送者ID,接收者ID发送给服务端,这样服务端才能知道将消息发给谁
this.ws.send(Data);
},
//连接建立失败重连
websocketOnError() {
this.initWebSocket();
},
//关闭
websocketClose(e) {
console.log('断开连接', e);
},