一、前言
在 WebSocket 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以数据互相传送。其特别重要的一个功能是可以实现服务端主动推送给前端,让前端去响应处理。刚好最近在开发中遇到了这个场景,所以将处理经验过程记录一下。
二、在 SpingBoot 中使用 Websocket(后端)
1、maven 依赖包的引用
在项目的 pom 文件中引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、在 SpringBoot 的启动类中添加注解和 Bean
@EnableWebSocket
@SpringBootApplication(scanBasePackages = "com.gmcc.frame")
public class SingleApplication {
public static void main(String[] args) {
SpringApplication.run(SingleApplication.class, args);
}
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3、WebSocket的使用(以 chat-room 为例)
(1)工具类
public final class WebSocketUtils {
/**
* 模拟存储 websocket session 使用
*/
public static final Map<String, Session> LIVING_SESSIONS_CACHE = new ConcurrentHashMap<>();
public static void sendMessageAll(String message) {
LIVING_SESSIONS_CACHE.forEach((sessionId, session) -> sendMessage(session, message));
}
/**
* 发送给指定用户消息
*
* @param session 用户 session
* @param message 发送内容
*/
public static void sendMessage(Session session, String message) {
if (session == null) {
return;
}
final RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic == null) {
return;
}
try {
basic.sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送给指定用户数据实体包
*
* @param session 用户 session
* @param socketEntity 数据实体包,需要自定义
*/
public static void sendEntity(Session session, SocketEntity socketEntity) {
if (session == null) {
return;
}
final RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic == null) {
return;
}
try {
basic.sendText(JsonUtil.toJson(socketEntity));
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)控制器
@RestController
@ServerEndpoint("/chat-room/{username}")
public class WebSocketEndpoint {
private static final Logger log = LoggerFactory.getLogger(WebSocketEndpoint.class);
@OnOpen
public void onConnect(@PathParam("username") String username, Session session) {
LIVING_SESSIONS_CACHE.put(username, session);
String message = "欢迎用户[" + username + "] 登录系统!";
log.info(message);
// 并且通知其他人当前用户已经进入聊天室了
sendMessageAll(message);
}
@OnMessage
public void onMessage(@PathParam("username") String username, String message) {
log.info(message);
sendMessageAll("用户[" + username + "] : " + message);
}
@OnClose
public void onClose(@PathParam("username") String username, Session session) {
// 当前的Session 移除
LIVING_SESSIONS_CACHE.remove(username);
String message = "用户[" + username + "] 已经离开系统了!";
log.info(message);
// 并且通知其他人当前用户已经离开聊天室了
sendMessageAll(message);
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
}
@PostMapping("/chat-room/{sender}/to/{receive}")
public void onMessage(@PathVariable("sender") String sender, @PathVariable("receive") String receive, String message) {
sendMessage(LIVING_SESSIONS_CACHE.get(receive), "[" + sender + "]" + "-> [" + receive + "] : " + message);
}
}
三、在 Vue 中使用 Websocket(前端)
(1)工具类
const wsUrl = process.env.SOCKET_BASE_API + '/chat-room/'
let ws = null
function doMessage(data) {
const socketEntity = JSON.parse(data)
// 处理服务器推送的信息,需要实现自己的业务逻辑
// ......
}
export function runSocket(userAccount) {
if (ws !== null) {
return
}
ws = new WebSocket(wsUrl + userAccount)
ws.onopen = function() {
console.log('建立 websocket 连接...')
}
ws.onmessage = (event) => {
// 服务端发送的消息
doMessage(event.data)
}
ws.onclose = function() {
console.log('关闭 websocket 连接...')
}
}
export function closeSocket() {
if (ws !== null) {
ws.onclose()
ws = null
}
}
(2)使用 Websocket
需要根据自己的实际情况,在合适的时机建立连接和断开连接,以下是在 store 中的用法举例
import { runSocket, closeSocket } from '@/api/socket'
const state = {
......
}
const mutations = {
......
// 设置用户相关信息
SET_USER_INFO: async(state, { ...... }) => {
......
await runSocket(account)
},
// 重置用户相关信息
RESET_USER: async(state) => {
......
closeSocket()
}
......
}
const actions = {
......
// 登录
login({ commit }, userInfo) {
......
},
// 获取用户信息
getInfo({ state, commit, dispatch }) {
return new Promise((resolve, reject) => {
aclApi.getUserInfo().then(res => {
// 设置用户信息
......
commit('SET_USER_INFO', userInfo)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// 登出
logout({ state, commit, dispatch }) {
return new Promise((resolve, reject) => {
aclApi.logout().then(() => {
commit('RESET_USER')
resolve()
}).catch(error => {
reject(error)
})
})
},
......
}
export default {
namespaced: true,
state,
mutations,
actions
}
四、Nginx的配置(前端分离部署)
(1)在 http 配置内添加:
# websocket support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
(2)在 server 配置内添加:
以下为一个举例的配置,需要更加自己的实际情况修改,请不要完全照抄:
location ^~ /service-module/ {
proxy_pass http://websocket-module/service-module/;
proxy_set_header Host $host;
proxy_set_header Referer $http_referer;
proxy_set_header Cookie $http_cookie;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# websocket support
proxy_http_version 1.1;
proxy_connect_timeout 4s;
proxy_read_timeout 36000s;
proxy_send_timeout 12s;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
注意:proxy_read_timeout 是服务器对你等待最大的时间,也就是说,当你 WebSocket 使用 nginx 转发的时候,超出这个时间就会自动断开,请根据具体需求调整。
最后修改:2020-04-02 11:31:07
© 著作权归作者所有
如果觉得我的文章对你有用,请随意赞赏
扫一扫支付

发表评论