赞
踩
WS(WebSocket)是一种网络通信协议,它提供了在客户端和服务器之间进行双向、实时通信的能力。相比于传统的HTTP协议,WebSocket具有更低的延迟和更高的效率。
传统的HTTP协议是一种无状态的协议,每次通信都需要客户端发起请求,服务器响应后关闭连接,因此无法实现实时的双向通信。而WebSocket协议则在初始握手阶段首先通过HTTP协议建立连接,然后升级为双向通信的WebSocket连接。一旦建立了WebSocket连接,客户端和服务器就可以通过该连接进行实时的数据交换,而无需每次都重新建立连接。
WebSocket协议使用基于帧的消息传递机制,允许客户端和服务器以消息的形式进行通信。客户端可以发送消息给服务器,服务器也可以主动推送消息给客户端,实现了真正的双向通信。这种实时通信的特性使得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);
- }
- }
-
-
- }
代码解析注释里面已经存在,就不过多阐述
- <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>
成品图
完成全双关双向通信
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。