WebSocket基础详解

news2024/11/27 2:20:35

文章目录

  • 前言
    • 由来
    • 简介
    • 优缺点
    • 适用场景
    • 兼容性
  • 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 something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

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'}))

            // 发送二进制ArrayBuffer
            const buffer = new ArrayBuffer(128)
            ws.send(buffer)
            
            // // 发送二进制Blob
            const blob = new Blob([buffer])
            ws.send(blob)
            
            // 发送二进制ArrayBufferView
            const 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 = onopenWS
  Socket.onmessage = onmessageWS
  Socket.onerror = onerrorWS
  Socket.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.coloradmin.cn/o/1437424.html

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

相关文章

1896_Linux中free命令小结

1896_Linux中free命令小结 全部学习汇总&#xff1a; little_bits_of_linux: 一星半点的Linux经验 (gitee.com) 查看Linux中存储的使用情况&#xff0c;我经常使用htop&#xff0c;毕竟这个命令提供的信息是十分直观的。我现在常用的一个小主机其实是我的树莓派3B&#xff0c;虽…

记录 | linux下切换python版本

查看系统中存在的 python 版本 ls /usr/bin/python* 查看系统默认的 python 版本 python --version

入侵报警系统行业研究:智能化潮流助力市场维持正增长

侵报警系统intruder alarm system(IAS)利用传感器技术和电子信息技术探测并指示非法进入或试图非法进入设防区域(包括主观判断面临被劫持或遭抢劫或其他危急情况时&#xff0c;故意触发紧急报警装置)的行为&#xff0c;处理报警信息、发出报警信息的电子系统或网络。 当入侵行为…

如何重新安装 macOS

你可以使用电脑的内建恢复系统“macOS 恢复”来重新安装 Mac 操作系统。不但简单快捷&#xff0c;而且重新安装后不会移除你的个人数据。 将 Mac 关机 选取苹果菜单  >“关机”&#xff0c;然后等待 Mac 关机。如果你无法将 Mac 关机&#xff0c;请按住它的电源按钮最长 …

辛芷蕾在《繁花》中美艳照人,实力打脸于正。

♥ 为方便您进行讨论和分享&#xff0c;同时也为能带给您不一样的参与感。请您在阅读本文之前&#xff0c;点击一下“关注”&#xff0c;非常感谢您的支持&#xff01; 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 在王家卫导演的电视剧《繁花》中&#xff0c;辛芷蕾饰演的李李…

JavaScript入门学习

JavaScript 一.什么是JavaScript与作用 JavaScript&#xff08;简称“JS”&#xff09; 是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言 嵌入HTML中&#xff0c;与Css一样。对浏览器事件作出响应操作HTML元素及节点。可以动态操作CSS样式。在数据被提交…

新年快乐,我们一起GOGOGO!

1. 为什么要安装Go&#xff1f; 工欲善其事必先利其器&#xff0c;通俗来讲&#xff0c;要想保留雪糕&#xff0c;那就必须把雪糕放在"冷"的环境。这其实是一个道理&#xff0c;相关其他编程语言&#xff0c;例如Java&#xff0c;它就需要安装Java的开发环境JDK。 …

【element-ui】el-select下拉框el-date-picker弹出框定位问题解决方案

问题描述&#xff1a; 项目开发过程中发现el-select和el-date-picker弹出框显示时候&#xff0c;滚动屏幕&#xff0c;导致弹出框定位出现问题。 首先考虑到看一下element-ui官网提供的api&#xff0c;如下图 1、select提供了popper-append-to-body属性的配置 代码如下&#x…

【网站项目】038汽车养护管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Linux下centos操作系统安装Mysql8.0过程及踩坑填补

我自己有一台服务器&#xff0c;之前安装的是MySQL5.5&#xff0c;现在我想升级为MySQL8.0&#xff0c;于是我干了以下操作,既有踩坑又有干货&#xff1a; 1.先卸载MySQL&#xff1b; 2.删除跟MySQL相关文件&#xff1b; 3.安装新的MySQL8.0版本&#xff08;这里踩了一个坑&…

从零开始手写mmo游戏从框架到爆炸(十)— 集成springboot-jpa与用户表

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 集成springboot-jpa&#xff0c;不用mybatis框架一个是方便对接不同的数据源。第二个目前规划的游戏内容可能对数据库的依赖不是很大&#xff0c;jpa应该肯定能满足要求了…

JVM 性能调优- 五种内存溢出(5)

在介绍之前先简单介绍下 直接内存(Direct Memory)和堆内存(Heap Memory): 关系: 直接内存并不是Java虚拟机的一部分,它是通过Java的NIO库中的ByteBuffer来分配和管理的。直接内存通常由操作系统的本地内存(Native Memory)提供支持。堆内存是Java虚拟机的一部分,用于存…

re:从0开始的CSS学习之路 3. CSS三大特性

0. 写在前面 很多的学习其实并不知道在学什么&#xff0c;学一个新东西学着学着就变成了抄代码&#xff0c;背概念。把看视频学习变成了一个赶进度的任务&#xff0c;到头来只学到了一些皮毛。 文章目录 0. 写在前面1. CSS三大特性——层叠性2. CSS三大特性——优先级3. CSS三…

C语言在Visual Studio 2010环境下使用<regex.h>正则表达式函数库

在Visual Studio 2010环境下&#xff0c;如果C语言想要使用<regex.h>头文件进行正则表达式匹配&#xff0c;则需要pcre3.dll这个动态链接库&#xff0c;可以去网上下载。 下载的网址是&#xff1a;Pcre for Windowspcre {whatisit}https://gnuwin32.sourceforge.net/pac…

12-树-有序链表转换二叉搜索树

这是树的第12篇算法&#xff0c;力扣链接。 给定一个单链表的头节点 head &#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为高度平衡的二叉搜索树。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。 示例 1: 输入: h…

解析与模拟常用字符串函数strcpy,strcat,strcmp,strstr(一)

今天也是去学习了一波字符串函数&#xff0c;想着也为了加深记忆&#xff0c;所以写一下这篇博客。既帮助了我也帮助了想学习字符串函数的各位。下面就开始今天的字符串函数的学习吧。 目录 strcpy与strncpy strcat与strncat strcmpy strstr strcpy与strncpy 在 C 语言中&…

宏景eHR fieldsettree SQL注入漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合,满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR fieldsettree 接口处存在SQL注入漏洞,未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令,从而窃取数据库敏感…

ChatGPT学习第一周

&#x1f4d6; 学习目标 掌握ChatGPT基础知识 理解ChatGPT的基本功能和工作原理。认识到ChatGPT在日常生活和业务中的潜在应用。 了解AI和机器学习的基本概念 获取人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;的初步了解。理解这些技术是如何支撑…

电动汽车雷达技术概述 —— FMCW干扰问题

一、电动汽车上有多少种传感器&#xff1f; 智能电动汽车&#xff08;包括自动驾驶汽车&#xff09;集成了大量的传感器来实现高级驾驶辅助系统&#xff08;ADAS&#xff09;、自动驾驶功能以及车辆状态监测等功能。以下是一份相对全面的智能电动汽车中可能使用到的传感器列表…

虚拟飞控计算机:飞行控制系统验证与优化的利器

01.背景介绍 随着航空技术的飞速发展&#xff0c;飞行控制系统作为飞机的心脏&#xff0c;全面负责监测、调整和维持飞行器的姿态、航向、高度等参数&#xff0c;用以确保飞行的安全和稳定。为了满足这些要求&#xff0c;现代飞控系统通常采用先进的处理器和外设来确保其高效、…