使用websocket实现实时通讯的功能,这里介绍两种方式
- 只创建一个聊天的房间,所有人都往里面发送数据,然后接收数据的那一方,通过自己定义的规则,对接收到的数据进行筛选,获取自自己想要的数据。
- 创建多个两天的房间,每个房间管理自己的聊天信息,接收方不用对数据进行筛选,因为只能接收自己所在房间的信息,所以无需过滤。
- 将webSocket 整合进SpringBoot(这里有个问题,让我找了半天)
(?:本人刚开始学习,所以有表述不准确或不对的地方,请您耐心提出,我会积极改正。)
1.一个房间的实现
package com.green.rainbow.modules.app.socket;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketService {
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketService对象。
private static CopyOnWriteArraySet<WebSocketService> webSocketSet = new CopyOnWriteArraySet<WebSocketService>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this);
try {
sendMessage("连接成功");
} catch (IOException e) {
System.out.println("IO异常");
}
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message, Session session) {
for (WebSocketService item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误时调用
* */
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
}
public void sendMessage(String message) throws IOException {
// websocket session发送文本消息有两个方法:
// getAsyncRemote()和getBasicRemote() 推荐使用getAsyncRemote()这个方法
// getBasicRemote是阻塞式的,getAsyncRemote是非阻塞式的
// 。。。别问我,你在这里为什么使用getBasicRemote
this.session.getBasicRemote().sendText(message);
}
}
2. 多个房间的实现
package com.example.demo.socket;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/websocket/{info}")
@Component
public class WebSocketService {
private static SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//创建时间格式对象
//创建房间的集合,使用ConcurrentHashMap是为了保证线程安全,HashMap在多线程的情况下会出现问题
private static ConcurrentHashMap<String, ConcurrentHashMap<String, WebSocketService>> roomList = new ConcurrentHashMap<>();
// 与某个客户端的连接会话,需要通过他来给客户端发送消息
private Session session;
//重新加入房间标识
private int rejoin = 0;
//info 格式: 标识|房间名|用户名(保证唯一性)
@OnOpen
public void onOpen(@PathParam("info")String param, Session session){
// 建立连接的时候,通过获取地址上的{info}的信息,通过截取字符串获取连接人的登录信息
this.session = session;
// flag 可以定义两个类型 joinRoom(加入房间) , existRoom(退出房间) 和 chatRoom (聊天)
String flag = param.split("[|]")[0];
// roomId 房间号
String roomId = param.split("[|]")[1];
if(flag.equals("joinRoom")){
// 用户名(必须保证唯一性)
String nickname = param.split("[|]")[2];
this.joinRoom(roomId , nickname);
}
}
//加入房间
public void joinRoom(String roomId, String nickname){
if (!roomList.containsKey(roomId)) {
// 创建房间不存在时,创建房间
ConcurrentHashMap<String, WebSocketService> room = new ConcurrentHashMap<>();
// 添加用户
room.put(nickname, this);
roomList.put(roomId, room);
} else {
// 房间已存在,直接添加用户到相应的房间
ConcurrentHashMap<String, WebSocketService> room = roomList.get(roomId);
if (room.get(nickname) != null) {
this.rejoin = 1;
}else{
room.put(nickname, this);
}
//发送消息给房间内的其他人,通知他们user已经上线
for(String item : room.keySet()){
Map<String, Object> map = new HashMap<>();
map.put("flag", "joinRoom");
map.put("name", item);
map.put("status", "上线");
room.get(item).sendMessage(map);
}
}
}
//发送消息
public void sendMessage(Map<String, Object> map){
JSONObject jsonObject = JSONObject.fromObject(map);
try {
// websocket session发送文本消息有两个方法:
// getAsyncRemote()和getBasicRemote() 推荐使用getAsyncRemote()这个方法
// getBasicRemote是阻塞式的,getAsyncRemote是非阻塞式的
// 。。。别问我,你在这里为什么使用getBasicRemote
this.session.getBasicRemote().sendText(jsonObject.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
//接收来自用户的消息
@OnMessage
public void onMessage(String message, Session session) throws IOException{
//把用户发来的消息解析成json对象
JSONObject param = JSONObject.fromObject(message);
//如果是退出房间 flag 分类:exitRoom 离开 joinRoom 加入房间 chatRoom 聊天
String flag = (String)param.get("flag");
if(flag.equals("exitRoom")){
String roomId = (String )param.get("roomId");
String nickname = (String)param.get("nickname");
ConcurrentHashMap<String, WebSocketService> room = roomList.get(roomId);//获取房间
room.remove(nickname);//从房间中移除该用户
//判断房间中是否有人,如果没人则删除房间,否则通知其他用户该用户已经下线
if(room.size() == 0 ){
roomList.remove(roomId);
}else{
for(String item : room.keySet()){
param.put("status", "下线");
room.get(item).sendMessage(param);
}
}
}else if(flag.equals("chatRoom")){
param.put("date", df.format(new Date()));
String roomId = (String)param.get("roomId");
String nickname = (String)param.get("nickname");
ConcurrentHashMap<String, WebSocketService> room = roomList.get(roomId);
if(room.get(nickname).rejoin == 0 ){
for(String item: room.keySet()){
room.get(item).sendMessage(param);
}
}else{
room.get(nickname).sendMessage(param);
}
}
}
//用户断开
@OnClose
public void onClose(Session session){
System.out.print("onClose");
}
@OnError
public void onError(Throwable t){
System.out.print("onError");
}
}
3. 整合SpringBoot 和 WebSocket
也许你在看到上面的代码时,就开始在自己的项目里调试了,可是当你在客户端调用ws://localhost:8080/websocket(以创建一个房间的那个为例)想创建连接时,会发现怎么也连不上,当时我是通过三步解决的这个问题的
- 这个应该是必须要有的,但是因为看不懂,以为没用就没加。。。。
(1). 引入mvn
<!--webSocket依赖jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
(2). 创建一个Java类,例如叫WebSocketConfig.java
package com.green.rainbow.modules.app.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 如果你的项目的拦截器或者过滤器,拦截的是所有路径(/*)那么他也会拦截ws://localhost:8080/websocket 的“/websocket ” 路径,所以会连接不上。解决的方法就是,在你的项目中配置一下不拦截这个路径。
- 。。。。在实现websocket的类上加上注解@Component,嗯~~完美。