WebSocket详解以及应用

  • 😜 是江迪呀
  • ✒️本文关键词websocket网络长连接前端
  • ☀️每日 一言任何一个你不喜欢而又离不开的地方,任何一种你不喜欢而又无法摆脱的生活,都是监狱!

一、前言

我们在日常开发中是否会思考,为什么一个系统在没有任何请求的前提下,会接受到服务器端发来的消息?聊天软件是为什么可以做到消息的发送和实时接收?网络游戏中为什么我可以在我们屏幕中看到其它玩家的操作?今天这篇关于WebSocket的文章完全可以解决你的疑问。废话不多说,让我们开始吧!

二、Websocket介绍

2.3 什么是Websocket?

WebSocket是一种全双工通信协议 ,它能够在单个TCP连接上实现 双向、持久的实时通信, 无需频繁地发起连接和关闭连接。通过WebSocket,我们可以在浏览器和服务器之间建立稳定的连接,实时传递数据实现即时聊天实时更新多人在线游戏等功能。

2.2 Websocket的特点

(1)双向通信: WebSocket允许客户端和服务器之间进行双向通信,无需等待对方的请求或响应。

(2) 持久连接: WebSocket连接一旦建立,会持续保持连接状态,避免了重复的连接和断开过程,减少了网络开销。

(3)低延迟: 由于连接一直保持打开状态,数据的传输可以更快地实现,从而实现低延迟的实时通信。

(4) 较少的数据传输量: 与传统的HTTP请求相比,WebSocket传输的数据头部信息较少,减少了数据传输量,提高了效率。

(5) 协议支持: WebSocket是一种独立于应用层协议的协议,可以在多种编程语言和平台上使用。

2.3 WebsocketHttp区别

(1)通信方式区别:

  • Http是请求-响应式的协议: 客户端发送请求,服务器返回响应,然后连接断开。这种方式适合传输静态内容或需要客户端不断向服务器发起请求的情况。
  • Websocket是全双工的通信协议WebSocket允许在客户端和服务器之间建立持久性的连接,双方可以随时相互发送数据。这种方式适合实时性要求较高、交互复杂的应用,如实时聊天、在线游戏等。

(2)连接状态区别:

  • Http是短连接:web端请求服务器端建立连接,服务器端返回后连接断开。
  • Websocket是长连接:连接一次,就会一直保持连接。

Http相当于写信,需要一来一回。
Websocket相当于打电话,只要不挂断,咱们可以一直通话。

三、实现方式

我采用 客户端:Client和 服务器端:Service的方式来展示WebSocket的实现方式。客户端使用JS实现服务器端使用Java + SpringBoot来实现。

3.1 客户端

javascript 复制代码
<!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 服务器端

引入依赖:

xml 复制代码
<!--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启动类:

java 复制代码
@SpringBootApplication
@EnableWebSocket
public class ApplicationRun {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationRun.class,args);
    }
}

这里注意一定要添加上@EnableWebSocket,表示开启Websocket

application.ymal配置文件:

yaml 复制代码
server:
  port: 5050

Websocket配置文件:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

Websocket服务端:

java 复制代码
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服务端(省略了onErroronClose方法它们不变):

java 复制代码
@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);
    }
}

使用调接口的方式,触发服务器推送消息动作:

java 复制代码
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);
    }

}

调用推送消息接口:

java 复制代码
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个汉堡再说吧~!

相关推荐
我也要当昏君1 小时前
6.3 文件传输协议 (答案见原书 P277)
网络
Greedy Alg1 小时前
Socket编程学习记录
网络·websocket·学习
刘逸潇20052 小时前
FastAPI(二)——请求与响应
网络·python·fastapi
软件技术员2 小时前
使用ACME自动签发SSL 证书
服务器·网络协议·ssl
我也要当昏君2 小时前
6.4 电子邮件 (答案见原书 P284)
网络协议
Mongnewer2 小时前
通过虚拟串口和网络UDP进行数据收发的Delphi7, Lazarus, VB6和VisualFreeBasic实践
网络
我也要当昏君3 小时前
6.5 万维网(答案见原书P294)
网络
嶔某3 小时前
网络:传输层协议UDP和TCP
网络·tcp/ip·udp
文火冰糖的硅基工坊4 小时前
[嵌入式系统-154]:各种工业现场总线比较
网络·自动驾驶·硬件架构
以己之4 小时前
详解TCP(详细版)
java·网络·tcp/ip