WebSocket基础详解

文章目录

  • 前言
    • 由来
    • 简介
    • 优缺点
    • 适用场景
    • 兼容性
  • API介绍
    • 构造函数
    • 实例方法
      • send()
      • close()
    • 实例属性
      • ws.readyState(只读)
      • ws.bufferedAmount(只读)
      • ws.binaryType
      • extensions(只读)
      • protocol(只读)
      • url(只读)
    • 实例事件
      • onopen
      • onclose
      • onmessage
      • onerror
  • 代码实例
    • 客户端
    • 服务端
  • 封装客户端库
    • WebSocket.js
    • 使用
  • 易混淆常识
    • WebSocket 与 HTTP 有什么关系?
    • WebSocket 与长轮询有什么区别?
    • 什么是 WebSocket 心跳?
    • Socket 是什么?

前言

由来

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

举例来说,我们想随时了解今天的天气变化,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。

WebSocket 出现之前,我们想实现实时通信、变更推送、服务端消息推送功能,我们一般的方案是使用Ajax轮询。轮询是指由浏览器每隔一段时间向服务器发出 HTTP 请求,然后服务器返回最新的数据给客户端。

常见的轮询方式分为短轮询与长轮询,它们的区别如下图所示:

为了更加直观感受短轮询与长轮询之间的区别,我们来看一下具体的代码:

Polling(短轮询)

function checkUpdates(url) {const xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.onload = function() { ... }xhr.send()
}setInterval(function(){checkUpdates('/poll')
}, 60000)

Long-Polling(长轮询)

function checkUpdates(url) {const xhr = new XMLHttpRequest()xhr.open('GET', url)xhr.onload = function() { checkUpdates('/poll')}xhr.send()
}
checkUpdates('/poll')

上面两种方案都有比较明显的缺点:

  • HTTP 协议包含较长的请求头,有效数据只占很少一部分,频繁轮询浪费带宽。
  • 短轮询频繁轮询对服务器压力较大,因为必须不停连接。即使使用长轮询方案,即HTTP 连接始终打开,客户端较多时仍会对服务端造成不小压力。

因此,工程师们一直在思考,有没有更好的方法。在这种情况下,HTML5 定义了 WebSocket 协议。它能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

简介

WebSocket 协议在2008年诞生,2011年成为国际标准,RFC6455 定义了它的通信标准,后由 RFC7936 补充规范。

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

在这里插入图片描述

WebSocket 建立在 TCP 协议之上,服务器端的实现比较容易,与 HTTP 和 HTTPS 使用相同的 TCP端口,因此与 HTTP(S) 协议有着良好的兼容性,可以绕过大多数防火墙的限制。

在这里插入图片描述

默认情况下,WebSocket 协议使用 80端口,协议标识符是ws;运行在 TLS 之上时,默认使用 443端口,则协议标识符为wss。并且握手阶段采用 HTTP(S) 协议,因此握手时不容易屏蔽,能通过各种 HTTP(S) 代理服务器。

Web 浏览器和服务器都必须实现 WebSocket 协议来建立和维护连接。由于 WebSocket 连接长期存在,与典型的 HTTP 连接不同,对服务器有重要的影响。

基于多线程或多进程的服务器无法适用于 WebSocket,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSocket 服务器端实现都需要一个异步服务器。

优缺点

  • 优点

    • 更强的实时性: 由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少。
    • 较小的数据传输开销: WebSocket 的数据帧相比于 HTTP 请求用于协议控制的数据包头部相对较小,减少了在每个请求中传输的开销,特别适用于需要频繁通信的应用。
    • 跨域通信: 与一些其他跨域通信方法相比,WebSocket 更容易实现跨域通信。
    • 较低的服务器资源占用: 由于 WebSocket 的长连接特性,服务器可以处理更多的并发连接,相较于短连接有更低的资源占用。
    • 保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
    • 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
    • 可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
  • 缺点

    • 连接状态保持: 长时间保持连接可能会导致服务器和客户端都需要维护连接状态,可能增加一些负担。
    • 不适用于所有场景: 对于一些请求-响应模式较为简单的场景,WebSocket 的实时特性可能并不是必要的,使用 HTTP 请求可能更为合适。
    • 复杂性: 与传统的 HTTP 请求相比,WebSocket 的实现和管理可能稍显复杂,尤其是在处理连接状态、异常等方面。

