- 😜作 者:是江迪呀
- ✒️本文关键词:
websocket
、网络
、长连接
、前端
- ☀️每日 一言:
任何一个你不喜欢而又离不开的地方,任何一种你不喜欢而又无法摆脱的生活,都是监狱!
一、前言
我们在日常开发中是否会思考,为什么一个系统在没有任何请求的前提下,会接受到服务器端发来的消息?聊天软件是为什么可以做到消息的发送和实时接收?网络游戏中为什么我可以在我们屏幕中看到其它玩家的操作?今天这篇关于WebSocket
的文章完全可以解决你的疑问。废话不多说,让我们开始吧!
二、Websocket
介绍
2.3 什么是Websocket
?
WebSocket
是一种全双工通信协议,它能够在单个TCP
连接上实现 双向、持久的实时通信, 无需频繁地发起连接和关闭连接。通过WebSocket
,我们可以在浏览器和服务器之间建立稳定的连接,实时传递数据,实现即时聊天、实时更新、多人在线游戏等功能。
2.2 Websocket
的特点
(1)双向通信: WebSocket
允许客户端和服务器之间进行双向通信,无需等待对方的请求或响应。
(2) 持久连接: WebSocket
连接一旦建立,会持续保持连接状态,避免了重复的连接和断开过程,减少了网络开销。
(3)低延迟: 由于连接一直保持打开状态,数据的传输可以更快地实现,从而实现低延迟的实时通信。
(4) 较少的数据传输量: 与传统的HTTP
请求相比,WebSocket
传输的数据头部信息较少,减少了数据传输量,提高了效率。
(5) 协议支持: WebSocket
是一种独立于应用层协议的协议,可以在多种编程语言和平台上使用。
2.3 Websocket
和Http
区别
(1)通信方式区别:
- Http是请求-响应式的协议: 客户端发送请求,服务器返回响应,然后连接断开。这种方式适合传输静态内容或需要客户端不断向服务器发起请求的情况。
- Websocket是全双工的通信协议:
WebSocket
允许在客户端和服务器之间建立持久性的连接,双方可以随时相互发送数据。这种方式适合实时性要求较高、交互复杂的应用,如实时聊天、在线游戏等。
(2)连接状态区别: - Http是短连接:web端请求服务器端建立连接,服务器端返回后连接断开。
- Websocket是长连接:连接一次,就会一直保持连接。
Http
相当于写信,需要一来一回。
Websocket
相当于打电话,只要不挂断,咱们可以一直通话。
三、实现方式
我采用**客户端-Client
和服务器端-Service
**的方式来展示WebSocket
的实现方式。客户端使用JS
实现服务器端使用Java
+ SpringBoot
来实现。
3.1 客户端
<!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>
3.2 服务器端
引入依赖:
<!--SpringBoot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<!--Websocket依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.3.7.RELEASE</version>
</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("服务器端发生了错误!");
}
}
3.3 测试 - 客户端发消息给服务器端
我们运行客户端在浏览器。
我们可以看到服务器端打印了如下日志:
表示已经连接上了。
在客户端发送消息给服务器端:
服务器端接收到了消息:
3.4 测试- 服务器端推送信息给客户端
要想服务器端推送消息给客户端,有个问题是服务器端如何知道你要发送给那个客户端呢?所以就必须存储下已经连接到服务器端的客户端消息。需要改动下代码:
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
客户端控制台:
3.5 测试 - 广播给所有客户端
这个也非常简单,我们已经把连接上的客户端全部存储到了ConcurrentHashMap
集合中(之所以使用ConcurrentHashMap
是因为它线程安全,可以保证再多个客户端链接时,存储的信息不会错乱)只需要遍历下集合,给每个客户端都发送消息即可实现广播的效果了。
四、应用
4.1 聊天室
基于上面的讲解,我们在客户端发送消息时指定下接受消息的toClient
就搞定了。
4.2 多人网络游戏
这个思路也比较简单,不过得分房间,比如一个房间内有四个玩家,其中一个玩家操作了游戏中的对象,只需要将对应的操作指令发送给服务器端,然后广播给房间内的所有玩家,那么就可以实现游戏世界的状态同步,让玩家能够看到其他玩家的操作了。
4.3 服务器推送
3.5测试演示的就是。
总结
最后通过一张图来说明吧!
我想使用Websocket
实现一个多人在线游戏作为示例更加充分的说明Websocket
的应用。等我吃60个汉堡
再说吧~!