- 前言
- 项目结构
- 依赖引入
- 配置bean
- controller
- socket.html
- websocket的服务
- 运行测试
- 测试一
- 测试二
- 测试三
- 代码下载
之前写过一篇类似的ws通信的博客,但感觉写的有点不忍直视,同时也不具备发送命令通信的基操。
所以重新写一篇完整的websocket配置和使用的博客。
项目结构 依赖引入以前文章地址:SpringBoot2.0集成WebSocket,实现后台向前端推送信息
本次使用到的springboot的版本为:2.1.4.RELEASE。
org.springframework.boot spring-boot-starter-parent 2.1.4.RELEASE
其他主要依赖如下所示:
配置beanorg.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-websocket org.springframework.boot spring-boot-starter-thymeleaf com.alibaba fastjson 1.2.47
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();
}
}
controller
controller包中只有一个界面的跳转操作,其他的不需要。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class Test {
@GetMapping("/socket/{cid}")
public String socket(@PathVariable String cid,Model model){
model.addAttribute("cid", cid);
return "socket";
}
}
socket.html
666666
传递来的数据值cid:
【toUserId】:
【toUserId】:
【操作】:
websocket的服务
websocket的服务是最重要的一环,在上面的socket.html文件中,使用到socket = new WebSocket(url);其中的url则是服务器中开放出来的ServerEndpoint 地址信息。
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@ServerEndpoint("/websocket/{sid}") // 方便html页面与之关联接口
@Component
public class WebSocketServer {
private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineCount = new AtomicInteger(0);
private static Map clients = new ConcurrentHashMap<>();
private String sid; // 每个socket过来的时候,能知道是哪个界面来的
@OnOpen
public void onOpen(@PathParam("sid")String sid,Session session) {
//加入set中
log.info("session_sid--->"+session.getId()); // 当前session会自动生成一个id,但这个id从0开始累加的
log.info("sid--->"+sid);
//clients.put(session.getId(), session); // 每个页面的加载都会由其自动生成一个id
clients.put(sid, session); // 将页面的sid和session绑定
onlineCount.incrementAndGet(); //在线数加1
log.info("有新窗口开始监听:"+sid+",当前在线人数为" + onlineCount);
//log.info("有新窗口开始监听:"+session.getId()+",当前在线人数为" + onlineCount);
this.sid=sid;
sendToOne(sid,"连接成功");
}
@OnClose
public void onClose(@PathParam("sid")String sid,Session session) {
//clients.remove(session.getId()); // 从 map 中移除
clients.remove(sid);
onlineCount.decrementAndGet(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + onlineCount);
}
@OnMessage
public void onMessage(String message, Session session) {
// {"sid":"20","message":"hello websocket"} // html界面传递来得数据格式,可以自己定义
JSONObject jsonObject = JSONObject.parseObject(message);
String sid = jsonObject.getString("sid");
String msg = jsonObject.getString("message");
log.info("发送给窗口"+sid+"的信息:"+msg);
// 如果未指定sid信息,则群发,否则就单独发送
if(sid == null || sid == ""||"".equalsIgnoreCase(sid)){
sendToMore(msg);
}else{
sendToOne(sid,msg);
}
}
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误 错误信息为:"+error.getMessage());
//error.printStackTrace();
}
private void sendToMore(String message){
// 遍历上面存储的map集合,获取其中是否有满足条件的信息
for (Map.Entry sessionEntry : clients.entrySet()) {
log.info("--->"+sessionEntry.toString());
// 获取value
Session toSession = sessionEntry.getValue();
// 获取key
String key = sessionEntry.getKey();
// 排除掉自己
if (!sid.equalsIgnoreCase(key)) {
//log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
log.info("服务端给客户端[{}]发送消息{}", key, message);
toSession.getAsyncRemote().sendText(message);
}
}
}
private void sendToOne(String toSid,String message){
// 通过sid查询map中是否存在
Session session = clients.get(toSid);
if(session == null){
log.error("不存在指定的sid");
return;
}
// 存在则发送
session.getAsyncRemote().sendText(message);
}
}
运行测试
分别开启三个浏览器的窗口:
http://localhost/socket/1
http://localhost/socket/2
http://localhost/socket/3
然后打开浏览器的控制台。
此时idea控制台中的输出信息如下所示:
当窗口1中给自己发送数据时:
测试二只能自己获取到自己的信息,其他窗口未获取到。
给指定的窗口sid发送数据时:
测试三指定的窗口能够收到数据,其他窗口收不到数据。
群发。在代码中定义的条件为:当不指定toUserid时,则为群发。
gitee 代码下载地址



