场景:对于某些实时性要求比较高的前后台交互,可以使用websocket进行全双工的交互,websocket可以使用H5的标准或者是Spring的实现分别如下
1.使用H5的标准如下:
a.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>${spring-boot.version}</version>
</dependency>
b.注入ServerEndpoint
@Configuration
public class WebSocketConfig1 {
/**
* 1.首先需要注入ServerEndpoint,这个Bean会自动注册使 用了@ServerEndpoint注解申明的Websocket endpoint
* 要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter
* 因为他将由容器自己提供和管理
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
c.编写连接处理逻辑类
可以采用@ServerEndpoint(value = "/websocket/{c2c}")来携带参数,以便根据此标记向不同类型的连接发送不同的消息
/**
* 1个session对应一个WebSocketController1对象
*/
@ServerEndpoint(value = "/websocket/{c2c}")
@Component
@EnableScheduling
public class WebSocketController1 {
/**
* 静态变量用来记录当前的在线连接数,应该将其设计为线程安全的
*/
private static AtomicLong onlineCount = new AtomicLong(0);
/**
* 线程安全的set,用来存放每个客户端对应的socket对象
*/
private static CopyOnWriteArraySet<WebSocketController1> webSockets = new CopyOnWriteArraySet<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
private String c2c;
/**
* 连接建立成功调用的方法
*
* @param c2c 交易对
* @param session 会话
*/
@OnOpen
public void onOpen(@PathParam("c2c") String c2c, Session session) {
this.session = session;
this.c2c = c2c;
webSockets.add(this);
// 以下代码非线程安全的,需要将这一块代码修正为线程安全的
// if(websocketMap.containsKey(c2c)){
// websocketMap.get(c2c).add(this);
// }else{
// List<WebSocketController1> list=new LinkedList<>();
// list.add(this);
// websocketMap.put(c2c,list);
// }
System.out.println("有新连接加入!当前在线人数为" + addOnlineCount());
try {
//通知成功建立
sendMessage("成功建立连接");
} catch (IOException e) {
System.out.println("IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSockets.remove(this);
System.out.println("有一连接关闭!当前在线人数为" + subOnlineCount());
}
/**
* @return
*/
@OnMessage
public void onMessage(String message) {
System.out.println("来自客户端的消息:" + message);
try {
this.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发生错误时调用
*
* @return
*/
@OnError
public void onError(Session session, Throwable error) throws IOException {
System.out.println("发生异常");
session.getBasicRemote().sendText("异常");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 此接口根据接受到的消息来自动的触发消息发送
*
* @throws IOException
*/
@Scheduled(fixedRate = 1000)
public static void sendInfo() throws IOException {
// 同时也可以采用伦循的方式发送消息
// webSockets.stream().filter(socket -> socket.getC2c().equals("c2c")).forEach();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (WebSocketController1 item : webSockets) {
try {
if (item.session.isOpen()) {
item.sendMessage(df.format(new Date()));
} else {
webSockets.remove(item);
}
} catch (IOException e) {
continue;
}
}
}
public static Long getOnlineCount() {
return onlineCount.get();
}
public static Long addOnlineCount() {
return onlineCount.incrementAndGet();
}
public static Long subOnlineCount() {
return onlineCount.decrementAndGet();
}
public String getC2c() {
return c2c;
}
}
测试可以采用 jmeter或者在线测试 ws://127.0.0.1:8088/websocket/{c2c}
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
2.采用spring提供的支持
a.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>${spring-boot.version}</version>
</dependency>
b.编写配置类
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig3 extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
/**
* 此类为自定义的连接处理类
*/
@Autowired
private SystemWebSocketHandler systemWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
System.out.println("......注册......");
//这个类的标红的代码 解释 问题2
//配置webSocket路径
registry.addHandler(systemWebSocketHandler, "/websocket1").addInterceptors(new MyHandshakeInterceptor()).setAllowedOrigins("*");
registry.addHandler(systemWebSocketHandler, "/websocket2").addInterceptors(new MyHandshakeInterceptor()).setAllowedOrigins("*");
//配置webSocket路径 支持前端使用socketJs
registry.addHandler(systemWebSocketHandler, "/sockjs/websocket").setAllowedOrigins("*").addInterceptors(new MyHandshakeInterceptor()).withSockJS();
}
}
c.定义连接的拦截器
public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
//System.out.println("Before Handshake");
//获得 session 和request对象(解释问题3)
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
//获得session
// HttpSession session = servletRequest.getServletRequest().getSession(false);
//获得httpServletRequest
HttpServletRequest httpRequest = servletRequest.getServletRequest();
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
//System.out.println("After Handshake");
super.afterHandshake(request, response, wsHandler, ex);
}
}
d.实现连接处理类
@Service
@EnableScheduling
public class SystemWebSocketHandler implements WebSocketHandler {
private static AtomicLong onlineCount = new AtomicLong(0);
public SystemWebSocketHandler() {
}
/**
* 所有的socket连接
*/
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();
/**
* 连接建立成功之后,向用户发送回应
* 也就是在连接成功建立的时候是能够携带参数的
*
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("++++ " + addOnlineCount());
// System.out.println("......连接之后After......" + "ConnectionEstablished");
// System.out.println("getId:" + session.getId());
// System.out.println("getLocalAddress:" + session.getLocalAddress().toString());
// System.out.println("getUri:" + session.getUri().toString());
// System.out.println("getPrincipal:" + session.getPrincipal());
//连接进来的用户添加到用户集合中方便以后发送消息
users.add(session);
session.sendMessage(new TextMessage("你好: webSocket connect 成功!!! write by spring boot"));
}
/**
* 处理接收到的消息 消息的类型封装在WebSocketMessage<?>中,如果要传递指定的消息类型 就指定 ? 的类型即可
*
* @param session
* @param message
* @throws Exception
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
System.out.println("发送信息" + message.toString() + " .session. " + session.getId());
session.sendMessage(new TextMessage(message.getPayload() + "世界和平"));
}
/**
* 发生异常的处理
*
* @param session
* @param exception
* @throws Exception
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
users.remove(session);
System.out.println("异常出现handleTransportError" + exception.getMessage());
throw new NullPointerException();
}
/**
* 连接被关闭的处理
*
* @param session
* @param closeStatus
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
System.out.println("--- " + subOnlineCount());
users.remove(session);
//System.out.println("关闭afterConnectionClosed" + closeStatus.getReason());
}
/**
* 判断是否是支持的消息类型
*
* @return
*/
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 每秒钟推送一次,推送给所有的客户端
*/
// @Scheduled(fixedRate = 1000)
public void sendMessageToUsers() {
System.out.println("调度方法被执行");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TextMessage message = new TextMessage(df.format(new Date()));
for (WebSocketSession user : users) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 给指定用户发消息
*
* @param username
* @param message
*/
public void sendMessageToUser(String username, TextMessage message) {
for (WebSocketSession user : users) {
if (user.getAttributes().get("username").equals(username)) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
public static Long addOnlineCount() {
return onlineCount.incrementAndGet();
}
public static Long subOnlineCount() {
return onlineCount.decrementAndGet();
}
}
e.测试 ws://127.0.0.1:8088/websocket1?name=123 (可采用此形式传递参数)