websocket是一种网络通信协议。
全双工。
弊端:HTTP协议无法实现服务器主动向客户端发起消息,
握手是基于http协议的
请求头
请求头说明
Connection:Upgrade 协议升级为WebSocket协议
Upgrade:WebSocket 标识该HTTP请求是一个协议升级请求
HTTP1.1 协议规定,Upgrade表示将通信协议从HTTP/1.1转向该字段指定的协议
Sec-Websocket-Key: 则是用于握手协议的密钥。
客户端采用base64编码的24位随机字符序列,服务器接受客户端HTTP协议升级的证明。 要求服务端响应一个对应加密的Sec-WebSocket-Accept头信息作为应答
Sec-WebSocket-Extensions: 协议扩展类型
响应头
Sec-WebSocket-Accept:浏览器对这个值进行验证,以证明确实是目标服务器回应了 WebSocket 请求。
使用vscode,搭建咯
浏览器端创建一个WebSocket
//参数url格式: ws://ip地址:端口号/资源名称 var ws = new WebSocket(url);
//onopen onopen
ws.addEventListener('open', function (event) {
ws.send('Hello Server!');
});
ws.addEventListener("close", function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
});
ws.addEventListener("message", function(event) {
//文本数据
if(typeOf event.data === String) {
console.log("Received data string");
}
var data = event.data;
// 处理数据
});
ws.addEventListener("error", function(event) {
// handle error event
});
websocket方法
ws.send('your message');
readyState
属性返回实例对象的当前状态,共有四种。
org.springframework.boot
spring-boot-starter-websocket
注入使用@ServerEndpoint的Websocket
@Configuration
public class WebSocketConfig {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Tomcat7.0.5支持 websocket
EndPoint对象,对应唯一的一个客户端。类比为Servlet
每一个连接,都会创建一个@ServerEndpoint 标注的类的实例。
@ServerEndpoint (”/资源路径“)
但是光写@Component还不够,还需要在配置类中注入
@Configuration public class WebSocketConfig { /** * 注入ServerEndpointExporter, * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
服务端接收客户端消息
通过@OnMessage注解指定接收消息的方法
服务端发送数据给客户端
发送消息则由RemoteEndpoint完成,其实例由session维护,根据使用情况,
我们可以通过
1、session.getBasicRemote获取同步消息发送的实例,然后调用其 sendXxx()方法就可以发送消息
2、可以通过session.getAsyncRemote获取异步消息发送实例
session.getBasicRemote().sendText(msg);
5、websocket测试
websocket在线测试
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}") // 接口路径 ws://localhost:8087/webSocket/userId;
public class WebSocket {
//存储的消息内容
public static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(100);
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 用户ID
*/
private String userId;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
//注:底下WebSocket是当前类名
private static CopyOnWriteArraySet webSockets = new CopyOnWriteArraySet<>();
// 用来存在线连接用户信息
private static ConcurrentHashMap sessionPool = new ConcurrentHashMap();
/**
* 链接成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
try {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的连接,总数为:" + webSockets.size());
//向客户端发送连接成功
sendOneMessage(userId, "建立连接成功");
} catch (Exception e) {
}
}
/**
* 链接关闭调用的方法
*/
@OnClose
public void onClose() {
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】连接断开,总数为:" + webSockets.size());
} catch (Exception e) {
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message
*/
@OnMessage
public void onMessage(String message) {
log.info("【websocket消息】收到客户端消息:" + message);
//发送队列中的内容
if (!arrayBlockingQueue.isEmpty() && message.equals("1")) {//在请求数据
String msa = (String) arrayBlockingQueue.element();
sendOneMessage(userId, msa);
Object remove = arrayBlockingQueue.remove();//移除成功返回移除的数据
}
}
/**
* 发送错误时的处理
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误,原因:" + error.getMessage());
error.printStackTrace();
}
// 此为广播消息
public void sendAllMessage(String message) {
log.info("【websocket消息】广播消息:" + message);
for (WebSocket webSocket : webSockets) {
try {
if (webSocket.session.isOpen()) {
webSocket.session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息
public void sendOneMessage(String userId, String message) {
Session session = sessionPool.get(userId);
if (session != null && session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:" + message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息(多人)
public void sendMoreMessage(String[] userIds, String message) {
for (String userId : userIds) {
Session session = sessionPool.get(userId);
if (session != null && session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:" + message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}