org.springframework.boot
spring-boot-starter-websocket
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfiguration {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.validation.constraints.NotNull;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@Slf4j
@ServerEndpoint("/websocket/{uid}")
public class WebSocketService {
private static final long SESSION_TIMEOUT = 60000;
/**
* 用来存放每个客户端对应的WebSocketServer对象
*/
private static final Map WEB_SOCKET_MAP = new ConcurrentHashMap<>();
/**
* 连接建立成功调用的方法
* @param session
* @param uid
*/
@OnOpen
public void onOpen(Session session, @PathParam("uid") String uid) throws IOException {
//判断此uid是否已有会话,如果已有会话,将之前的会话断掉
Session sessionExist = WEB_SOCKET_MAP.get(uid);
if (Objects.nonNull(sessionExist) && sessionExist.isOpen()) {
log.warn("uid:{} 存在存量会话,即将将其断开", uid);
sessionExist.close();
log.warn("uid:{} 存在存量会话,已将其断开", uid);
}
session.setMaxIdleTimeout(SESSION_TIMEOUT);
WEB_SOCKET_MAP.put(uid, session);
log.info("websocket连接成功编号uid: " + uid + ",当前在线数: " + getOnlineClients());
try {
sendMessage(session, "连接成功");
} catch (IOException e) {
log.error("websocket发送连接成功错误编号uid: " + uid + ",网络异常!!!");
}
}
/**
* 连接关闭调用的方法
*
*/
@OnClose
public void onClose(Session session, @PathParam("uid") String uid) {
try {
WEB_SOCKET_MAP.remove(uid);
log.info("websocket退出编号uid: " + uid + ",当前在线数为: " + getOnlineClients());
} catch (Exception e) {
log.error("websocket编号uid连接关闭错误: " + uid + ",原因: " + e.getMessage());
}
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam("uid") String uid) {
log.info("websocket收到客户端编号uid消息: " + uid + ", 报文: " + message);
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error, @PathParam("uid") String uid) {
log.error("websocket编号uid错误: " + uid+ "原因: " + error.getMessage());
error.printStackTrace();
}
/**
* 向指定uid,发送消息
* @param uid
* @param message
* @return boolean
*/
public boolean sendMessage2Uid(@NotNull String uid, String message) {
Session session = WEB_SOCKET_MAP.get(uid);
if (Objects.nonNull(session)) {
try {
this.sendMessage(session, message);
log.info("websocket发送消息编号uid为: " + uid + "发送消息: " + message);
return true;
} catch (Exception e) {
log.error("websocket发送消息失败编号uid为: " + uid + "消息: " + message);
return false;
}
} else {
log.error("websocket未连接编号uid号为: " + uid + "消息: " + message);
return false;
}
}
public boolean sendMessage2Group(List uids, String message) {
log.info("websocket发送group消息, uids:{}, 消息内容:{}", uids, message);
if (CollectionUtils.isEmpty(uids)) {
return false;
}
for (String uid : uids) {
this.sendMessage2Uid(uid, message);
}
return true;
}
/**
* 广播发送消息
* @param message
*/
public void sendMessage2All(String message) {
WEB_SOCKET_MAP.forEach((k, v) -> {
Session session = WEB_SOCKET_MAP.get(k);
try {
this.sendMessage(session, message);
log.info("websocket群发消息编号uid为: " + k + ",消息: " + message);
} catch (IOException e) {
log.error("群发自定义消息失败: " + k + ",message: " + message);
}
});
}
/**
* 服务端群发消息-心跳包
* @param message
* @return int
*/
public synchronized int sendPing(String message) {
if (WEB_SOCKET_MAP.size() == 0) {
return 0;
}
StringBuffer uids = new StringBuffer();
AtomicInteger count = new AtomicInteger();
WEB_SOCKET_MAP.forEach((uid, server) -> {
count.getAndIncrement();
if (WEB_SOCKET_MAP.containsKey(uid)) {
Session session = WEB_SOCKET_MAP.get(uid);
try {
this.sendMessage(session, message);
if (count.equals(WEB_SOCKET_MAP.size() - 1)) {
uids.append("uid");
return; // 跳出本次循环
}
uids.append(uid).append(",");
} catch (IOException e) {
WEB_SOCKET_MAP.remove(uid);
log.info("客户端心跳检测异常移除: " + uid + ",心跳发送失败,已移除!");
}
} else {
log.info("客户端心跳检测异常不存在: " + uid + ",不存在!");
}
});
log.info("客户端心跳检测结果: " + uids + "连接正在运行");
return WEB_SOCKET_MAP.size();
}
/**
* 向
*
* @param message
* @throws IOException
*/
private void sendMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(message);
}
/**
* 获取客户端在线数
* @return
*/
public static synchronized int getOnlineClients() {
return WEB_SOCKET_MAP.size();
}
/**
* 连接是否存在
* @param uid
* @return boolean
*/
public static boolean isConnected(String uid) {
if (WEB_SOCKET_MAP.containsKey(uid)) {
return true;
} else {
return false;
}
}
}
至此服务端就写完了,可以通过在线工具或者调试工具进行调试,如果没有调试工具,可以试试
Websocket在线测试工具,Websocket接口测试、模拟请求
然后在控制台就可以看到打印的log
@RestController
@RequestMapping("/websocket/message/send")
public class WebSocketSendMsgController {
@Autowired
private WebSocketService webSocketService;
@PostMapping("/one")
public void sendMsgToOne(@RequestBody String userId) {
webSocketService.sendMessage2Uid(userId, "hello one");
}
@PostMapping("/group")
public void sendMsgToOne(@RequestBody List userId) {
webSocketService.sendMessage2Group(userId, "hello group");
}
@PostMapping("/all")
public void sendMsgToAll() {
webSocketService.sendMessage2All("hello all");
}
}
其实就是客户端隔一段时间给服务端发一个消息,通过这种方式服务端可以知道客户端是活的,否则通过超时机制,自动断开连接,
这里我没写domo,就不粘了 (在线心跳测试WebSocket在线测试工具)