目录
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
config
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
socket
package cn.wqk.serverwebsocket.socket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created with IntelliJ IDEA.
*
* @Auther: 吴青珂
* @Date: 2021/05/31/16:16
* @Description:
*/
@Component
@Slf4j
@ServerEndpoint("/websocket/{username}") //暴露的ws应用的路径
public class WebSocket {
/** 当前在线客户端数量(线程安全的) */
private static AtomicInteger onlineClientNumber = new AtomicInteger(0);
/** 当前在线客户端集合(线程安全的):以键值对方式存储,key是连接的编号,value是连接的对象 */
private static Map<String ,Session> onlineClientMap = new ConcurrentHashMap<>();
/**
* 客户端与服务端连接成功
* @param session
* @param username
*/
@OnOpen
public void onOpen(Session session,@PathParam("username") String username){
/*
do something for onOpen
与当前客户端连接成功时
*/
onlineClientNumber.incrementAndGet();//在线数+1
onlineClientMap.put(session.getId(),session);//添加当前连接的session
log.info("时间[{}]:与用户[{}]的连接成功,当前连接编号[{}],当前连接总数[{}]",
new Date().toLocaleString(),
username,
session.getId(),
onlineClientNumber);
}
/**
* 客户端与服务端连接关闭
* @param session
* @param username
*/
@OnClose
public void onClose(Session session,@PathParam("username") String username){
/*
do something for onClose
与当前客户端连接关闭时
*/
onlineClientNumber.decrementAndGet();//在线数-1
onlineClientMap.remove(session.getId());//移除当前连接的session
log.info("时间[{}]:与用户[{}]的连接关闭,当前连接编号[{}],当前连接总数[{}]",
new Date().toLocaleString(),
username,
session.getId(),
onlineClientNumber);
}
/**
* 客户端与服务端连接异常
* @param error
* @param session
* @param username
*/
@OnError
public void onError(Throwable error,Session session,@PathParam("username") String username) {
/*
do something for onError
与当前客户端连接异常时
*/
error.printStackTrace();
}
/**
* 客户端向服务端发送消息
* @param message
* @param username
* @throws IOException
*/
@OnMessage
public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {
/*
do something for onMessage
收到来自当前客户端的消息时
*/
log.info("时间[{}]:来自连接编号为[{}]的消息:[{}]",
new Date().toLocaleString(),
session.getId(),
message);
sendAllMessage(message);
}
//向所有客户端发送消息(广播)
private void sendAllMessage(String message){
Set<String> sessionIdSet = onlineClientMap.keySet(); //获得Map的Key的集合
for (String sessionId : sessionIdSet) { //迭代Key集合
Session session = onlineClientMap.get(sessionId); //根据Key得到value
session.getAsyncRemote().sendText(message); //发送消息给客户端
}
}
//只向当前客户端发送消息
private void sendOneMessage(String message){
}
}
package com.lyh.mp.socket;
import com.lyh.mp.entity.Message;
import com.lyh.mp.mapper.MessageMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.Instant;
// 使用 @ServerEndpoint 注解表示此类是一个 WebSocket 端点
// 通过 value 注解,指定 websocket 的路径
@ServerEndpoint(value = "/channel/echo")
@Component
public class EchoChannel implements
ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(EchoChannel.class);
private Session session;
// 全局静态变量,保存 ApplicationContext
private static ApplicationContext applicationContext;
// MessageMapper.
private MessageMapper messageMapper;
// 收到消息
@OnMessage
public void onMessage(String message) throws IOException {
/*
前端需要传入,发送者id,接收者id
*/
Message messageA = new Message();
messageA.setReceiverId(1L);
messageA.setSenderId(2L);
messageA.setMessageText(message);
messageMapper.insert(messageA);
LOGGER.info("[websocket] 收到消息:id={},message={}", this.session.getId(), message);
if (message.equalsIgnoreCase("bye")) {
// 由服务器主动关闭连接。状态码为 NORMAL_CLOSURE(正常关闭)。
this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Bye"));
return;
}
this.session.getAsyncRemote().sendText("[" + Instant.now().toEpochMilli() + "] Hello " + message);
}
// 连接打开
@OnOpen
public void onOpen(Session session, EndpointConfig endpointConfig) {
// 保存 session 到对象
this.session = session;
this.messageMapper = EchoChannel.applicationContext.getBean(MessageMapper.class);
LOGGER.info("[websocket] 新的连接:id={}", this.session.getId());
}
// 连接关闭
@OnClose
public void onClose(CloseReason closeReason) {
LOGGER.info("[websocket] 连接断开:id={},reason={}", this.session.getId(), closeReason);
}
// 连接异常
@OnError
public void onError(Throwable throwable) throws IOException {
LOGGER.info("[websocket] 连接异常:id={},throwable={}", this.session.getId(), throwable.getMessage());
// 关闭连接。状态码为 UNEXPECTED_CONDITION(意料之外的异常)
this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage()));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
EchoChannel.applicationContext = applicationContext;
}
}
vue参考
<template>
<div>
<table>
<thead>
<tr>
<th>消息编号</th>
<th>发送者</th>
<th>发送时间</th>
<th>发送内容</th>
</tr>
</thead>
<tbody>
<tr v-for="item in messageList" :key="item.time">
<td>{{ item.id }}</td>
<td>{{ item.username }}</td>
<td>{{ new Date(item.time).toLocaleTimeString() }}</td>
<td>{{ item.message }}</td>
</tr>
</tbody>
</table>
<input
type="text"
v-model="sendMessage"
placeholder="请输入你要发送的消息">
<button @click="handleSendButton()">发送</button>
<button @click="handleLogoutButton()">退出</button>
</div>
</template>
<script>
import {
getUsername,
removeUsername
} from "@/utils/username";
export default {
name: "Home",
data() {
return {
webSocketObject: null,
username: '',
messageList: [
],
sendMessage: ''
}
},
created() {
//从localStorage中获得username
this.username = getUsername()
//如果username不存在返回到登录页面
if (!this.username){
this.$router.push({
name: 'Login'
})
}
//初始化WebSocket
this.webSocketInit()
},
beforeDestroy() {
this.webSocketObject.close();//在该组件销毁时关闭该连接以节约资源
},
methods: {
webSocketInit(){
const webSocketUrl = 'ws://localhost:8000/websocket/'+this.username
this.webSocketObject = new WebSocket(webSocketUrl);
this.webSocketObject.onopen = this.webSocketOnOpen
this.webSocketObject.onmessage = this.webSocketOnMessage
this.webSocketObject.onerror = this.webSocketOnError
this.webSocketObject.onclose = this.webSocketOnClose
},
webSocketOnOpen(e){
console.log('与服务端连接打开->',e)
},
webSocketOnMessage(e){
console.log('来自服务端的消息->',e)
const receiveMessage = JSON.parse(e.data);
this.messageList.push(receiveMessage)
},
webSocketOnError(e){
console.log('与服务端连接异常->',e)
},
webSocketOnClose(e){
console.log('与服务端连接关闭->',e)
},
handleSendButton() {
const username = this.username
const message = this.sendMessage
this.webSocketObject.send(JSON.stringify({
id: 1,
message,
username,
time: new Date().getTime()
}))
this.sendMessage = ''
},
handleLogoutButton(){
removeUsername() //清除username然后断开连接
this.webSocketObject.close();
this.$router.push({
name: 'Login'
})
}
},
}
</script>
<style scoped>
</style>