websocket 是http的补充,为了实现实时通信,一次握手便可以保持长连接,避免繁琐的请求头浪费宽带。websocket 可以非常简单的实现一个聊天室的项目。
下面,我简易地用Springboot+websocket 实现聊天室小项目。
(1)pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.1.RELEASE
com.microservice
websocket
0.0.1-SNAPSHOT
websocket
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-freemarker
org.springframework.boot
spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-devtools
org.springframework.boot
spring-boot-maven-plugin
(2)一个配置类 WebSocketConfig
package com.microservice.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @Description: 编写一个WebSocketConfig配置类,注入对象ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
(3)一个websocket 业务类
package com.microservice.websocket.socket;
import com.microservice.websocket.entity.UserInfo;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* * @Description: websocket的具体实现类
* * 使用springboot的唯一区别是要@Component声明下,而使用独立容器是由容器自己管理websocket的,
* * 但在springboot中连容器都是spring管理的。
* 虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,
* 所以可以用一个静态set保存起来。
*/
@ServerEndpoint(value = "/websocket/{nickName}")
@Component
public class MyWebSocket {
//用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
private static Map connectmap = new HashMap<>();//用session作为key,保存用户信息
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("nickName") String nickName) {
this.session = session;
UserInfo userInfo = new UserInfo(session.getId(),nickName);
connectmap.put(session,userInfo);
webSocketSet.add(this); //加入set中
System.out.println(nickName+" 上线了!当前在线人数为" + webSocketSet.size());
//群发消息,告诉每一位
broadcast(nickName+" 上线了!-->当前在线人数为:"+webSocketSet.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
String nickName=connectmap.get(session).getNickName();
connectmap.remove(session);
webSocketSet.remove(this); //从set中删除
System.out.println(nickName+" 下线了!当前在线人数为" + webSocketSet.size());
//群发消息,告诉每一位
broadcast(nickName+" 下线,当前在线人数为:"+webSocketSet.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* */
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
//群发消息
String nickName=connectmap.get(session).getNickName();
broadcast(nickName+" 说:"+message);
}
/**
* 发生错误时调用
*
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
/**
* 群发自定义消息
* */
public void broadcast(String message){
for (MyWebSocket item : webSocketSet) {
//同步异步说明参考:http://blog.csdn.net/who_is_xiaoming/article/details/53287691
//this.session.getBasicRemote().sendText(message);
item.session.getAsyncRemote().sendText(message);//异步发送消息.
}
}
}
(3)一个用户实体类 UserInfo
package com.microservice.websocket.entity;
/**
* @program: websocket->UserInfo
* @description: 用户信息
* @author: ChenZhihao
* @create: 2019-12-26 16:04
**/
public class UserInfo {
private String id;
private String nickName;
private String password;
public UserInfo() {
}
public UserInfo(String id, String nickName) {
this.id = id;
this.nickName = nickName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
(4)如果你不是前后端分离,即前后端放在同一个项目里面,还需要配置页面的路径。如果是前后端分离,这步省略。
package com.microservice.websocket.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("web")
public class WebController {
@RequestMapping("index")
public String index(){
return "index";
}
}
(5)好了,后端的代码写完了。这步开始写页面和js。
页面 index
Spring Boot Demo - FreeMarker
用户名:
消息:
(6)页面的js : index.js
var websocket = null;
function connectWebSocket(){
//判断当前浏览器是否支持WebSocket
//判断当前浏览器是否支持WebSocket
if ('WebSocket'in window) {
var nickName=document.getElementById('nickName').value;
console.log("ws://localhost:8080/websocket/"+nickName)
websocket = new WebSocket("ws://localhost:8080/websocket/"+nickName);
} else {
alert('当前浏览器不支持websocket');
}
//连接发生错误的回调方法
websocket.onerror = function() {
setMessageInnerHTML("error");
};
//接收到消息的回调方法
websocket.onmessage = function(event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function() {
setMessageInnerHTML("Loc MSG:关闭连接");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
websocket.close();
}
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '
';
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
(7)CSS : index.css
#message{
margin-top:40px;
border:1px solid gray;
padding:20px;
}
(8)好了,全部写完了。现在开始测试。
1. 先打开一个网页:http://localhost:8080/web/index
输入用户名 志豪,点连接,发送一次信息。如图(志豪的界面):
2. 再打开一个页面,输入用户名 老毛,点连接,然后发送信息。如图(老毛的界面):
3. 这时,志豪看到的界面如图:
4. 当老毛关闭了页面时,志豪看到的界面如图: