浏览器判断某一个 ip 是否与其在同一个局域网的几种方式

前言
前段时间有个需求, 就是我们有做一个 web 的投屏端, 可以将另一个客户端(比如 android,ios,win,mac) 投屏到 web 站点来。 但是期间因为涉及到引流, 所以针对投屏的客户端是否在同一个局域网下要做不同的判断,如果在同一个局域网下,那么就可以免费使用,如果不是的话,就会有其他的引导。

所以我们得到客户端的 ip 地址之后,需要判断当前浏览器是否跟这一台客户端在同一个局域网下。 有几种判断方式

以下的测试数据,都是基于 chrome 98 的浏览器, 早期的浏览器可能会有不同的表现

  1. 让客户端开启一个本地端口,然后浏览器去请求这个端口
    最简单的方式就是让这个客户端开启一个本地端口,就是类似于 ip+port 的方式,可以是 http ,也可以是 websocket 的方式。 然后让浏览器去请求这个端口。

如果可以请求成功,那么就说明是在同一个局域网。 不过这边有几个问题:

1.1. 在 https 下会有问题
如果当前的站点是 https 的话,那么在 https 下请求 http 协议的 ip 形式的接口的话, 浏览器是会直接 block 掉的 (请求不会到服务端这边)

1

如果是 ws 协议的 ip 地址的 websocket 的话,也是一样会被 block 掉 (请求不会到服务端这边)

1

那如果是 https 或者是 wss 下的 ip 形式的接口呢 (客户端是没有合格的证书的,就算加上 tls ,也是用自制证书的 ip 地址的方式),是不是就可以? 答案肯定是不行的, 直接就报证书错误了 (除非事先这个接口已经在浏览器那边事先手动允许访问了)

1

所以总结一下:

在 https 的站点下, 非 tls 的 http 和 ws 的 ip 地址的接口请求,会直接 block 掉
在 https 的站点下, tls 的 https 和 wss 的 ip 地址的接口请求,会报证书错误,也是 block
1.2. https 请求 localhost 的例外情况
那有没有例外呢,在我的测试中,还真有一个例外, 就是如果是本机的客户端的服务,以 localhost 的 host 方式,不管是 http://localhost:3000 还是 ws://localhost:3000, 都可以在 https 的站点下 work, 不过两者有差别:

如果是 localhost 的 http 请求的话,浏览器也会 block (测试的时候,之所以 block 是因为 CORS 限制),不过他只会 block 返回值,但是请求是有出去的,也就是说返回值是 200,只不过 js 没法取的返回值,但是服务端是有收到这个请求的
1

如果是 localhost 的 ws 请求的话, 哪怕是在 https 的站点,也是可以 work 的。就跟在 http 站点中表现一样
关于这一点其实我也有点疑惑,正常来说, localhost 的形式跟 ip 地址的形式应该差别不大才对, 但是实验的结果却是 ip 地址的不行,localhost 就可以, 差别可能就是 ip 地址的不仅仅是本机,同一个局域网下的机器都有,但是 localhost 却是限于本机上开启的服务, 可能相对来说也会比较安全吧。

事实上我们还真有一个服务是在 https 站点下调用 localhost 服务接口的,就是我们 win 端应用在进行第三方 google 登录授权的时候,就是跳转到我们的 https 下的官网站点的 web 页面进行授权的, 然后授权完成之后,就会调用该应用创建的 localhost 的服务接口,将信息传递到 win 端的应用, 从而完成整个第三方授权过程。

1.3. 在 http 下也会有问题
在 https 下有问题, 其实在 http 下也会有问题的, chrome 可能在 2022-05-17 之后, http 协议的远程站点,就不能再请求本地的专用网络了。包括 ip 形式的, localhost 形式的。

之前我还针对过这个情况,进行过一次兼容: chrome 94 之后 http 远程站点请求 ip 地址报 CORS 错误

综上所述, 通过请求 ip+port 的方式,请求客户端暴露的接口来判断跟客户端是处于同一个局域网的策略,是行不通的

  1. 根据 ip 网段来判断是否是同一个局域网
    还是一种方式,就是既然可以得到 客户端的 ip (可以通过很多途径,比如去服务端取,或者直接两者连接同一个 websocket 通道,然后进行信息转发之类的), 那能不能直接通过这个 ip 是不是局域网 ip 网段来直接确定是不是在同一个局域网。

我们知道从 0.0.0.0 到 255.255.255.255 这些网段中,其中有一些网段是给局域网的,包括:

a类网 10.0.0.0~10.255.255.255
b类网 172.16.0.0~172.31.255.255
c类网 192.168.0.0~192.168.255.255
那能不能直接根据客户端传过来的 ip 地址来判断是不是在同一个局域网内,比如这样子判断:

checkIpInLAN: function (ip) {
return ip && (ip.indexOf(“10.”) === 0 || ip.indexOf(“172.”) === 0 || ip.indexOf(“192.168”) === 0);
},
其实也是不准确的,因为这边混淆了一个概念,以上确实是属于局域网的网段, 但是该网段不一定跟当前的浏览器在同一个局域网。

因为同样是局域网的网段,但是相互之间可能就是不通的,比如我们公司提供给客人的wifi,和我们内部人员自己使用的 wifi,就是都属于 192.168.x.x 的局域网网段, 但是两者其实是不通, 也就是两者不在同一个局域网下。

所以这种方式也是没办法判断浏览器和客户端是在同一个局域网下的

  1. 通过 webrtc 的 stun/turn 得到局域网 ip,然后扔给客户端,让客户端去 ping
    还有一种方式就是通过 webrtc 的 stun/turn server 去获取当前浏览器的局域网 ip,然后将这个 ip 扔给客户端,让客户端去 ping,如果通,说明就在同一个局域网。

但是我后面试了一下,发现这种方式会有问题, 他返回的 host 信息是有问题的,变成这样子:

{“candidate”:“candidate:1316551156 1 udp 2113937151 493c5821-465b-41d5-9cfe-f0b703d3add6.local 57516 typ host generation 0 ufrag gjxD network-cost 999”,“sdpMid”:“0”,“sdpMLineIndex”:0}
局域网 ip 就变成这样子 493c5821-465b-41d5-9cfe-f0b703d3add6.local

后面查了一下,原来 chrome 在 75 版本之后,使用了一种 mDNS 的技术,将 local ip 匿名了: Google Chrome - Anonymize local IPs exposed by WebRTC prevents live view in cloud console

导致我们没办法看到这个局域网 ip。 这边附上操作代码:

