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.rhkb.cn/news/252943.html

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

相关文章

JVM内存分析与优化

JVM内存模型分析 在minor gc过程中对象挪动后&#xff0c;引用如何修改&#xff1f; 对象在堆内部挪动的过程其实是复制&#xff0c;原有区域对象还在&#xff0c;一般不直接清理&#xff0c;JVM内部清理过程只是将对象分配指针移动到区域的头位置即可&#xff0c;比如扫描s0区…

Springboot 整合 Elasticsearch(三):使用RestHighLevelClient操作ES ①

&#x1f4c1; 前情提要&#xff1a; Springboot 整合 Elasticsearch&#xff08;一&#xff09;&#xff1a;Linux下安装 Elasticsearch 8.x Springboot 整合 Elasticsearch&#xff08;二&#xff09;&#xff1a;使用HTTP请求来操作ES 目录 一、Springboot 整合 Elasticsea…

机器学习系列——(十六)回归模型的评估

引言 在机器学习领域&#xff0c;回归模型是一种预测连续数值输出的重要工具。无论是预测房价、股票价格还是天气温度&#xff0c;回归模型都扮演着不可或缺的角色。然而&#xff0c;构建模型只是第一步&#xff0c;评估模型的性能是确保模型准确性和泛化能力的关键环节。本文…

双向链表的插入、删除、按位置增删改查、栈和队列区别、什么是内存泄漏

2024年2月4日 1.请编程实现双向链表的头插&#xff0c;头删、尾插、尾删 头文件&#xff1a; #ifndef __HEAD_H__ #define __HEAD_H__ #include<stdio.h> #include<stdlib.h> #include<string.h> typedef int datatype; enum{FALSE-1,SUCCSE}; typedef str…

Python进阶--爬取下载人生格言(基于格言网的Python3爬虫)

目录 一、此处需要安装第三方库: 二、抓包分析及Python代码 1、打开人生格言网&#xff08;人生格言-人生格言大全_格言网&#xff09;进行抓包分析 2、请求模块的代码 3、抓包分析人生格言界面 4、获取各种类型的人生格言链接 5、获取下一页的链接 6、获取人生格言的…

【并发编程】手写线程池阻塞队列

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程 ⛺️稳重求进&#xff0c;晒太阳 示意图 步骤1&#xff1a;自定义任务队列 变量定义 用Deque双端队列来承接任务用ReentrantLock 来做锁并声明两个条件变量 Condition fullWai…

【wu-lazy-cloud-network】Java自动化内网穿透

项目介绍 wu-lazy-cloud-network 是一款基于&#xff08;wu-framework-parent&#xff09;孵化出的项目&#xff0c;内部使用Lazy ORM操作数据库&#xff0c;主要功能是网络穿透&#xff0c;对于没有公网IP的服务进行公网IP映射 使用环境JDK17 Spring Boot 3.0.2 功能 1.内网…

办公软件巨头CCED、WPS面临新考验,新款办公软件异军突起

办公软件巨头CCED、WPS的成长经历 众所周知&#xff0c;CCED和WPS在中国办公软件领域树立了两大知名品牌的地位。然而&#xff0c;它们的成功并非一朝一夕的成就&#xff0c;而是历经了长时间的发展与积淀。 在上世纪80年代末至90年代初&#xff0c;CCED作为中国大陆早期的一款…

Unity 接口、抽象类、具体类对象的配合使用案例

文章目录 示例1&#xff1a;接口&#xff08;Interface&#xff09;示例2&#xff1a;抽象类&#xff08;Abstract Class&#xff09;示例3&#xff1a;结合使用接口与抽象类示例4&#xff1a;多接口实现示例5&#xff1a;抽象类与接口结合 在Unity中使用C#编程时&#xff0c;接…

华为OD机试真题C卷-篇3

文章目录 查找一个有向网络的头节点和尾节点幼儿园篮球游戏 查找一个有向网络的头节点和尾节点 在一个有向图中&#xff0c;有向边用两个整数表示&#xff0c;第一个整数表示起始节点&#xff0c;第二个整数表示终止节点&#xff1b;图中只有一个头节点&#xff0c;一个或者多…

一、SSM 整合理解

