websocket的使用可以使前后端的数据之间进行实时通讯。普通的web程序,由客户端发出请求,由服务端接收请求,进行处理并将结果返回客户端的这么一个流程。可是如果服务端的数据变化频率比较快,客户端要想要比较实时的获得最新的服务端数据,可能需要不断的发送请求,而websoceket进行一次握手之后,浏览器和服务器之间就会建立一条通道,就可以随时进行数据传输。
websocket里面使用@Autowired注入bseries和bean的时候可能会取不到,原因是spring管理的都是单例(singleton),和 websocket (多对象)相冲突。项目启动时初始化,会初始化 websocket (非用户连接的),spring 同时会为其注入 service,该对象的 service 不是 null,被成功注入。但是,由于 spring 默认管理的是单例,所以只会注入一次 service。当新用户进入聊天时,系统又会创建一个新的 websocket 对象,这时矛盾出现了:spring 管理的都是单例,不会给第二个 websocket 对象注入 service,所以导致只要是用户连接创建的 websocket 对象,都不能再注入了。
下面是websocket作为服务端的一个demo:
@ServerEndpoint("/webSocket/{sid}")
@Slf4j
@Component
public class WebSocketServer {
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收sid
*/
private String sid="";
//存储session的map
private static Map<String,Object> dataMap = new HashMap<String,Object>();
/**
* 连接建立成功调用的方法
* */
@OnOpen
public void onOpen(Session session,@PathParam("sid") String sid) {
this.session = session;
//将session存起来
dataMap.put(sid,session);
//如果存在就先删除一个,防止重复推送消息
for (WebSocketServer webSocket:webSocketSet) {
if (webSocket.sid.equals(sid)) {
webSocketSet.remove(webSocket);
}
}
webSocketSet.add(this);
this.sid=sid;
log.info("连接已建立");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
log.info("连接已关闭");
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来"+sid+"的信息:"+message);
String sid = getSid(session);
if (sid == null) {
return;
}
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message,sid);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String getSid(Session session) {
String sid = null;
for (String key:dataMap.keySet()) {
Session cursession = (Session) dataMap.get(key);
if (cursession == session) {
sid = key;
break;
}
}
return sid;
}
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message, String sid) throws IOException {
Session session = (Session) dataMap.get(sid);
session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendInfo(SocketMsg socketMsg, @PathParam("sid") String sid) throws IOException {
String message = JSONObject.toJSONString(socketMsg);
log.info("推送消息到"+sid+",推送内容:"+message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(sid==null) {
item.sendMessage(message,sid);
}else if(item.sid.equals(sid)){
item.sendMessage(message,sid);
}
} catch (IOException ignored) { }
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
WebSocketServer that = (WebSocketServer) o;
return Objects.equals(session, that.session) &&
Objects.equals(sid, that.sid);
}
@Override
public int hashCode() {
return Objects.hash(session, sid);
}
}
这是websocket作为服务端的其中一种写法,在连接websocket的时候,传入了一个区分用户的ID,并把用户ID存在session中,这里的session代表的是一个websocket连接,这样在服务端向前端返回数据的时候就会知道是给谁发送的信息了。
websocket的传输信息的数据类型好像只有字符串,而且在默认情况下,会有大小的一个限制。解决方式后面补上。
自己测试的时候,可以使用
websocket在线测试
来进行收发信息的测试,注意连接的地址是以 ws:// 开头的,以上面的后端代码为例,@ServerEndpoint("/webSocket/{sid}")注解需要的连接格式为:
ws://127.0.0.1:8080/webSocket/123
连接成功以后就可以收发信息了。