const findIP = (onNewIP) => {
const myPeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection
const pc = new myPeerConnection({
iceServers: [
{urls: ‘stun:stun.l.google.com:19302’},
{urls: ‘stun:stun.services.mozilla.com:3478’},
{urls: ‘stun:stun.qq.com:3478’}
] })
const noop = () => {}
const localIPs = {}
const ipRegex = /([0-9]{1,3}(.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g

const ipIterate = (ip) => {
if (!localIPs[ip]) onNewIP(ip)
localIPs[ip] = true
}
pc.createDataChannel(‘’) // create a bogus data channel
pc.createOffer(sdp => {
sdp.sdp.split(‘\n’).forEach(line => {
if (line.indexOf(‘candidate’) < 0) return
line.match(ipRegex).forEach(ipIterate)
})
pc.setLocalDescription(sdp, noop, noop)
}, noop) // create offer and set local description
pc.onicecandidate = (ice) => { // listen for candidate events
if (
!ice ||
!ice.candidate ||
!ice.candidate.candidate ||
!ice.candidate.candidate.match(ipRegex)
) return
ice.candidate.candidate.match(ipRegex).forEach(ipIterate)
}
}

const addIP = (ip) => {
document.body.append(----get ip: ${ip})
}

findIP(addIP)
这种方式虽然没办法得到局域网 ip, 但是却可以得到外网 ip, 以下就是我利用上述的程序得到的 外网 ip 125.xx.xx.250 :

{“candidate”:“candidate:842163049 1 udp 1677729535 125.xx.xx.250 55956 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag 12LL network-cost 999”,“sdpMid”:“0”,“sdpMLineIndex”:0}
这个在 chrome 是有配置项的: chrome://flags/#enable-webrtc-hide-local-ips-with-mdns, 默认是开启的。 如果将其设置为 disable 后, 那么是不会启用 mDNS 技术来隐藏真实的 local ip, 那么直接就是局域网 ip 了

1

{“candidate”:“candidate:1316551156 1 udp 2122260223 192.168.40.51 56234 typ host generation 0 ufrag n4Ma network-id 1”,“sdpMid”:“0”,“sdpMLineIndex”:0}
有一种例外情况可以忽略 mDNS
查阅了相关资料,发现有一种例外情况也可以显示原始的局域网 ip:

对应用程序的影响

当该功能处于活动状态时,ICE 候选主机中的私有 IP 地址将被替换为 mDNS 主机名,例如1f4712db-ea17-4bcf-a596-105139dfd8bf.local 。
目前,除具有 getUserMedia 权限的站点外,该功能对所有站点都有效,假定这些站点具有较高的用户信任度。

WebRTC 公开的私有 IP 地址更改为 mDNS 主机名groups.google.com/g/discuss-webrtc/c/6stQXi72BEU
那就是在有启用 getUserMedia 权限的站点 (要是 https 的站点), 所以将上述的代码最后一行改为:

navigator.mediaDevices.getUserMedia({ video: false, audio: true }).then( stream => {
findIP(addIP)
}).catch(err => {
findIP(addIP)
})
这时候就会弹出授权框

1

点击允许之后,就会出现局域网 ip 了 (而且下一次不需要再授权了,只要有权限,那么就会有不匿名的局域网 ip)

1

  1. 跟客户端建立起 webrtc 连接,如果是在一个局域网的话,会有 localCandidate 信息
    目前最稳妥的方式,就是跟客户端建立起 webrtc 连接, 这时候如果真的是在同一个局域网下的话,就可以得到 localCandidate,同时他的值是 host 的话, 那么就可以认为是在同一个局域网下了。

具体操作就是在 ice 连接成功的情况下, 这时候我们可以获取 webrtc 的统计数据(这个是一个异步), 通过 RTCPeerConnection 对象的 getStats 方法:

const report = await this.#pc.getStats()
// 遍历report,按照 type 来分类
/** @type {Record<string, any[]>} */
const data = {};
report.forEach((item) => {
if (data[item.type]) {
data[item.type].unshift(item)
} else {
data[item.type] = [item]
}
});
return data
然后这时候得到统计数据之后, 就可以去找到当前连接的通道 transport, 然后在 candidate-pair 找到这个通道, 然后接下来在 local-candidate 找到这个 id, 最后一步就是判断是不是 host 了

具体的代码如下:

/**

  • 通过 webrtc 的 stat 数据来判断当前连接的通道是否是局域网
  • @param stat
  • @returns {boolean}
    */
    checkIsLocalCandidateType(stat){
    const originRaw = stat._raw
    const throwErr = (msg) => {
    throw { msg: [checkIsLocalCandidateType]: ${msg} }
    }
    const findLocalCandidate = (targetCandidate) => {
    if(targetCandidate.length){
    let localCandidateId = targetCandidate[0][“localCandidateId”]
    // 然后接下来在 local-candidate 找到这个 id
    if(originRaw[“local-candidate”]){
    let targetLocalCandidate = originRaw[“local-candidate”].filter(item => item.id === localCandidateId)
    if(targetLocalCandidate.length){
    let candidateType = targetLocalCandidate[0][“candidateType”]
    // 最后一步就是判断是不是 host 了
    logger.debug([WebRtcProcess] => checkIsLocalCandidateIdFromWebRtcStats: ${candidateType})
    return candidateType === “host”
    }else{
    throwErr(“local-candidateType not found”)
    }
    }else{
    throwErr(“local-candidate not found”)
    }
    }else{
    throwErr(“targetCandidate not found”)
    }
    }
    // 找到当前连接的通道, 这个是 chrome 的流程
    if(originRaw.transport?.length){
    const selectedCandidatePairId = originRaw.transport[0].selectedCandidatePairId
    // 然后在 candidate-pair 找到这个通道
    if(originRaw[“candidate-pair”]?.length){
    let targetCandidate = originRaw[“candidate-pair”].filter(item => item.id === selectedCandidatePairId)
    return findLocalCandidate(targetCandidate)
    }else{
    throwErr(“candidate-pair not found”)
    }
    }else{
    // firefox 是没有 transport 参数的,要额外处理
    if(originRaw[“candidate-pair”]?.length){
    let targetCandidate = originRaw[“candidate-pair”].filter(item => item[“selected”] === true && item[“state”] === “succeeded”)
    return findLocalCandidate(targetCandidate)
    }
    // 有些旧的浏览器比如 safari 11.1.2,就会出现找不到 local-candidate 参数的情况
    }
    return false
    }
    这个是在 ice 连接之后, 第一次获取 getStats 方法的时候,就可以判断, 因为 ice 连接之后,说明当前的 webrtc 肯定是有连接的 transport 通道的, 只要当前的这一条通道的 local-candidate 的值是 host, 那么就肯定是在同一个局域网下。

通过这种方式绝大部分情况下是可以判断浏览器和客户端是在同一个局域网下,但是有一种情况下会误判, 就是当前是局域网,但是网络连通不好,导致不是 host 先连上,而是穿透(srflx)或者转发(relay)先连上 transport, 这时候第一次的 getStats 就不会是 host 了,因为他当前的连接通道确实不是 host。

同时需要注意一点的是,正常情况下这个 host 的 RTCIceCandidate 也是不会显示 局域网 ip 的, 而且他不是用 mDNS 加密,而是直接为空:

{
“id”: “RTCIceCandidate_1wdP0rnA”,
“timestamp”: 1651027428646,
“type”: “local-candidate”,
“transportId”: “RTCTransport_audio_1”,
“isRemote”: false,
“networkType”: “unknown”,
“ip”: “”,
“address”: “”,
“port”: 63261,
“protocol”: “udp”,
“candidateType”: “host”,
“priority”: 2113937151
}
也是只有在启用 getUserMedia 权限,才会显示局域网 ip 出来。

  1. 一样建立 webrtc 连接,但是双方只接受 host ice, 如果还能连上,那么肯定是在同一个局域网
    上述的情况是连接上 webrtc 之后,才去判断是不是走 host 的 transport 通道,再去判断当前是否是局域网连接。

但是如果我们在传输交换 ice 的时候,只传和接收 局域网 ice 的话, 那么能连接上 webrtc 的情况下,肯定是局域网。 因为在只有 host 模式下 ice 可以连接上,说明两端肯定是在同一个局域网下

所以就要改成双方在发送 ice 的时候,只发送 host 模式下的 ice, 类似于这样子

{“sdpMLineIndex”:0,“sdpMid”:“0”,“candidate”:“candidate:559267639 1 udp 2122202367 ::1 45061 typ host generation 0 ufrag yvH5 network-id 2”}
{“sdpMLineIndex”:0,“sdpMid”:“0”,“candidate”:“candidate:2065939615 1 udp 2122260223 192.168.197.13 47089 typ host generation 0 ufrag yvH5 network-id 3 network-cost 10”}
然后在 this.#pc.addIceCandidate(new RTCIceCandidate(item), () => { 添加的时候,就会只有 host 的 ice 了。

这种情况下,对于一端浏览器端,一端其他端(windows,android, ios),这个其他端因为有更详细的底层 sdk 的 api, 所以可以做到只发送 host 的 ice。

但是对于两端都是浏览器端的情况,因为预置的 RTCIceTransportPolicy 只有两个值, relay 和 all, 并没有类似于 only-local 的值,没办法控制只发送 host ice 的情况

enum RTCIceTransportPolicy {
“relay”,
“all”
};

mozillaw3c.github.io/webrtc-pc/#rtcicetransportpolicy-enum
但是我们可以在接收添加 ice 的时候,过滤掉非 host 的 ice,比如这样子:

/**

  • 添加ICE
  • pc.addIceCandidate
    */
    addIce = (data) => {
    return new Promise((resolve) => {
    const RTCIceCandidate = window.RTCIceCandidate || (window as any).mozRTCIceCandidate || (window as any).webkitRTCIceCandidate
    data.forEach(item => {
    // 只接收 host 的 ice
    if(JSON.stringify(item).includes(“typ host”)){
    this.#pc.addIceCandidate(new RTCIceCandidate(item), () => {
    logger.debug([PeerConnection]: add ice success: ${JSON.stringify(item)})
    this.#emitter.emit(‘addIceStatus’, ‘success’)
    }, (err) => {
    logger.error([PeerConnection]: add ice fail: ${err})
    this.#emitter.emit(‘addIceStatus’, ‘fail’)
    })
    }
    })
    resolve(data)
    });
    }
    这样子,如果 ice 可以连接上,那么肯定两端是在同一个局域网中。

虽然通过这种方式可以 100% 判断是否在同一个局域网中, 但是会伴随两个小问题:

因为只接收 host ice,所以连接失败的情况下,没办法判断是 webrtc 整体连接不上, 还是因为只有 host ice 连接不上,这个不好定位(因为有可能不卡 host ice 的话,是可以连接上的)
判断时间会比较长,因为要等到 host ice 连接不上,至少也要给予 5s 的尝试连接时间,或者更久才能准确得到信息, 太短的时候,会不准确,因为有些虽然在同一个局域网,但是网络不太行,需要更长的时间来连接
总结
本文介绍了几种浏览器判断跟客户端是否在同一个局域网的方式, 前三种都有一定的问题, 第四种一定情况下,在网络比较差的情况下,会有误判。 只有第五种是可以 100% 判断是在同一个局域网下的情况。

https://kebingzao.com/2022/03/29/browser-detect-lan/

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

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

相关文章

三年没写了, 访问量竟然几十万次。

分享知道是我的爱好。 能帮助任何一个人都是值得的。

谈谈我对HashMap扩容机制的理解及底层实现

目录 一、HashMap的底层实现 二、HashMap扩容机制 概念 详细扩容&#xff1a; 1、初始容量 2、添加元素 3、元素数量检查 4、触发扩容 5、迁移元素 6、更新容量和阈值 代码&#xff1a; 一、HashMap的底层实现 HashMap 是 Java 中常用的数据结构之一&#xff0c;用于…

