WebSocket简介
WebSocket 是一种支持双向通讯网络通信协议。
意思就是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息属于服务器推送技术的一种。
WebSocket特点
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据(blob对象或Arraybuffer对象)。
- 收到的数据类型 可以使用binaryType 指定, 显式指定收到的二进制数据类型。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(握手http)(如果加密,则为wss(tcp +TLS)),服务器网址就是 URL。
WebSocket使用场景
- 社交聊天
- 弹幕
- 协同编辑
- 多玩家游戏
- 股票基金实时报价
- 体育实况更新
- 视频会议/聊天
- 基于位置的应用
需求背景
模拟一个用户分别通过H5,PC端,小程序登录系统,后登录的应用都会被告知其他客户端。
需求实现
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
自己定义的适配器:当时我用spring默认的适配器(SpringConfigurator)会报错,我是参考下面的这篇文章解决的。
https://blog.csdn.net/weixin_43833048/article/details/106540647
public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
/**
* Spring application context.
*/
private static volatile BeanFactory context;
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CustomSpringConfigurator.context = applicationContext;
}
}
定义WebSocket配置类注册Bean到Spring容器:
@ConditionalOnWebApplication
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Bean
public CustomSpringConfigurator customSpringConfigurator() {
return new CustomSpringConfigurator();
}
}
WebSocket业务类实现
@Component
@Slf4j
@ServerEndpoint(value = "/webSocket/{userId}/{loginType}", configurator = CustomSpringConfigurator.class)
public class WebSocketSession {
private static AtomicInteger maximumConnection = new AtomicInteger(0);
public static HashBasedTable<String, String, Session> result = HashBasedTable.create();
/**
* 连接握手时调用
*
* @param userId
* @param loginType
* @param session
*/
@OnOpen
public synchronized void onOpen(@PathParam("userId") String userId,
@PathParam("loginType") String loginType,
Session session) {
Assert.isNull(result.get(userId, loginType), () -> "用户已建立连接:" + userId);
send(userId,loginType);
result.put(userId, loginType, session);
maximumConnection.addAndGet(1);
log.info("webSocket用户id:{},登录类型:{} 连接成功,当前连接数:{}", userId, loginType,maximumConnection.get());
}
/**
* @Title: onClose
* @Description: 连接关闭的操作
*/
@OnClose
public void onClose(@PathParam("userId") String userId,
@PathParam("loginType") String loginType) {
result.remove(userId, loginType);
log.info("webSocket用户id:{},登录类型:{}下线", userId, loginType);
}
/**
* @param @param message 收到的消息
* @param @param session 该连接的session属性
* @Title: onMessage
* @Description: 收到消息后的操作
*/
@OnMessage
public void onMessage(String message,
Session session,
@PathParam("userId") String userId,
@PathParam("loginType") String loginType) {
log.info("webSocket用户收到来自用户id为:{}的消息:{}", userId, message);
Map<String, Session> webSocketSessionMap = Optional.ofNullable(userId).map(uId -> result.row(userId)).orElseThrow(() -> new IllegalStateException("非法消息"));
webSocketSessionMap.forEach((key, wbSession) -> {
if (loginType.equals(key)) return;
synchronized (wbSession) {
try {
wbSession.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("com.formssi.mall.order.infrastructure.config.WebSocketSession.onMessage e:{}", e);
}
}
});
}
/**
* @param @param session 该连接的session
* @param @param error 发生的错误
* @Title: onError
* @Description: 连接发生错误时候的操作
*/
@OnError
public void onError(Session session,
Throwable error,
@PathParam("userId") String userId) {
log.info("webSocket用户用户id为:{}的连接发送错误", userId);
error.printStackTrace();
}
/**
* 发送
* @param userId
* @param loginType
*/
private void send(String userId, String loginType){
Map<String, Session> userMap = result.row(userId);
if (CollectionUtils.isNotEmpty(userMap)){
userMap.forEach((loginTypeKey,session)->{
try {
session.getBasicRemote().sendText(String.format("用户%s通过%s登录系统",userId,loginType));
} catch (IOException e) {
log.error("com.formssi.mall.order.infrastructure.config.WebSocketSession.send e:{}",e);
}
});
}
}
}
首先H5端发起请求
当然建议大家用中文当参数,这里只是为了更直观的展示就用的中文名字
然后PC端发起请求
当PC端登录后我们在来看H5端的PostMan端是否有收到消息推送?
从上图来看高先生登录PC端后,H5是有收到消息的,最后在验证小程序登录是不是H5和PC端都会收到通知?
到这来一个简单的WebSocket就完成啦~~~~~
下期在讲基于Spring实现WebSocket;