- 作 者:是江迪呀
- ✒️本文关键词:
websocket
、网络
、长连接
、前端
- ☀️每日 一言:
任何一个你不喜欢而又离不开的地方,任何一种你不喜欢而又无法摆脱的生活,都是监狱!
我们在日常开发中是否会思考,为什么一个系统在没有任何请求的前提下,会接受到服务器端发来的消息?聊天软件是为什么可以做到消息的发送和实时接收?网络游戏中为什么我可以在我们屏幕中看到其它玩家的操作?今天这篇关于WebSocket
的文章完全可以解决你的疑问。废话不多说,让我们开始吧!
Websocket
介绍Websocket
?WebSocket
是一种全双工通信协议,它能够在单个TCP
连接上实现 双向、持久的实时通信, 无需频繁地发起连接和关闭连接。通过WebSocket
,我们可以在浏览器和服务器之间建立稳定的连接,实时传递数据,实现即时聊天、实时更新、多人在线游戏等功能。
Websocket
的特点(1)双向通信: WebSocket
允许客户端和服务器之间进行双向通信,无需等待对方的请求或响应。
(2) 持久连接: WebSocket
连接一旦建立,会持续保持连接状态,避免了重复的连接和断开过程,减少了网络开销。
(3)低延迟: 由于连接一直保持打开状态,数据的传输可以更快地实现,从而实现低延迟的实时通信。
(4) 较少的数据传输量: 与传统的HTTP
请求相比,WebSocket
传输的数据头部信息较少,减少了数据传输量,提高了效率。
(5) 协议支持: WebSocket
是一种独立于应用层协议的协议,可以在多种编程语言和平台上使用。
Websocket
和Http
区别(1)通信方式区别:
WebSocket
允许在客户端和服务器之间建立持久性的连接,双方可以随时相互发送数据。这种方式适合实时性要求较高、交互复杂的应用,如实时聊天、在线游戏等。(2)连接状态区别:
Http
相当于写信,需要一来一回。
Websocket
相当于打电话,只要不挂断,咱们可以一直通话。
我采用 客户端:Client
和 服务器端:Service
的方式来展示WebSocket
的实现方式。客户端使用JS
实现服务器端使用Java
+ SpringBoot
来实现。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Websocket</title>
</head>
<body>
<input id="messageInput" placeholder="消息">
<button id="sendButton">发送</button>
</body>
</html>
<script>
// url 路径最后的 1 表示是那个客户端
const websocket = new WebSocket('ws://192.168.31.136:5050/websocket/1');
websocket.onmessage = event => {
const data = JSON.parse(event.data);
// 处理服务器发送过来的消息
console.log('Received message:', data);
};
sendButton.addEventListener('click', () => {
const message = messageInput.value;
const messageData = {
message: message
};
//发送消息到服务端
websocket.send(JSON.stringify(messageData));
messageInput.value = ''; // 清空输入框
});
window.addEventListener('beforeunload', () => {
// 在页面关闭前关闭 WebSocket 连接
websocket.close();
});
</script>
引入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
<version>2.3.7.RELEASEversion>
dependency>
SpringBoot
启动类:
@SpringBootApplication
@EnableWebSocket
public class ApplicationRun {
public static void main(String[] args) {
SpringApplication.run(ApplicationRun.class,args);
}
}
这里注意一定要添加上@EnableWebSocket
,表示开启Websocket
。
application.ymal
配置文件:
server:
port: 5050
Websocket
服务端:
package com.hjd.websocket.websocketserver;
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.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint("/websocket/{clientId}")
@Slf4j
public class MsgWebSocketServer {
/**
* 连接成功后会调用的方法
*/
@OnOpen
public void webSocketOpen(@PathParam("clientId") String clientId) {
log.info("客户端{}连接上了服务端:", clientId);
}
/**
* 关闭连接会调用方法
* 关闭方法在服务器端停止或者是客户端页面关闭时都会被调用。
* 或者是客户端调用了关闭链接的方法。
*/
@OnClose
public void onClose() {
log.info("有客户端断开了连接!");
}
/**
* 接收到消息会调用的方法
*/
@OnMessage
public void onMessage(@PathParam("clientId") String clientId,String message) {
log.info("客户端:{}发来了消息:{}",clientId,message);
}
/**
* 出现错误时调用的方法
*/
@OnError
public void onError(Session session, Throwable error) {
log.info("服务器端发生了错误!");
}
}
在客户端发送消息给服务器端:
要想服务器端推送消息给客户端,有个问题是服务器端如何知道你要发送给那个客户端呢?所以就必须存储下已经连接到服务器端的客户端消息。需要改动下代码:
Websocket
服务端(省略了onError
和onClose
方法它们不变):
@Component
@ServerEndpoint("/websocket/{clientId}")
@Slf4j
public class MsgWebSocketServer {
/**
* 用来存放已经成功连接到服务器端的客户端
*/
private static ConcurrentHashMap<String, MsgWebSocketServer> webSocketSet = new ConcurrentHashMap<>();
private Session session;
/**
* 客户端标识
*/
private String clientId;
/**
* 连接成功后调用的方法
*/
@OnOpen
public void webSocketOpen(Session session,@PathParam("clientId") String clientId) {
this.saveClient(session,clientId);
log.info("客户端:{}连接上了服务端", clientId);
}
@OnMessage
public void onMessage(@PathParam("clientId") String clientId,String message,@PathParam("toClientId") String toClientId) throws Exception {
log.info("客户端:{}发来了消息:{}",clientId,message);
//给指定的客户端推送消息
if(toClientId != null){
webSocketSet.get(toClientId).sendMessage(message);
}
}
/**
* 保存客户端
* @param session
* @param clientId
*/
private void saveClient(Session session,String clientId){
this.session = session;
this.clientId = clientId;
if (webSocketSet.containsKey(clientId)) {
webSocketSet.remove(clientId);
webSocketSet.put(clientId, this);
} else {
webSocketSet.put(clientId, this);
}
}
/**
* 发送消息
* @param message
* @throws Exception
*/
private void sendMessage(String message) throws Exception {
this.session.getBasicRemote().sendText(message);
}
}
使用调接口的方式,触发服务器推送消息动作:
package com.hjd.websocket.controller;
import com.hjd.websocket.websocketserver.MsgWebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.websocket.server.PathParam;
@RequestMapping("/send")
@RestController
public class WebSocketController {
@Autowired
private MsgWebSocketServer msgWebSocketServer;
/**
* 发送消息给指定客户端
* @param msg
* @param toClient
* @throws Exception
*/
@GetMapping("/sendMsgToClient/{msg}")
public void getIndex(@PathParam("msg") String msg,@PathParam("toClient") String toClient) throws Exception {
msgWebSocketServer.onMessage(null,msg,toClient);
}
}
调用推送消息接口:
http://localhost:5050/send/sendMsgToClient?msg=%22%E5%93%88%E5%93%88%E5%93%88%E5%93%88%22&toClient=1
这个也非常简单,我们已经把连接上的客户端全部存储到了ConcurrentHashMap
集合中(之所以使用ConcurrentHashMap
是因为它线程安全,可以保证再多个客户端链接时,存储的信息不会错乱)只需要遍历下集合,给每个客户端都发送消息即可实现广播的效果了。
基于上面的讲解,我们在客户端发送消息时指定下接受消息的toClient
就搞定了。
这个思路也比较简单,不过得分房间,比如一个房间内有四个玩家,其中一个玩家操作了游戏中的对象,只需要将对应的操作指令发送给服务器端,然后广播给房间内的所有玩家,那么就可以实现游戏世界的状态同步,让玩家能够看到其他玩家的操作了。
3.5测试演示的就是。
最后通过一张图来说明吧!
我想使用Websocket
实现一个多人在线游戏作为示例更加充分的说明Websocket
的应用。等我吃60个汉堡
再说吧~!