webSocket
什么是ws?
WS(WebSocket)是一种网络通信协议,它提供了在客户端和服务器之间进行双向、实时通信的能力。相比于传统的HTTP协议,WebSocket具有更低的延迟和更高的效率。
传统的HTTP协议是一种无状态的协议,每次通信都需要客户端发起请求,服务器响应后关闭连接,因此无法实现实时的双向通信。而WebSocket协议则在初始握手阶段首先通过HTTP协议建立连接,然后升级为双向通信的WebSocket连接。一旦建立了WebSocket连接,客户端和服务器就可以通过该连接进行实时的数据交换,而无需每次都重新建立连接。
WebSocket协议使用基于帧的消息传递机制,允许客户端和服务器以消息的形式进行通信。客户端可以发送消息给服务器,服务器也可以主动推送消息给客户端,实现了真正的双向通信。这种实时通信的特性使得WebSocket在许多场景下非常有用,例如在线聊天应用、实时数据监控和游戏等。
SpringBoot集成WebSocket
在Java项目中的pom.xml文件中导入依赖wzbsocket依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
导入websocket配置文件注册成为一个工具类,交给ioc管理。加载websocket
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket支持
* @author wzk
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
加载业务逻辑
websocket方法:
@ServerEndpoint(value = "/chat/{username}"):用于建立连接路径,username表示当前连接的用户(前端的连接 路径为:ws://localhost:端口号/chat/当前连接用户)
@Component:当前类注入ioc管理对方
@OnOpen:用户发送请求触发函数方法
@PathParam("name"):将用户请求路径中的 参数注入到该方法的参数上面
@OnError : 用户请求连接异常触发函数
@OnMessage:用户客户端*(前端)项目服务端(后端)发送信息触发函数
@OnClose : 用户关闭连接触发函数
发送消息方法sendMessage :
toSession.getBasicRemote().sendText(msg)
:通过toSession
的getBasicRemote()
方法获取到与目标会话关联的RemoteEndpoint.Basic
对象,通过调用sendText(msg)
方法发送文本消息。sendText
方法是WebSocket API提供的方法,用于发送文本消息至远程端点。
import com.alibaba.fastjson.JSON;
import com.hqyj.project.pojo.Message;
import com.hqyj.project.utils.ApplicationContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
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.List;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/chat/{username}")
@Component
@Slf4j
public class SocketServer {
// 保存链接的session,key为用户名,value为对应的session名
private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
/**
* 创建连接
* 用于监听建立连接,当有客户端与该服务端点建立连接时,将会自回调该注解标注的方法
* @param session
* @param username
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "username") String username) {
log.info("用户{}已创建连接", username);
String key = "webSocket:"+username;
sessionMap.put(username,session);
//获取redis对象
RedisTemplate redisTemplate =(RedisTemplate)ApplicationContextUtil.getBean("redis");
//判断是否为空
List<String> members = redisTemplate.opsForList().range(key,0,-1);
if (null != members && !members.isEmpty()){
for (String message : members) {
Session toSession = sessionMap.get(username);
//发送缓存信息
sendMessage(toSession,message);
}
//删除redis中的键
redisTemplate.delete(key);
}
}
/**
* 用于监听客户端向服务端发送消息,当客户端与服务端发送消息时,将会回调该注解标注的方法
* @param msg
* @param username
*/
@OnMessage
public void onMessage(String msg,@PathParam(value = "username") String username){
log.info("用户{}发来消息:{}",username,msg);
//解析用户发送的信息,使用系列化,映射到Message类
Message message = JSON.parseObject(msg, Message.class);
//使用工具类获取redis缓存数据库
RedisTemplate redisTemplate =(RedisTemplate)ApplicationContextUtil.getBean("redis");
//根据message中的to属性获取接收消息的用户的session,利用其session将消息转发过
if (null == sessionMap.get(message.getTo())){ //发送给的用户为空
//存入redis
redisTemplate.opsForList().rightPush("webSocket:"+message.getTo(),message.getMsg());
return;
}
Session toSession = sessionMap.get(message.getTo());
//发送信息给用户
sendMessage(toSession, message.getMsg());
}
/**
* 用于监听连接关闭,当客户端与该服务端点断开连接时,将会回调该注解标注的方法
* @param session
* @param username
*/
@OnClose
public void onClose(Session session, @PathParam(value = "username") String username){
log.info("用户{}已关闭连接", username);
sessionMap.remove(username);
}
/**
* 用于监听该连接上的任何错误,当客户端与该服务端点的连接发生任何异常,都将回调该注解标注的方法
* 注意该方法的参数必选Throwable,可选Sessiion以及0-n个String参数,且String参数需要使用@PathParam注解标注
* @param throwable
* @param username
*/
@OnError
public void onError(Throwable throwable,@PathParam(value = "username") String username){
log.error("用户{}连接发生异常", username);
}
/**
* 用来发送消息的方法,参数分别为接收消息的用户的session,和对应的消息
*/
private void sendMessage(Session toSession,String msg){
try {
toSession.getBasicRemote().sendText(msg);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
前端Vue代码
代码解析注释里面已经存在,就不过多阐述
<template>
<div class="chat-container">
<div class="chat-header">
<span class="chat-header-text">{{ userFriend.userFriendNickname }}</span>
</div>
<div class="chat-messages">
<div v-for="message in messages" :key="message.id" class="chat-message">
<div v-if="message.email === user.email " class="chat-message-right">
<div class="chat-message-author">{{ message.nickName }}</div>
<div class="chat-message-avatar" :style="{ backgroundImage: `url(${message.cover})` }"></div>
<div class="chat-message-text">{{ message.text }}</div>
</div>
<div v-else class="chat-message-left">
<div class="chat-message-author">{{ message.nickName }}</div>
<div class="chat-message-avatar" :style="{ backgroundImage: `url(${message.cover})` }"></div>
<div class="chat-message-text">{{ message.text }}</div>
</div>
</div>
<div v-if="messages.length === 0" class="no-message-tip">暂无消息</div>
</div>
<div class="chat-input-container">
<input v-model="inputText" placeholder="输入消息" @keydown.enter="send" />
<button @click="send">发送</button>
</div>
</div>
</template>
<script>
// 创建websocket
let webSocket = null;
export default {
name: "Chat",
data() {
return {
// toUser: "",
messages: [],
// userName: "",
inputText: "",
messageId: 1,
user:{
cover:"",
email:"",
nickName:""
},
userFriend:{
userEmail:"",
userFriendEmail:"",
userFriendNickname:"",
userFriendCover:""
}
};
},
methods: {
sendMessage(message) { //当前账号发送信息
if (message !== "") {
this.messages.push({
id: this.messageId++,
email:this.user.email, //当前账号
nickName: this.user.nickName, //当前账号昵称
text: message, //当前发送的信息
cover: this.user.cover //当前头像的url
});
// console.log(this.messages)
this.inputText = "";
}
},
responseMessage(message) { //响应信息
if (message !== "") {
this.messages.push({
id: this.messageId++,
email:this.userFriend.userFriendEmail,
nickName: this.userFriend.userFriendNickname, //对方昵称
text: message, //对方发送的信息
cover: this.userFriend.userFriendCover //对方头像的url
});
// console.log(this.messages)
this.inputText = "";
}
},
connectWebSocket() { //获取链接
const userName = this.user.email; //当前账号
console.log(this.userFriend)
const target = "ws://localhost:8081/chat/" + userName;
if ("WebSocket" in window) { //判断浏览器是否支持,创建websocket对象
webSocket = new WebSocket(target);
} else {
alert("浏览器不支持websocket");
}
webSocket.onerror = function () {
alert("发生错误连接失败");
};
webSocket.onopen = function () {
console.log("连接成功");
};
webSocket.onmessage = (res) => {
console.log(res)
this.responseMessage(res.data); //res.data对方响应的信息
};
webSocket.onclose = function () {
// this.sendMessage("Loc MSG:关闭连接");
console.log("关闭连接");
};
window.onbeforeunload = function () {
webSocket.close();
};
},
closeWebSocket() {
webSocket.close();
},
send() {
const msg = this.inputText.trim();
if (msg !== "") {
const toUser = this.userFriend.userFriendEmail;
const chatMsg = {
msg: msg,
to: toUser
};
webSocket.send(JSON.stringify(chatMsg));
this.sendMessage(msg);
}
},
sendToUser() {
//获取当前用户账号和发送信息用户账号
this.user = JSON.parse(sessionStorage.getItem("user"));
// console.log(this.user)
const userFriend = JSON.parse(sessionStorage.getItem("userFriendEmail"));
// console.log(userFriend)
this.userFriend.userFriendEmail = userFriend.userFriendEmail;
this.userFriend.userFriendNickname = userFriend.userFriendNickname;
this.userFriend.userFriendCover = userFriend.userFriendCover;
// console.log(this.userFriend)
},
},
mounted() {
this.sendToUser();
this.connectWebSocket();
},
};
</script>
<style scoped>
.chat-container {
width: 400px;
height: 500px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
}
.chat-header {
padding: 10px;
background-color: #1890ff;
color: #fff;
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
}
.chat-header-text {
width: 100%;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-messages {
flex-grow: 1;
padding: 10px;
overflow-y: auto;
background-color: #f5f5f5;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.chat-message {
margin-bottom: 10px;
}
.chat-message-left {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.chat-message-avatar {
/*变为行元素*/
display: inline;
width: 40px;
height: 40px;
background-size: cover;
background-position: center;
border-radius: 50%;
margin-right: 10px;
}
.chat-message-author {
font-weight: bold;
margin-bottom: 5px;
}
.chat-message-right .chat-message-text {
display: inline;
position:relative;
top: -40px;
right: 55px;
word-wrap: break-word;
max-width: 200px;
padding: 8px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
.chat-message-left .chat-message-text {
display: inline;
position:relative;
top: -40px;
right: -45px;
word-wrap: break-word;
max-width: 200px;
padding: 8px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
.chat-message-right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.chat-input-container {
display: flex;
align-items: center;
padding: 10px;
background-color: #f5f5f5;
border-top: 1px solid #ccc;
}
.chat-input-container input {
flex-grow: 1;
height: 30px;
margin-right: 10px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ccc;
outline: none;
font-size: 14px; /* 添加字体大小样式 */
}
.chat-input-container button {
padding: 5px 10px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
outline: none;
font-size: 14px; /* 添加字体大小样式 */
}
</style>
成品图
完成全双关双向通信