Python学习路线 - Python语言基础入门 - 判断语句

Python学习路线 - Python语言基础入门 - 判断语句 前言布尔类型和比较运算符布尔类型布尔类型的定义 比较运算符 if语句的基本格式if判断语句 if else 语句if elif else 语句判断语句的嵌套实战案例 前言 进行逻辑判断&#xff0c;是生活中常见的行为。同样&#xff0c;在程序…

Powerbuilder9.0 安装是一直卡在setup is running无法继续

这种情况是安装时&#xff0c;他后面弹出来一个提示框&#xff0c;但是因为其他进程的干扰&#xff0c;我们无法看到也就无法继续了。 我看到这个文章&#xff1a;https://blog.csdn.net/FLORY_/article/details/105244102 使用他说的方法的确有效。过程 1. 打开任务管理器 …

es常用查询编辑

查询指定id信息 GET /index_name/_doc/1074266245查询指定信息并降序 GET /index_name/_search {"query": {"term": {"deviceId": {"value": "1074266245"}}}, "sort": [{"timestamp": {"order&qu…

Mysql的事务日志

Mysql的事务具有四个特性&#xff1a;原子性、一致性、隔离性、持久性。那么事务的四种特性分别是靠什么机制实现的呢&#xff1f; 事务的隔离性由锁机制来保证 事务的原子性、一致性、持久性则由redo log和Undo log来保证。 - redo log是重做日志&#xff0c;提供再写入操作&…

子查询在SQL中的应用和实践

作者&#xff1a;CSDN-川川菜鸟 在SQL中&#xff0c;子查询是一种强大的工具&#xff0c;用于解决复杂的数据查询问题。本文将深入探讨子查询的概念、类型、规则&#xff0c;并通过具体案例展示其在实际应用中的用途。 文章目录 子查询概念子查询的类型子查询的规则实际案例分析…

Photoshop Elements 2023 v21.0(ps简化版)

Photoshop Elements 2023是一款ps简化版图像处理软件&#xff0c;它加入了一些新的功能和工具&#xff0c;以帮助用户更高效地处理图片。 新功能&#xff1a;软件加入了黑科技&#xff0c;采用Adobe Sensei AI技术&#xff0c;主打人工智能&#xff0c;一键P图&#xff0c;新增…

【C语言实现windows环境下Socket编程TCP/IP协议】

C语言实现windows环境下Socket编程TCP/IP协议 主要是记录解决一些在我本地编译运行时出现的问题connect &#xff1a;No error关于头文件关于stray /xxx和socket&#xff1a;No error问题千万记得是服务器先启动哦&#xff0c;客户端后启动下面附上我改好的代码 主要是记录解决…

Demystifying DeFi MEV Activities in Flashbots Bundle

目录 笔记后续的研究方向摘要引言贡献 Demystifying DeFi MEV Activities in Flashbots Bundle CCS 2023 笔记 本文介绍了对 Flashbots 捆绑包中的去中心化金融 &#xff08;DeFi&#xff09; 矿工可提取价值 &#xff08;MEV&#xff09; 活动的研究。作者开发了ActLifter&am…

文献速递:多模态影像组学文献分享(一种诊断方法结合了多模态放射组学和基于腰椎CT及X光的机器学习模型,用于骨质疏松症)

文献速递&#xff1a;多模态影像组学文献分享:(一种诊断方法结合了多模态放射组学和基于腰椎CT及X光的机器学习模型&#xff0c;用于骨质疏松症)** Title 题目 A diagnostic approach integrated multimodal radiomics with machine learning models based on lumbar spine CT…

Codeforces Round 913 (Div. 3) A~E

目录 A. Rook 问题分析: B. YetnotherrokenKeoard 问题分析: C. Removal of Unattractive Pairs 问题分析: D. Jumping Through Segments 问题分析: E. Good Triples 问题分析: A. Rook 问题分析: 给一个棋子将其同行同列的位置输出 #include<bits/s…

❀My学习Linux命令小记录(14)❀

目录 ❀My学习Linux命令小记录&#xff08;14&#xff09;❀ 56.man指令 57.whatis指令 58.info指令 59.--help指令 60.uname指令 ❀My学习Linux命令小记录&#xff08;14&#xff09;❀ 56.man指令 功能说明&#xff1a;查看Linux中的指令帮助。 &#xff08;ps.man命…

上门预约洗鞋店小程序

互联网洗鞋店小程序开发&#xff0c;结合洗鞋行业线下实际运营情况和经验&#xff0c;专为洗鞋人、洗鞋店打造的高效、实用、有价值的洗鞋私域流量管理软件系统。 帮助洗鞋人建立自己的私域流量&#xff0c;实现会员用户管理&#xff0c;用户与商家点对点互联互通&#xff0c;提…

工业级路由器在智能交通系统(ITS)中的创新应用

智能交通系统&#xff08;ITS&#xff09;作为一种先进的交通管理与控制系统&#xff0c;旨在提高交通运输系统的效率、安全性和便捷性。随着科技的不断发展&#xff0c;智能交通系统已经成为城市交通管理的重要组成部分。而工业级路由器作为一种可靠的网络通信设备&#xff0c…

微信小程序开发步骤及简单开发案例

开发步骤: 注册开发者账号:前往微信公众平台注册一个小程序开发者账号。创建小程序:登录小程序开发者工具,创建一个新的小程序项目,并填写小程序基本信息。配置开发环境:在小程序开发者工具中配置开发环境,包括开发者工具的设置、调试工具的设置等。设计小程序界面:根据…

SCAU:18051 勾股数

18051 勾股数 时间限制:1000MS 代码长度限制:10KB 提交次数:0 通过次数:0 题型: 编程题 语言: G;GCC;VC Description 若三个正整数a、b、c&#xff0c;其中a<b<c&#xff0c;满足a^2b^2c^2&#xff0c;其中^表示上标&#xff0c;称这三个数为“勾股数”&#xff0c;例…

python执行命令的方式

常见方法 Python中常用的执行操作系统命令有os.system()、os.popen()、commands、subprocess.popen()、subprocess.call()、subprocess.run()、subprocess.getstatusoutput()等方法。 os.system方法 这个方法是直接调用标准C的system() 函数&#xff0c;仅仅在一个子终端运行…

使用Python内置库实现数据的加密与校验

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 数据的安全性是现代应用程序中不可忽视的一个重要方面。在Python中&#xff0c;我们可以利用内置的加密和校验库来保护敏感信息。本文将深入讨论如何使用Python内置库实现数据的加密和校验&#xff0c;为开发者提…

favicon

所谓favicon&#xff0c;即Favorites Icon的缩写&#xff0c;顾名思义&#xff0c;便是其可以让浏览器的收藏夹中除显示相应的标题外&#xff0c;还以图标的方式区别不同的网站。当然&#xff0c;这不是Favicon的全部&#xff0c;根据浏览器的不同&#xff0c;Favicon显示也有所…