springboot+WebSocket详细demo
新建springboot项目省略。。。。
pom添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
application配置文件:
spring.application.name=thinkd-webserve
server.port=9800
#server.port=${random.int(9800,9809)}
eureka.instance.ip-address=true
eureka.client.service-url.defaultZone=http://localhost:9000/eureka,http://localhost:9001/eureka
新建html文件,放到资源static目录下:
<!DOCTYPE html>
<!--<html xmlns:th="http://www.thymeleaf.org">-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HELLO WEBSOCKET!</title>
<!-- 微软压缩版 -->
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script>
<!-- <!– 官网压缩版 –>-->
<!-- <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>-->
</head>
<body>
<h2 align="center" style="color: red"> 欢迎欢迎热烈欢迎!!!</h2>
<p align="center" style="color: blue">
这是第一个WebSocket页面啊!!!!!!!!!!
</p>
<div align="center">
<form action="#" id="wsreqform">
<span style="color: brown"><b>WSmsg: </b></span> 
<input type="text" name="message" value="发送一句话吧"/>
<br/><br/><span style="color: brown"><b>提交: </b></span> 
<input type="submit" id="subws01"/>
</form>
</div>
</body>
</html>
<script type="text/javascript">
// var socket;
var index;
var msgs;
var ws_id = 2;
var basepath = "http://localhost:9800/webserve/";//自己的项目访问URI
$(function () {
socketChecks();
verifyMsgInfo();
});
function socketChecks() {
if(typeof(WebSocket) == "undefined") {
console.log(">>>>>>>>>> 您的浏览器不支持WebSocket");
alert("您的浏览器不支持WebSocket");
}else{
console.log(">>>>>>>>>> 您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于
// index = new WebSocket("ws://"+ host +":"+ port +"/websocket/" + ws_id);
index = new WebSocket((basepath + "websocket/" + ws_id).replace("http", "ws"));
//socket = new WebSocket("${basepath}websocket/${cid}".replace("http","ws"));
//打开事件
index.onopen = function() {
console.log(">>>>>>>>>> WebSocket 已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
index.onmessage = function(msg) {
console.log(">>>>>>>>>> 发现消息进入 开始处理前端触发逻辑");
console.log(">>>>>>>>>> WebSocket获得信息: " + msg.data);
alert(msg.data);
};
//关闭事件
index.onclose = function() {
console.log(">>>>>>>>>> WebSocket 已关闭");
};
//发生了错误事件
index.onerror = function() {
console.log(">>>>>>>>>> WebSocket 发生了错误,可以尝试刷新页面");
alert("WebSocket错误,请尝试刷新页面");
}
}
}
// //生成从minNum到maxNum的随机数
// function randomNum(minNum, maxNum) {
// switch (arguments.length) {
// case 1:
// return parseInt(Math.random() * minNum + 1, 10);
// // break;
// case 2:
// return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
// // break;
// default:
// return 0;
// // break;
// }
// }
function verifyMsgInfo() {
$("#subws01").click(function () {
// var msgs = $("#wsreqform input[type='text']").value();
var formData = $("#wsreqform input[type='text']").serialize();
console.log(" ^^^^^^^^^^^^^^^^ 客户端WebSocket请求参数JSON:" + formData);
alert(formData);
//提交URL
var wsurl = basepath + "sysWS/socket/push/" + ws_id;
getTestSocket(wsurl, formData);
return false;
});
}
function getTestSocket(wsurl, wsparam){
$.ajax({
type: "post",
url: wsurl.toString().trim(),
// data: {},
data: wsparam,
dataType: "json",
success: function (data) {
console.log(" ^^^^^^^^^^^^^^^^ 服务器WebSocket数据返回正确!" + data.toString());
},
error: function () {
console.log(" ^^^^^^^^^^^^^^^^ 服务器WebSocket数据异常了!");
alert("【服务器WebSocket数据异常了】");
}
});
}
</script>
websocket配置文件:
//@Configuration
@Component //开启WebSocket支持
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
websocket服务类:
@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//接收sid
private String sid = "";
//连接建立成功调用的方法
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:" + sid + ",当前在线人数为" + getOnlineCount());
this.sid = sid;
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("websocket IO异常");
}
}
//连接关闭调用的方法
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
//收到客户端消息后调用的方法 message 客户端发送过来的消息
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口" + sid + "的信息:" + message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 通信异常方法
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
//实现服务器主动推送
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
// 群发自定义消息
public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
log.info("推送消息到窗口" + sid + ",推送内容:" + message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if (sid == null) {
item.sendMessage(message);
} else if (item.sid.equals(sid)) {
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
写个controller:
@Controller
@RequestMapping("/sysWS")
public class WebsocketSysController {
//@Autowired
//private ThymeleafViewResolver thymeleafViewResolver;
//@Autowired
//private MessageSource messageSource;
//页面请求
//@RequestMapping(value = "/index/{userId}", method = RequestMethod.GET)
//public String socket(@PathVariable("userId") String userId) {
@GetMapping(value = "/actiond")
//public String socket(@RequestParam("userId") String userId) {//, Model model) {
// //ModelAndView mav = new ModelAndView("/wstest1");
// //mav.addObject("userId", userId);
// //model.addAttribute("userId", userId);
// return "wstest1";
//}
public Object getSocketd(ModelMap model){
model.addAttribute("aaa","我是一个兵");
model.addAttribute("bbb","来自老百姓!");
return "wstest1";//自定义的html文件
}
@ResponseBody //推送提交数据接口
@RequestMapping("/socket/push/{cid}")
public Map pushToWeb(@PathVariable("cid") String cid, String message) {
Map result = new HashMap();
try {
WebSocketServer.sendInfo(message, cid);
result.put("code", 200);
result.put("msg", "success");
} catch (IOException e) {
e.printStackTrace();
result.put("code", 500);
result.put("msg", "ERROR");
}
System.out.println("响应数据:》》》》》》》》》 "+JSONObject.toJSONString(result));
return result;
}
启动类加上注解:@EnableDiscoveryClient
创建eureka注册中心
pom文件:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
*配置文件node01:*
## 两个注册服务name必须一样,不然高可用会导致unavailable-replicas
spring.application.name=thinkd-eureka
#server.port=${random.int(9000,9003)}
server.port=9000
# 指定通过IP注册,一般服务集群时配置,需要ip-address参数一起设置(保证hostName和ip-address一样),
# 以免eureka管理页面出现不可用分片提示信息(unavailable-replicas),
# 服务的注册默认是通过hostName注册的,当判断注册的hostName和服务
eureka.instance.prefer-ip-address=true
# 强制指定eureka的ip地址
eureka.instance.ip-address=${EUREKA_IP:${eureka.instance.hostname}}
# eureka server管理页面显示的服务状态
eureka.instance.instance-id=${eureka.instance.hostname}:${server.port}
eureka.instance.hostname=eureka01
# 集群时需要强制手动指定,fetch-registry、register-with-eureka都需要设置为true
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
#eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka
eureka.client.service-url.defaultZone=http://eureka02:9001/eureka
*配置文件node02:*
## 两个注册服务name必须一样,不然高可用会导致unavailable-replicas
spring.application.name=thinkd-eureka
#server.port=${random.int(9000,9003)}
server.port=9001
# 指定通过IP注册,一般服务集群时配置,需要ip-address参数一起设置(保证hostName和ip-address一样),
# 以免eureka管理页面出现不可用分片提示信息(unavailable-replicas),
# 服务的注册默认是通过hostName注册的,当判断注册的hostName和服务
eureka.instance.prefer-ip-address=true
# 强制指定eureka的ip地址
eureka.instance.ip-address=${EUREKA_IP:${eureka.instance.hostname}}
# eureka server管理页面显示的服务状态
eureka.instance.instance-id=${eureka.instance.hostname}:${server.port}
eureka.instance.hostname=eureka02
# 集群时需要强制手动指定,fetch-registry、register-with-eureka都需要设置为true
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
#eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka
eureka.client.service-url.defaultZone=http://eureka01:9000/eureka
**配置启动:**
# 项目启动会加载激活的配置文件,系统会按需加载配置文件
spring.profiles.active=node02
> 启动类添加注解: @EnableEurekaServer
分别启动eureka,webserver服务
测试效果
访问: http://localhost:9800/webserve/wstest1.html
如下图即开始通信:
服务端日志:
2020-10-09 12:20:46.833 INFO 6804 — [nio-9800-exec-2]
c.t.a.t.websocket.WebSocketServer : 有新窗口开始监听:2,当前在线人数为1
2020-10-09 12:20:54.143 INFO 6804 — [nio-9800-exec-5]
c.t.a.t.websocket.WebSocketServer : 推送消息到窗口2,推送内容:发送一句话吧
响应数据:》》》》》》》》》 {“msg”:“success”,“code”:200} 2020-10-09 12:21:13.954
INFO 6804 — [nio-9800-exec-7] c.t.a.t.websocket.WebSocketServer
: 推送消息到窗口2,推送内容:aaaaaaaaaaaaaaa 响应数据:》》》》》》》》》
{“msg”:“success”,“code”:200}
好了,可以通信了,唉,真的简单!!! 特此留个爪印,哈哈。。。