适用场景

  • 推送服务: 用于实现消息推送服务,向客户端主动推送更新或通知。
  • 实时数据展示: 对于需要实时展示数据变化的应用,例如股票行情、实时监控系统等,WebSocket 提供了一种高效的通信方式。
  • 实时聊天应用: WebSocket 是实现实时聊天室、即时通讯应用的理想选择,因为它能够提供低延迟和高实时性。
  • 在线协作和协同编辑: 对于需要多用户协同工作的应用,如协同编辑文档或绘图,WebSocket 的实时性使得用户能够看到其他用户的操作。
  • 在线游戏: 在线游戏通常需要快速、实时的通信,WebSocket 能够提供低延迟和高并发的通信能力。

兼容性

在介绍 WebSocket API 之前,我们先来了解一下它的兼容性:

由上图可知:目前主流的 Web 浏览器都支持 WebSocket,所以我们可以在大多数项目中放心地使用它。

API介绍

构造函数

WebSocket 对象作为一个构造函数,用于新建实例。

语法:const ws = new WebSocket(url [, protocols]);

相关参数说明如下:

  • url:表示连接的 URL,这是 WebSocket 服务器将响应的 URL;
  • protocols(可选):一个协议字符串或者一个包含协议字符串的数组。

针对protocols:这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议。

比如:你可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互。如果不指定协议字符串,则假定为空字符串。

使用WebSocket 构造函数时,当尝试连接的端口被阻止时,会抛出 SECURITY_ERR 异常。

const ws = new WebSocket('ws://localhost:8080');

执行上面语句之后,客户端就会与服务器进行连接。

实例方法

send()

