WebSocket初始
1.1 WebSocket介绍
WebSocket是一种网络通信协议。RFC6455定义了它的通信标准。
WebSocket是HTML5开始的提供的一种在单个TCP连接上进行全双工通讯的协议。
HTTP协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端: HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数web应用程序将通过频繁的异步AJAX请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者HTTP 连接始终打开)。


1.2 WebSocket协议
本协议有两部分:握手和数据传输。



1.3 springboot的webSocket集成
项目使用的spirngboot版本是2.0.0.RELEASE
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
添加配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @Author :
* @Date :Created in 2023/3/23 15:52
* @Description:Endpoint 端点实例是由Servlet容器创建的(未由spring管理),所以在端点实例里面使用spring的注入 @Autowired … 之类的注解,没用。
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter (){
return new ServerEndpointExporter();
}
}
端点创建
import com.common.util.SpringCtxUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author :
* @Date :Created in 2023/3/23 15:56
* @Description:
*/
@ServerEndpoint("/ruleExecuteProcess/{processId}")
@Component
@Slf4j
public class WebSocketServer {
/**
* spring管理的都是单例(singleton)和 websocket (多对象)相冲突。
* 需要了解一个事实:websocket 是多对象的,每个用户的聊天客户端对应 java 后台的一个 websocket 对象,前后台一对一(多对多)实时连接,
* 所以 websocket 不可能像 servlet 一样做成单例的,让所有聊天用户连接到一个 websocket对象,这样无法保存所有用户的实时连接信息。
* 可能 spring 开发者考虑到这个问题,没有让 spring 创建管理 websocket ,而是由 java 原来的机制管理websocket ,所以用户聊天时创建的
* websocket 连接对象不是 spring 创建的,spring 也不会为不是他创建的对象进行依赖注入,所以如果不用static关键字,每个 websocket 对象的 service 都是 null。
* example结果为null
* @Autowired
* private Example example;
*/
/**
* 存放连接的客户端信息
*/
public static final Map<String, Session> SESSION_MAP =new ConcurrentHashMap<>();
/**
* 建立连接成功调用的方法
* @param session
* @param processId
*/
@OnOpen
public void onOpen(Session session,@PathParam("processId")String processId) {
SESSION_MAP.put(processId,session);
log.info("连接成功!");
basicSendMessage(session,"连接成功");
}
/**
* 连接关闭调用的方法
* @param session
* @param processId
*/
@OnClose
public void onClose(Session session,@PathParam("processId")String processId){
SESSION_MAP.remove(processId);
log.info("断开连接!");
}
/**
* 收到客户端消息后调用的方法
* @param message
* @param session
* @param processId
*/
@OnMessage
public void onMessage(String message,Session session,@PathParam("processId")String processId){
log.info("客户端标识:{}--->消息:{}",processId,message);
basicSendMessage(session,"收到消息!");
Example example = SpringCtxUtils.getBean(Example.class);
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.info("webSocket发生错误!");
log.error(error.getMessage(),error);
}
/**
* 服务器向客户端发送消息(异步)
* @param toSession
* @param message
*/
public static void asyncSendMessage(Session toSession,String message) {
toSession.getAsyncRemote().sendText(message);
}
/**
* 服务器向客户端发送消息(同步)
* 必须等上一条消息发送完之后才能发送新消息
* @param toSession
* @param message
*/
public static void basicSendMessage(Session toSession,String message) {
try {
toSession.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
/**
* 关闭与客户端连接
* @param processId
*/
public static void sessionClose(String processId) {
Session session = SESSION_MAP.get(processId);
SESSION_MAP.remove(processId);
try {
session.close();
} catch (IOException e) {
log.info(e.getMessage() , e);
}
}
}
webSocket依赖注释为null解决办法
手动从spring容器中获取
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @Author :
* @Date :Created in 2023/4/8 13:05
* @Description:
* 获取spring容器
* 当一个类实现了这个接口ApplicationContextAware之后,这个类就可以方便获得ApplicationContext中的所有bean。
* 换句话说,这个类可以直接获取spring配置文件中所有有引用到的bean对象
* 前提条件需作为一个普通的bean在spring的配置文件中进行注册
*/
@Component
public class SpringCtxUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringCtxUtils.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> type) {
try {
return applicationContext.getBean(type);
} catch (NoUniqueBeanDefinitionException e) {
//出现多个,选第一个
String beanName = applicationContext.getBeanNamesForType(type)[0];
return applicationContext.getBean(beanName, type);
}
}
public static <T> T getBean(String beanName, Class<T> type) {
return applicationContext.getBean(beanName, type);
}
}
1.4 OkHttpClient作为webSocket客户端
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import java.util.concurrent.TimeUnit;
/**
* @Author :
* @Date :Created in 2023/3/24 15:14
* @Description:
*/
@Data
@Slf4j
public class WebSocketClient {
/**
* websocket请求
*/
private Request request;
/**
* httpclient客户端
*/
private OkHttpClient client;
/**
* websocket客户端
*/
private WebSocket webSocket;
/**
* 构造方法
* @param bxmQualityIpAndPort
* @param processId
*/
public WebSocketClient(String bxmQualityIpAndPort, String processId) {
client = new OkHttpClient.Builder()
// 设置 PING 帧发送间隔
.pingInterval(5, TimeUnit.SECONDS)
.build();
request = new Request.Builder()
.url("ws://"+bxmQualityIpAndPort+"/ruleExecuteProcess/"+processId)
.build();
}
/**
* 关闭websocket连接
*/
public void close() {
if (webSocket != null) {
webSocket.close(1000, null);
}
client.dispatcher().executorService().shutdown();
}
/**
* 发起websocket请求
* @param message
*/
public void start(StringBuilder message){
webSocket=client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
// WebSocket 连接建立
log.info("建立连接成功!");
message.append("建立连接成功!");
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
// 收到服务端发送来的 String 类型消息
log.info("发送信息:{}!",text);
message.append(text);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
// WebSocket 连接关闭
log.info("断开连接!");
message.append("断开连接!");
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
// 出错了
log.info("连接出错!");
message.append("连接出错!");
}
});
}
/**
* 向服务器端发送消息
* @param message
*/
public void sendMessage(String message) {
webSocket.send(message);
}
}

浙公网安备 33010602011771号