基于Spring后端websocket
有三种实现方式:Servlet技术栈的实现方式(也就是Spring MVC);响应式的实现方式(也就是Spring WebFlux);暴露端点的方式实现。
1.SpringMVC:
a.实现WebSocketHandler消息处理类
public class TextMessageHandler extends TextWebSocketHandler {
private final Log log = LogFactory.getLog(TextMessageHandler.class);
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info(message.toString());
session.sendMessage(new TextMessage("hello world!!!"));
session.close();
}
}
b.实现WebSocketConfigurer配置类
@Configuration
@EnableWebSocket
public class TextWebsocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/websocket").setAllowedOrigins("*");
}
@Bean
public WebSocketHandler webSocketHandler() {
return new TextMessageHandler();
}
}
注:
a.是org.springframework.web.socket包下面的类
b.WebSocketHandler控制生命周期,WebSocketConfigurer配置和管理连接属性(比如,上面的地址与允许的域)
c.依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.SpringWebFlux
a.实现WebSocketHandler
public class TextMessageHandler implements WebSocketHandler{
private final Log log = LogFactory.getLog(TextMessageHandler.class);
@Override
public Mono<Void> handle(WebSocketSession session) {
Flux<WebSocketMessage> result = session.receive().doOnNext(data->log.info(data.getPayloadAsText())).map(data->session.textMessage(data.getPayloadAsText()+"Hello world!!!"));
return session.send(result);
}
}
b.配置mapping
@Configuration
public class TextWebsocketConfig {
@Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/websocket", new TextMessageHandler());
int order = -1; // before annotated controllers
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(order);
return mapping;
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
a.是org.springframework.web.reactive.socket包下面的类
b.依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
3.暴露端点的方式
a. 配置ServerEndpointExporter
@Configuration
public class WebsocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter (){
return new ServerEndpointExporter();
}
}
b. 编写ServerEndpoint行为
@ServerEndpoint(value = "/websocket")
@Component
public class SocketHandler {
private volatile static int onlineCount = 0;
private static CopyOnWriteArraySet<SocketHandler> webSocketSet = new CopyOnWriteArraySet<SocketHandler>();
private Session session;
@OnOpen
public void onOpen(Session session){
this.session = session;
System.out.println(session.getId()+"---=====");
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* @throws IOException */
@OnMessage
public void onMessage(String message, Session session) throws IOException {
JsonMapper mapper=new JsonMapper();
MessageContent messageContent=mapper.readValue(message, MessageContent.class);
webSocketSet.parallelStream().filter(socketHandler->socketHandler.getSession().getId().equals(messageContent.getUserId())).forEach((socketHandler)->{
try {
socketHandler.getSession().getBasicRemote().sendText(messageContent.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
System.out.println("来自客户端的消息:" + message);
System.out.println(session.getId()+"------");
session.getBasicRemote().sendText("hhhhhh");
}
/**
* 发生错误时调用***/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendInfo(String message) throws IOException {
for (SocketHandler item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
SocketHandler.onlineCount++;
}
public static synchronized void subOnlineCount() {
SocketHandler.onlineCount--;
}
public Session getSession() {
return session;
}
}
注:
a.很显然你只要会第一种用法就ok了,第一种已经是一套成熟的体系了。
b.Spring MVC是依赖servlet容器的,而Spring WebFlux不依赖容器默认使用netty。在绝大多数情况下他们的用法是区别不大的,但是我依然主张你使用Spring MVC,因为它更加规范也更加健壮。
c.本质上来说websocket是一个基础的套接字,他没有http本身那么多条条框框,这使得它的实现更加随意(就是处理套接字连接)。
d.有意见或者建议请评论区留言;转载请标明作者与来源。