实例对象的send()方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的数据的大小来增加 bufferedAmount 的值 。若数据无法传输(比如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

语法:ws.send(data)

参数data,用于传输至服务器的数据。它必须是以下类型之一:

  • String

    文本字符串。字符串将以 UTF-8 格式添加到缓冲区,并且 bufferedAmount 将加上该字符串以 UTF-8 格式编码时的字节数的值。

  • ArrayBuffer

    你可以使用字节数组对象发送底层二进制数据;其二进制数据内存将被缓存于缓冲区,bufferedAmount 将加上所需字节数的值。

  • Blob

    Blob 类型将队列 blob 中的原始数据以二进制传输。 bufferedAmount 将加上原始数据的字节数的值。

  • ArrayBufferView

    你可以以二进制帧的形式发送任何 JavaScript 类数组对象;其二进制数据内容将被队列于缓冲区中。值 bufferedAmount 将加上必要字节数的值。

发送文本的例子。

ws.send('your message');

发送 Blob 对象的例子。

const file = document.querySelector('input[type="file"]').files[0];
ws.send(file);

发送 ArrayBuffer 对象的例子。

// Sending canvas ImageData as ArrayBuffer
const img = canvas_context.getImageData(0, 0, 400, 320);
const binary = new Uint8Array(img.data.length);
for (const i = 0; i < img.data.length; i++) {binary[i] = img.data[i];
}
ws.send(binary.buffer);

close()

语法:ws.close([code[, reason]])

该方法用于关闭 WebSocket 连接,如果连接已经关闭,则此方法不执行任何操作。

ws.close();

实例属性

ws.readyState(只读)

ws.readyState属性返回实例对象的当前状态。共有四种数值,0|1|2|3。

  • WebSocket.CONNECTING:值为0,表示正在连接。
  • WebSocket.OPEN:值为1,表示连接成功,可以通信了。
  • WebSocket.CLOSING:值为2,表示连接正在关闭。
  • WebSocket.CLOSED:值为3,表示连接已经关闭,或者打开连接失败。

下面是一个示例。

switch (ws.readyState) {case WebSocket.CONNECTING:// do somethingbreak;case WebSocket.OPEN:// do somethingbreak;case WebSocket.CLOSING:// do somethingbreak;case WebSocket.CLOSED:// do somethingbreak;default:// this never happensbreak;
}

ws.bufferedAmount(只读)

ws.bufferedAmount 是一个只读属性,用于返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。一旦队列中的所有数据被发送至网络,则该属性值将被重置为 0,这可以用来判断发送是否结束。但是,若在发送过程中连接被关闭,则属性值不会重置为 0。如果你不断地调用send(),则该属性值会持续增长。

const data = new ArrayBuffer(10000000);
ws.send(data);if (ws.bufferedAmount === 0) {console.log('发送完毕')
} else {console.log('发送还没结束')
}

ws.binaryType

ws.binaryType 返回 WebSocket 连接所传输二进制数据的类型。

语法:const binaryType = ws.binaryType

返回值如下:

  • blob:如果传输的是 Blob 类型的数据。

  • arraybuffer:如果传输的是 ArrayBuffer 类型的数据。

extensions(只读)

ws.extensions 是只读属性,返回服务器已选择的扩展值。目前,链接可以协定的扩展值只有空字符串或者一个扩展列表。

protocol(只读)

ws.protocol 是个只读属性,用于返回服务器端选中的子协议的名字;这是一个在创建 WebSocket 对象时,在参数 protocols 中指定的字符串,当没有已建立的链接时为空串。

url(只读)

ws.url 是一个只读属性,返回值为当构造函数创建 WebSocket 实例对象时 URL 的绝对路径。

实例事件

使用 addEventListener() 或将一个事件监听器赋值给 WebSocket 对象的 oneventname 属性,来监听下面的事件。

onopen

实例对象的onopen属性,用于指定连接成功后的回调函数。

ws.onopen = function () {ws.send('Hello Server!');
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('open', function (event) {ws.send('Hello Server!');
});

onclose

实例对象的onclose属性,用于指定连接关闭后的回调函数。

ws.onclose = function(event) {const code = event.code;const reason = event.reason;const wasClean = event.wasClean;// handle close event
};ws.addEventListener("close", function(event) {const code = event.code;const reason = event.reason;const wasClean = event.wasClean;// handle close event
});

onmessage

实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。

ws.onmessage = function(event) {const data = event.data;// 处理数据
};ws.addEventListener("message", function(event) {const data = event.data;// 处理数据
});

注意,服务器数据可能是文本,也可能是二进制数据(Blob对象或Arraybuffer对象)。

ws.onmessage = function(event){if(typeof event.data === 'string') {console.log("Received data string");}if(event.data instanceof Blob){const buffer = event.data;console.log("Received blob");}if(event.data instanceof ArrayBuffer){const arrayBuffer = event.data;console.log("Received arraybuffer");}
}

如果收到的是二进制数据类型,可以设置binaryType属性值,显式指定返回数据的类型。

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {console.log(e.data.size);
};// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {console.log(e.data.byteLength);
};

onerror

实例对象的onerror属性,当websocket的连接由于一些错误事件的发生 (例如无法发送一些数据) 而被关闭时,一个error事件将被引发。

socket.onerror = function(event) {// handle error event
};socket.addEventListener("error", function(event) {// handle error event
});

代码实例

WebSocket 服务器常用的 Node 实现有以下几种

  • ws
  • Socket.IO
  • WebSocket-Node
  • µWebSocket

具体的用法请查看它们的文档,这里不详细介绍了。

客户端

支持H5的浏览器均内置 WebSocket 对象,直接引用即可,具体代码如下:

<!DOCTYPE html>
<html><head><title>WebSocket Test</title></head><body><script>const ws = new WebSocket("ws://127.0.0.1:3000");   //建立连接ws.binaryType = 'arraybuffer'ws.onopen = function(){  //发送请求console.log("open");// 发送UTF-8编码的文本信息ws.send("This is string");// 发送UTF-8编码的JSON数据ws.send(JSON.stringify({msg: 'This is json'}))// 发送二进制ArrayBufferconst buffer = new ArrayBuffer(128)ws.send(buffer)// // 发送二进制Blobconst blob = new Blob([buffer])ws.send(blob)// 发送二进制ArrayBufferViewconst intView = new Uint32Array(buffer)ws.send(intView)};ws.onmessage = function(ev){  //获取后端响应console.log('message', ev.data);};ws.onclose = function(ev){console.log("close");};ws.onerror = function(ev){console.log("error");};</script></body>
</html>

服务端

为与客户端一致,后端引入ws模块,构建服务器,监听对应事件,具体代码如下:

const ws = require("ws"); // 加载ws模块;// 启动`WebSocket`服务器
const wsServer = new ws.Server({host: "127.0.0.1",port: 3000,
})
console.log('WebSocket sever is listening at port localhost:3000');let closeTimer = null // 设置定时器// 监听客户端请求,绑定对应事件;
function wsListener(wsObj) {wsObj.on("message", function(reqData) {// 当10s没有消息进来则对此次连接进行断开clearTimeout(closeTimer)closeTimer = setTimeout(() => {wsObj.close()}, 10 * 1000);console.log("request message: ", reqData);//收到请求,回复setTimeout(() => { wsObj.send("1秒延时,收到了,正在处理");}, 1000);// 处理业务逻辑setTimeout(() => {wsObj.send("3秒延时,返回数据");wsObj.send(reqData)}, 3000);});wsObj.on("close", function() {console.log("request close");});wsObj.on("error", function(err) {console.log("request error", err);});
}// 建立连接
function onServerConnection (wsObj) {console.log("request comming");wsListener(wsObj);
}wsServer.on("connection", onServerConnection);

运行该js构建服务器

封装客户端库

对客户端WebSocket方法进行简易封装,后端依然使用上面代码示例中的node websocket服务。

WebSocket.js

let Socket = null // websocket实例
let setIntervalWesocketPush = null // 心跳计时器let onopenWS; let onmessageWS; let onerrorWS; let oncloseWS;/*** 建立WebSocket连接* @param {string} url ws地址*/
export const createSocket = (url) => {if (Socket) {Socket.close()Socket = null}console.log('新建WebSocket连接')Socket = new WebSocket(url)Socket.onopen = onopenWSSocket.onmessage = onmessageWSSocket.onerror = onerrorWSSocket.onclose = oncloseWS
}/** 连接错误 */
onerrorWS = () => {// 错误导致连接关闭则尝试重连if (Socket.readyState !== 3) {console.log('连接失败重连中')createSocket(Socket.url)}
}/** WS数据接收统一处理 */
onmessageWS = (e) => {window.dispatchEvent(new CustomEvent('onmessageWS', {detail: {data: e.data,},}))
}/*** 发送数据但连接未建立时进行处理等待重发* @param {any} message 需要发送的数据*/
const connecting = (message) => {setTimeout(() => {if (Socket.readyState === 0) {connecting(message)} else {Socket.send(JSON.stringify(message))}}, 1000)
}/*** 发送数据* @param {any} message 需要发送的数据*/
export const sendWSPush = (message) => {if (Socket?.readyState === 0) {connecting(message)} else if (Socket?.readyState === 1) {Socket.send(JSON.stringify(message))} else if (Socket?.readyState === 3) {createSocket(Socket.url)}
}/** 断开重连 */
oncloseWS = () => {clearInterval(setIntervalWesocketPush)console.log('WebSocket已断开')// 非正常关闭导致的断开,则尝试重连if (Socket.readyState !== 2) {console.log('正在尝试重连....')createSocket(Socket.url)}
}/** 发送心跳* @param {number} time 心跳间隔毫秒 默认5000* @param {string} ping 心跳名称 默认字符串ping*/
export const sendPing = (time = 5000, ping = 'ping') => {clearInterval(setIntervalWesocketPush)Socket.send(ping)setIntervalWesocketPush = setInterval(() => {Socket.send(ping)}, time)
}/** 打开WS之后发送心跳 */
onopenWS = () => {sendPing()
}

使用

// 在main.js或需要使用的地方引入
import { createSocket, sendWSPush } from '@/utils/websocket'// 创建接收消息函数
const getsocketData = (e) => {console.log(e.detail.data)
}mounted() {// 建立连接createSocket('ws://127.0.0.1:3001')// 发送消息sendWSPush('This is test string')// 注册监听事件window.addEventListener('onmessageWS', getsocketData)
},
beforeDestroy() {// 在需要的时候卸载监听事件,比如离开页面window.removeEventListener('onmessageWS', getsocketData)
}

易混淆常识

WebSocket 与 HTTP 有什么关系?

WebSocket 是一种与 HTTP 不同的协议。两者都位于 OSI 模型的应用层,并且都依赖于传输层的 TCP 协议。

虽然它们不同,但是 RFC 6455 中规定:WebSocket 被设计为在 HTTP 80 和 443 端口上工作,并支持 HTTP 代理和中介,从而使其与 HTTP 协议兼容。为了实现兼容性,WebSocket 握手使用 HTTP Upgrade 头,从 HTTP 协议更改为 WebSocket 协议。

既然已经提到了 OSI(Open System Interconnection Model)模型,这里分享一张很生动形象描述 OSI 模型的示意图(如下图所示)。

WebSocket 与长轮询有什么区别?

长轮询就是:客户端发起一个请求,服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断请求的数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则等待一定的时间后才返回。

长轮询的本质还是基于 HTTP 协议,它仍然是一个一问一答(请求 — 响应)的模式。而 WebSocket 在握手成功后,就是全双工的 TCP 通道,数据可以主动从服务端发送到客户端。

什么是 WebSocket 心跳?

网络中的接收和发送数据都是使用 Socket 进行实现。但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题。

可是如何判断这个套接字是否还可以使用呢?这个就需要在系统中创建心跳机制。

所谓 “心跳” 就是定时发送一个自定义的结构体(心跳包或心跳帧),让对方知道自己 “在线”,以确保链接的有效性。

而所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息,如果服务端几分钟内没有收到客户端信息则视客户端断开。

WebSocket 协议中定义了 心跳 Ping 和 心跳 Pong 的控制帧:

  1. 心跳 Ping 帧包含的操作码是 0x9:如果收到了一个心跳 Ping 帧,那么终端必须发送一个心跳 Pong 帧作为回应,除非已经收到了一个关闭帧。否则终端应该尽快回复 Pong 帧;
  2. 心跳 Pong 帧包含的操作码是 0xA:作为回应发送的 Pong 帧必须完整携带 Ping 帧中传递过来的 “应用数据” 字段。

针对第2)点:如果终端收到一个 Ping 帧但是没有发送 Pong 帧来回应之前的 Ping 帧,那么终端可以选择仅为最近处理的 Ping 帧发送 Pong 帧。此外,可以自动发送一个 Pong 帧,这用作单向心跳。

Socket 是什么?

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个 Socket(套接字),因此建立网络通信连接至少要一对端口号。

Socket 本质:是对 TCP/IP 协议栈的封装,它提供了一个针对 TCP 或者 UDP 编程的接口,并不是另一种协议。通过 Socket,你可以使用 TCP/IP 协议。

百度百科上关于Socket的描述是这样:

Socket 的英文原义是“孔”或“插座”:作为 BSD UNIX 的进程通信机制,取后一种意思。通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。

在Internet 上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket 正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供 220 伏交流电, 有的提供 110 伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

关于 Socket,可以总结以下几点:

  1. 它可以实现底层通信,几乎所有的应用层都是通过 socket 进行通信的;
  2. 对 TCP/IP 协议进行封装,便于应用层协议调用,属于二者之间的中间抽象层;
  3. TCP/IP 协议族中,传输层存在两种通用协议: TCP、UDP,两种协议不同,因为不同参数的 socket 实现过程也不一样。

下图说明了面向连接的协议的套接字 API 的客户端/服务器关系:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/672336.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Power Designer的使用 创建数据库表模型,生成sql语句,生成C#实体类

几年前用过PowerDesigner&#xff0c;好几年没用&#xff0c;有点忘记了&#xff0c;在这里记个笔记&#xff0c;需要的时候翻一翻 PowerDesigner版本16.5 下面的例子是以MySQL数据库为准 生成C#实体类 一 安装 1.1 安装 不让放网盘链接&#xff0c;审核通不过。。。。 …

JCTC | 利用几何深度学习对蛋白质-配体结合pose进行等变灵活建模

Overview 该论文解决了药物开发中蛋白质-配体复合结构灵活建模的挑战。作者提出了一种名为FlexPose的新型深度学习框架&#xff0c;它可以直接对复杂结构进行建模&#xff0c;而不需要传统的采样和评分策略。 该模型结合了标量-向量双特征表示和 SE(3)等变网络设计来处理动态结…

用Python做一个文件夹整理工具

文章目录 目标文件夹对话框文件映射组件完整组件 此前曾经基于函数实现过这个功能&#xff1a;用Python做一个文件夹整理工具。这次基于面向对象的方法&#xff0c;重新实现这个小工具。 本文中提到的FileSystem&#xff0c;来自于这里&#xff1a;Python根据文件后缀整理文件…

2024年G3锅炉水处理证考试题库及G3锅炉水处理试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年G3锅炉水处理证考试题库及G3锅炉水处理试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大纲随机…

Linux安装Java

yum安装 下面命令直接复制粘贴一件安装java17 yum list installed | grep java #查看已经安装的javayum remove java* -y #移除现在系统已经安装的javayum list | grep java-17 #查看安装java17yum install -y java-17-openjdk #安装java17此处可…

Win32 SDK Gui编程系列之--弹出式菜单

1.弹出式菜单 例如,在命令提示窗口中点击鼠标右键,会出现如下图所示的弹出菜单(下拉菜单)。 这种弹出式菜单的实现很简单。不创建菜单栏,用CreatePopupMenu函数创建的菜单是最顶端的菜单就可以了。 菜单的显示使用TrackPopupMenu函数进行。 例如,点击鼠标右键显示弹出…

C#中使用OpenCvSharp4绘制直线、矩形、圆、文本

C#中使用OpenCvSharp4绘制直线、矩形、圆、文本 继之前的Python中使用Opencv-python库绘制直线、矩形、圆、文本和VC中使用OpenCV绘制直线、矩形、圆和文字&#xff0c;将之前的Python和C示例代码翻译成C#语言&#xff0c;很简单&#xff0c;还是借用OpenCvSharp4库中的Line、…

每日一题——LeetCode1422.分割字符串的最大得分

方法一 暴力枚举 枚举所有分割点的情况&#xff0c;取最大得分 var maxScore function(s) {let res 0;const n s.length;for (let i 1; i < n; i) {let score 0;for (let j 0; j < i; j) {if (s[j] 0) {score;}}for (let j i; j < n; j) {if (s[j] 1) {sco…

小白代码审计入门

最近小白一直在学习代码审计,对于我这个没有代码审计的菜鸟来说确实是一件无比艰难的事情。但是着恰恰应了一句老话:万事开头难。但是小白我会坚持下去。何况现在已经喜欢上了代码审计,下面呢小白就说一下appcms后台模板Getshell以及读取任意文件,影响的版本是2.0.101版本。…

SpringBoot集成Swagger2的增强版Knife4j

1. 背景 作为SpringBoot集成中间件其中的一篇文章吧&#xff0c;既然打算出这么一个系列了&#xff0c;争取做到虽小却全&#xff0c;又精又美的一个系列吧。 Swagger应该都有接触吧&#xff0c;knife4j是Swagger2的增强版&#xff0c;更加友好的操作页面&#xff0c;更多强大…

【Java程序设计】【C00187】基于SSM的旅游资源网站管理系统(论文+PPT)

基于SSM的旅游资源网站管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的旅游资源网站 本系统分为前台系统、用户和管理员3个功能模块。 前台系统&#xff1a;当游客打开系统的网址后&#xff0c;首先看到的就是…

介绍 MSTest Runner – CLI、Visual Studio 等

作者&#xff1a;Amaury Lev Marco Rossignoli Jakub Jareš 排版&#xff1a;Alan Wang 我们很高兴推出 MSTest 运行器&#xff0c;这是一款全新的轻量级 MSTest 测试运行器。这个新的运行器使测试更加便携和可靠&#xff0c;运行速度更快&#xff0c;并且具有可扩展性&#x…

React+Antd+tree实现树多选功能(选中项受控+支持模糊检索)

1、先上效果 树型控件&#xff0c;选中项形成一棵新的树&#xff0c;若父选中&#xff0c;子自动选中&#xff0c;子取消&#xff0c;父不取消&#xff0c;子选中&#xff0c;所有的父节点自动取消。同时支持模糊检索&#xff0c;会检索出所有包含该内容的关联节点。 2、环境准…

centos 7.6 安装cas 对接ldap 单点登录实战

centos 7.6 安装cas 对ldap 单点登录实战 1、安装前准备工作1.1、centos 7.6 安装JDK 1.81.2、centos 7 安装tomcat 9.0.841.3、windows10 安装JDK 1.81.4、windows10 安装打包工具 maven 3.9.6 2、下载cas 5.3 并打包成war包3、部署cas到tomcat4、cas对接ldap 1、安装前准备工…

回归预测 | Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现POA-BP鹈鹕算法优化BP神经网络多变量回归预测&#xff08;完整源码…

【UE】游戏运行流程的简单理解

流程图 官方的游戏流程图&#xff1a; 一般顺序为初始化引擎、创建并初始化 GameInstance、加载关卡&#xff0c;最后开始游戏。 总的来说就是&#xff1a; 开始游戏-》游戏实例-》关卡-》游戏模式-》玩家控制器-》Pawn、玩家状态、HUD、UMG&#xff08;可有可无&#xff09; …

RabiitMQ延迟队列(死信交换机)

Dead Letter Exchange&#xff08;死信交换机&#xff09; 在MQ中&#xff0c;当消息成为死信&#xff08;Dead message 死掉的信息&#xff09;后&#xff0c;消息中间件可以将其从当前队列发送到另一个队列中&#xff0c;这个队列就是死信队列。而 在RabbitMQ中&#xff0c;由…

二、SSM 整合配置实战

本章概要 依赖整合和添加控制层配置编写(SpringMVC 整合)业务配置编写(AOP/TX 整合)持久层配置编写(MyBatis 整合)容器初始化配置类整合测试 2.1 依赖整合和添加 数据库准备 数据库脚本 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT…

【C#】MVVM架构

示例结果展示 前提了解 MVVM是Model-View-ViewModel的缩写形式,它通常被用于WPF或Silverlight开发。 Model——可以理解为带有字段,属性的类。例如学校类,教师类,学生类等 View——可以理解为我们所看到的UI。前端界面。 View Model在View和Model之间,起到连接的作用,…

2024-02-06(Sqoop)

1.Sqoop Apache Sqoop是Hadoop生态体系和RDBMS&#xff08;关系型数据库&#xff09;体系之间传递数据的一种工具。 Sqoop工作机制是将导入或者导出命令翻译成MapReduce程序来实现。在翻译出的MapReduce中主要是对inputformat和outputformat进行定制。 Hadoop生态包括&#…