本章概要 什么是 SSM 整合&#xff1f;SSM 整合核心问题明确 SSM 整合需要几个 IoC 容器&#xff1f;每个 IoC 容器对应哪些类型组件&#xff1f;IoC 容器之间关系和调用方向&#xff1f;具体多少配置类以及对应容器关系&#xff1f;IoC 初始化方式和配置位置&#xff1f; 1…

用甘特图有效管理多个项目进度

当公司或组织同时承担多个项目时,合理规划各项目的时间节点与资源分配对确保高效完成至关重要。采用甘特图可以直观地展示多个项目的时间进程、关键里程碑以及资源分配情况,便于从宏观层面全面把控各项目的动态。 在线甘特图软件 zz-plan.com 提供了非常强大的时间轴规划功能,支…

Xampp中Xdebug的安装使用

工欲善其事&#xff0c;必先利其器 XDebug简介 XDebug 是一个用于 PHP 的调试和性能分析工具。它提供了一系列功能&#xff0c;帮助开发者在开发和调试 PHP 应用程序时更加高效。 以下是 XDebug 的一些主要特性和功能&#xff1a; 调试功能&#xff1a; 断点调试&#xff1a;…

基础面试题整理7之Redis

1.redis持久化RDB、AOF RDB(Redis database) 在当前redis目录下生成一个dump.rdb文件&#xff0c;对redis数据进行备份 常用save、bgsave命令进行数据备份&#xff1a; save命令会阻塞其他redis命令&#xff0c;不会消耗额外的内存&#xff0c;与IO线程同步&#xff1b;bgsav…

MySql索引分类

目录 第一章、按数据结构分类1.1&#xff09;树型数据结构索引1.2&#xff09;Hash数据结构索引1.3&#xff09; 其他数据结构索引 第二章、按物理存储方式分类2.1&#xff09;聚簇索引&#xff08;聚集索引&#xff09;2.2&#xff09;非聚簇索引&#xff08;非聚集索引&#…

Blender教程(基础)-顶点的移动、滑移-16

一、顶点的移动与缩放 ShiftA新建柱体、切换到编辑模式 点模式下&#xff0c;选择一个顶点、选择移动&#xff08;GZ&#xff09;&#xff0c;发现顶点严Z轴移动&#xff0c;如下图所示 GY 按数字键盘7切换视图&#xff0c;选择这个面的所有顶点 按S把面缩放大 Ctrl…

【大模型上下文长度扩展】FlashAttention-2:比1代加速1.29倍、GPU利用率从55%上升到72%

FlashAttention-2 提出背景FlashAttention-2 改进 前向传播和反向传播对比FlashAttention前向传播FlashAttention反向传播FlashAttention-2前向传播FlashAttention-2反向传播FlashAttention-2并行性线程束之间的工作分区 总结FlashAttentionFlashAttention-2 论文&#xff1a;h…

Typora导出html文件图片自动转换成base64

Typora导出html文件图片自动转换成base64 一、出现问题二、解决方案三、编码实现3.1.创建Java项目3.2.代码3.3.打包成Jar包 四、如何使用endl 一、出现问题 typora 导出 html 的时候必须带有原图片&#xff0c;不方便交流学习&#xff0c;文件太多显得冗余&#xff0c;只有将图…

Golang GC 介绍

文章目录 0.前言1.发展史2.并发三色标记清除和混合写屏障2.1 三色标记2.2 并发标记问题2.3 屏障机制Dijkstra 插入写屏障Yuasa 删除写屏障混合写屏障 3.GC 过程4.GC 触发时机5.哪里记录了对象的三色状态&#xff1f;6.如何观察 GC&#xff1f;方式1&#xff1a;GODEBUGgctrace1…

鸿蒙OS导入项目报错不能运行 @ohos\hvigor\bin\hvigor.js‘

在自学HarmonyOS时&#xff0c;想在DevEco Studio导入官方示例代码&#xff1a;待办列表&#xff08;ArkTS&#xff09;报错 C:\Users\woods\Downloads\test01\ToDoListArkTS\node_modules\ohos\hvigor\bin\hvigor.js --mode module -p moduleentrydefault -p productdefault …