当前位置: 首页 > news >正文

Server Name Indication(SNI),HTTP/TLS握手过程解析

Server Name Indication(SNI)是一种TLS扩展,用于在TLS握手过程中传递服务器的域名信息。在未使用SNI之前,客户端在建立TLS连接时只能发送单个IP地址,并且服务器无法知道客户端请求的具体域名。这导致服务器需要使用默认证书进行握手,无法正确选择合适的证书。

使用SNI扩展后,客户端在发送ClientHello消息时会包含所请求的服务器的域名。服务器根据该域名来选择对应的证书进行握手,从而实现了多个域名共享同一个IP地址并使用不同证书的能力。

SNI对于虚拟主机或者CDN等场景特别有用,因为这些场景下,多个网站可能共享同一个IP地址。通过使用SNI,服务器能够正确地选择与域名匹配的证书,提高了安全性和灵活性。

SNI产生解决什么问题

Server Name Indication(SNI)的产生背景与互联网发展和IPv4地址短缺有关。

在互联网发展初期,每个网站通常拥有自己独立的IP地址。但随着网站数量的迅速增长和IPv4地址资源的有限,IP地址短缺问题变得日益严重。

为了解决IPv4地址短缺问题,一种解决方案是引入虚拟主机技术。虚拟主机是指在同一台物理服务器上托管多个不同域名的网站。通过共享同一个IP地址,多个网站能够减少IP地址的使用,并降低运维成本。

然而,这种虚拟主机技术在使用TLS(Transport Layer Security)协议时遇到了问题。TLS协议是用于加密和保护网络通信的协议,它在握手过程中需要使用正确的证书来验证服务器身份。但在使用虚拟主机时,服务器无法根据传统的握手方式正确选择合适的证书,因为无法获知客户端请求的具体域名。

为了解决这个问题,SNI扩展被引入TLS协议中。它允许客户端在握手时发送所请求的服务器域名信息,使服务器能够正确选择并使用对应的证书,实现了多个域名共享同一个IP地址并使用不同证书的能力。SNI的引入为虚拟主机和CDN等场景提供了更好的支持,进一步促进了IPv4地址资源的有效利用。

抓包分析http握手过程

触发http请求

curl www.baidu.com

tcpdump抓包:

tcpdump -i any port 80 -nnnvvvv -w http.pcap


HTTP (Hypertext Transfer Protocol) 的握手过程如下:

  • 1.建立TCP连接:客户端通过向服务器发送一个SYN包来发起与服务器的TCP连接。

  • 2.服务器回应:服务器接收到客户端的SYN包后,返回一个SYN-ACK包,表示同意建立连接。

  • 3.客户端确认:客户端接收到服务器的SYN-ACK包后,发送一个ACK包给服务器,表示连接已建立成功。

  • 4.发送HTTP请求:客户端与服务器建立了TCP连接后,客户端可以发送HTTP请求。HTTP请求包括HTTP方法(GET, POST等)、URI(Uniform Resource Identifier)和HTTP协议版本等信息。

  • 5.服务器响应:服务器接收到HTTP请求后,根据请求的内容进行处理,并返回HTTP响应。HTTP响应包括响应状态码(例如200表示成功,404表示未找到等)、响应头和响应体等信息。

  • 6.关闭连接:客户端接收到服务器的HTTP响应后,根据需要继续发送其他请求,或者关闭TCP连接。

这是一个简化的HTTP握手过程,实际上还有更多的细节和可选项,例如在建立TCP连接时可能需要进行三次握手以确保连接的可靠性,还有可能使用持久连接(Keep-Alive)机制来减少握手的次数等。HTTP握手过程中的这些步骤保证了客户端和服务器之间的通信顺利进行,并确保数据在网络传输时的可靠性和完整性。
抓包分析TLS握手过程

触发https请求

curl -k --insecure "https://www.baidu.com"
tcpdump -i any port 443 -nnnvvvv -w https.pcap

WireShark抓包验证SNI:

TLS握手过程是一个复杂的过程,包括以下步骤:

  • 1.客户端发送ClientHello报文:客户端发送一个ClientHello报文,该报文包含客户端支持的协议版本、会话ID、密码组和压缩方法。客户端提交的ClientHello所包含的密码组是客户支持的密码算法列表(按优先级降序排列),压缩方法是客户支持的压缩方法列表。
  • 2.服务器返回ServerHello报文:服务器发送一个ServerHello报文,该报文包含服务器建议的协议版本、服务器选择的密码组和压缩方法,以及两个随机数:ClientHello.random和ServerHello.random。
  • 3.服务器返回CA证书和请求客户端证书:服务器发送自己的证书(Certificate),Certificate 报文包含一个X.509证书或者一条证书链。除了匿名DH之外的密钥交换方法都需要发送Certificate报文。如果服务器需要被认证的话,服务器会发送自己的证书。同时,服务器可能会发送一个ServerKeyExchange报文,例如当服务器没有自己的证书或者证书仅用于签名时。
  • 4.客户端提交证书并发送ClientKeyExchange报文:客户端将自己的证书上报给服务器,同时会发送一个ClientKeyExchange报文,该报文包含密钥协商后的秘钥给服务器。
  • 5.客户端验证服务器的证书:客户端会验证服务器的证书的有效性。如果服务器的证书是有效的,客户端会发送一个ChangeCipherSpec报文和一个EncryptedHandshake消息。
  • 6.服务器发送ChangeCipherSpec报文和EncryptedHandshake消息:服务器也会发送ChangeCipherSpec报文和EncryptedHandshake消息,表示握手协商已经完成。
  • 7.应用层面交互:完成握手协商后,客户端和服务器会开始进行应用层面的交互,通过之前协商的秘钥来加密通信内容。

以上就是TLS握手过程的详细步骤。

Linux C/C++解析 Server Name Indication(SNI)字段

这是一个类似tcpdump的程序,用于在实时或捕获的流量中打印TLS SNI和HTTP/1.1主机字段。

char *tls_ContentType(uint8_t n)
{switch (n) {/* 20*/case SSL3_RT_CHANGE_CIPHER_SPEC:return "change_cipher_spec";/* 21 */case SSL3_RT_ALERT:return "alert";/* 22 */case SSL3_RT_HANDSHAKE:return "handshake";/* 23 */case SSL3_RT_APPLICATION_DATA:return "application_data";default:return "UNKNOWN";}return "";
}char *tls_AlertLevel(uint8_t n)
{switch(n) {/* 1 */case SSL3_AL_WARNING:return "warning";/* 2 */case SSL3_AL_FATAL:return "fatal";default:return "UNKNOWN";}return "";
}char *tls_AlertDescription(uint8_t n)
{switch(n) {/* 0 */case SSL3_AD_CLOSE_NOTIFY:return "close_notify";/* 10 */case SSL3_AD_UNEXPECTED_MESSAGE:return "unexpected_message";/* 20 */case SSL3_AD_BAD_RECORD_MAC:return "bad_record_mac";case 21:return "decryption_failed_RESERVED";case 22:return "record_overflow";case 30:return "decompression_failure";case 40:return "handshake_failure";case 41:return "no_certificate_RESERVED";case 42:return "bad_certificate";case 43:return "unsupported_certificate";case 44:return "certificate_revoked";case 45:return "certificate_expired";case 46:return "certificate_unknown";case 47:return "illegal_parameter";case 48:return "unknown_ca";case 49:return "access_denied";case 50:return "decode_error";case 51:return "decrypt_error";default:return "UNKNOWN";}return "";
}char *tls_HandshakeType(uint8_t n)
{switch(n) {/* 0 */case SSL3_MT_HELLO_REQUEST:return "hello_request";/* 1 */case SSL3_MT_CLIENT_HELLO:return "client_hello";/* 2 */case SSL3_MT_SERVER_HELLO:return "server_hello";/* 11 */case SSL3_MT_CERTIFICATE:return "certificate";/* 12 */case SSL3_MT_SERVER_KEY_EXCHANGE:return "server_key_exchange";/* 13 */case SSL3_MT_CERTIFICATE_REQUEST:return "certificate_request";/* 14 */case SSL3_MT_SERVER_DONE:return "server_hello_done";/* 15 */case SSL3_MT_CERTIFICATE_VERIFY:return "certificate_verify";/* 16 */case SSL3_MT_CLIENT_KEY_EXCHANGE:return "client_key_exchange";/* 20 */case SSL3_MT_FINISHED:return "finished";default:return "UNKNOWN";}return "";
}
...
int tls_set_callback_handshake_clienthello_servername(int (*handler)(uint8_t *, uint16_t));
int tls_process_Handshake_ClientHello_Extensions_ServerName();
int tls_process_Handshake_ClientHello_Extensions();
int tls_process_Handshake_ClientHello();
uint32_t tls_process_record(uint8_t *payload, uint32_t payload_length);
int http_set_callback_request_host(int (*handler)(uint8_t *host_name, uint16_t host_name_length));
int http_init();
uint16_t http_process_request(uint8_t *payload, uint16_t payload_length);
void http_cleanup();
int sni_handler (uint8_t *host_name, uint16_t host_name_length);int main (int argc, char *argv[])
{
...while ((i = getopt(argc, argv, "hf:pi:r:w:")) != -1) {switch(i) {case 'h':fprintf(stderr,"Use: %s [-h] [-f bpf] [-p] -i interface [-w dump.pcap]\n", argv[0]);fprintf(stderr,"Use: %s [-h] [-f bpf] [-p] -r trce.pcap [-w dump.pcap]\n", argv[0]);return -1;break;case 'f':bpf_s = optarg;opt_flags |= OPT_BPF;break;case 'p':opt_flags |= OPT_PROMISCUOUS;break;case 'i':device_name = optarg;opt_flags |= OPT_DEVICE;break;case 'r':trace_fname = optarg;opt_flags |= OPT_TRACE;break;case 'w':dump_fname = optarg;opt_flags |= OPT_DUMP;break;default:break;}}...fprintf(stdout, "[*] PID: %u\n", getpid());if (opt_flags & OPT_DEVICE) {fprintf(stdout, "[*] Device: '%s'\n", device_name);fprintf(stdout, "[*] Promiscuous: %d\n", PROMISCUOUS);if (!(pcap_handle =pcap_open_live(device_name, SNAPLEN, PROMISCUOUS, PCAP_TIMEOUT,errbuf))) {fprintf(stderr, "[FATAL] %s\n", errbuf);return -1;}}if (opt_flags & OPT_TRACE) {fprintf(stdout, "[*] Trace: '%s'\n", trace_fname);if (!(pcap_handle =pcap_open_offline(trace_fname, errbuf))) {fprintf(stderr, "[FATAL] %s\n", errbuf);return -1;}}...if (!(opt_flags & OPT_BPF)) {bpf_s = bpf_default;opt_flags |= OPT_BPF;}fprintf(stdout, "[*] BPF: '%s'\n", bpf_s);if (pcap_compile(pcap_handle, &bpf, BPF, BPF_OPTIMIZE,PCAP_NETMASK_UNKNOWN) == -1) {fprintf(stderr, "[FATAL] Couldn't parse filter. %s\n",pcap_geterr(pcap_handle));pcap_close(pcap_handle);return -1;}if (pcap_setfilter(pcap_handle, &bpf) == -1) {fprintf(stderr, "[FATAL] Couldn't install filter. %s\n",pcap_geterr(pcap_handle));pcap_close(pcap_handle);return -1;}pcap_freecode(&bpf);pcap_dumper_handle = NULL;if (opt_flags & OPT_DUMP) {fprintf(stdout, "[*] Dump: '%s'\n", dump_fname);if (!(pcap_dumper_handle = pcap_dump_open(pcap_handle, dump_fname))) {fprintf(stderr, "[WARNING] Couldn't create dump file. %s\n",pcap_geterr(pcap_handle));}}
...tls_set_callback_handshake_clienthello_servername(&sni_handler);http_set_callback_request_host(&sni_handler);http_init();act.sa_handler = signal_handler;sigemptyset (&act.sa_mask);act.sa_flags = 0;if (sigaction(SIGINT, &act, NULL)) {perror("sigaction");fprintf(stderr,"[WARNING] Failed to set signal handler for SIGINT.\n");}if (sigaction(SIGTERM, &act, NULL)) {perror("sigaction");fprintf(stderr,"[WARNING] Failed to set signal handler for SIGTERM.\n");}if (sigaction(SIGSEGV, &act, NULL)) {perror("sigaction");fprintf(stderr,"[WARNING] Failed to set signal handler for SIGSEGV.\n");}fprintf(stderr, "Capturing ...\n");if (pcap_loop(pcap_handle, -1, &my_pcap_handler, NULL) == -1) {fprintf(stderr, "[FATAL] pcap_loop failed. %s\n",pcap_geterr(pcap_handle));}if (!(opt_flags & OPT_TRACE)) {if (pcap_stats(pcap_handle, &ps) == -1) {fprintf(stderr, "pcap_stats failed. %s\n", pcap_geterr(pcap_handle));} else {fprintf(stderr, "%u packets received\n", ps.ps_recv);fprintf(stderr, "%u packets dropped\n", ps.ps_drop + ps.ps_ifdrop);}}pcap_close(pcap_handle);http_cleanup();if (pcap_dumper_handle) {pcap_dump_close(pcap_dumper_handle);fprintf(stderr, "Written %s\n", dump_fname);if (!(opt_flags & OPT_DUMP)) {free(dump_fname);}}...
}

If you need the complete source code, please add the WeChat number (c17865354792)

curl触发SNI字段解析

curl -k --insecure "https://www.baidu.com"
curl www.baidu.com

该程序从TLS握手客户端Hello消息中提取服务器名称指示(SNI)字段(RFC 4366),并从HTTP/1.1请求中提取主机请求头字段(RFC 2616)。

它接受一个网络接口作为输入来监控其流量(可选地以混杂模式),或者接受一个PCAP文件作为输入来读取。默认情况下,它使用BPF以目标端口为80或443的TCP数据包为目标,但如果相应配置,它将处理其他端口上的TLS和HTTP数据包以及UDP数据包。捕获的流量可以保存到PCAP文件中。

总结

SNI,即服务器名称指示,是TLS协议的扩展。它允许在握手过程开始时通过客户端告诉服务器正在连接的主机名称,从而解决一个服务器拥有多个域名的情况。

在TLS握手信息中并没有携带客户端要访问的目标地址,导致当一台服务器有多个虚拟主机,且每个主机的域名不一样,使用了不一样的证书时,不知道和哪台虚拟主机进行通信。而SNI允许Web服务器通过SSL或TLS握手的扩展在单个IP地址上托管多个站点,从而使得HTTPS网站具有唯一的TLS证书,即使它们位于共享IP地址上。使用SNI时,服务器的主机名包含在TLS握手中,这使得HTTPS网站具有唯一的TLS证书,即使它们位于共享IP地址上也是如此。

Welcome to follow WeChat official account【程序猿编码

参考:RFC 4366、RFC 2616

相关文章:

Server Name Indication(SNI),HTTP/TLS握手过程解析

Server Name Indication(SNI)是一种TLS扩展,用于在TLS握手过程中传递服务器的域名信息。在未使用SNI之前,客户端在建立TLS连接时只能发送单个IP地址,并且服务器无法知道客户端请求的具体域名。这导致服务器需要使用默认…...

react项目实现文件预览,比如PDF、txt、word、Excel、ppt等常见文件(腾讯云cos)

使用腾讯云文档预览&#xff0c;需要开通文档预览功能&#xff0c;该功能需要收费的。 使用限制 如果需要图片预览、视频或音频可以使用获取下载链接。 页面代码 <button onClick() > {handleClick(myself/文档.xlsx)}>预览</button><div style{{ height:…...

ES SearchAPI----Query DSL语言

文章目录 Getting Startedmatch_all查询全部sort排序from\size分页_source指定字段 match匹配查询match_phrase短语匹配multi_match多字段匹配range范围查询bool复合查询must必须匹配&#xff0c;可贡献得分must_not必须不匹配&#xff0c;可贡献得分should可有可无&#xff0c…...

【STM32】HAL库——串口中断只接收到两个字符

【STM32】HAL库——串口中断只接收到两个字符 一、问题描述二、解决方法三、原因分析 一、问题描述 环境&#xff1a;STM32CubeMX(6.7.0)MDK-ARM(V5.36.0.0)STM32F103C8T6 使用XCOM发送字符串&#xff08;总共8个字符&#xff09;&#xff0c;单片机进行解析为ModBus协议失败…...

页面html结构导出为word或pdf

一、使用场景和原理 需要将当前页面(一般详情页面)或者dom容器中的内容保存/截图&#xff0c;并且导出为word或者pdf 导出word:获取dom结构直接转化为word导出 导出pdf:用canvas生成当前页面或者dom范围的快照&#xff0c;参考截图功能&#xff0c;然后将生成的canvas转为pdf内…...

Object.prototype.toString.call() 和 instanceOf 和 Array.isArray() 详解

解析: 理解 Object.prototype.toString.call(), instanceof, 和 Array.isArray() 是 JavaScript 中重要的类型检查工具。以下是一个较为详细的解释和示例代码&#xff0c;帮助你理解它们的工作原理和使用场景 Object.prototype.toString.call()&#xff1a; Object.prototyp…...

自学(黑客技术)方法——网络安全

如果你想自学网络安全&#xff0c;首先你必须了解什么是网络安全&#xff01;&#xff0c;什么是黑客&#xff01;&#xff01; 1.无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面性&#xff0c;例如 Web 安全技术&#xff0c;既有 Web 渗透2.也有 Web 防…...

CVE-2023-46227 Apache inlong JDBC URL反序列化漏洞

项目介绍 Apache InLong&#xff08;应龙&#xff09;是一站式、全场景的海量数据集成框架&#xff0c;同时支持数据接入、数据同步和数据订阅&#xff0c;提供自动、安全、可靠和高性能的数据传输能力&#xff0c;方便业务构建基于流式的数据分析、建模和应用。 项目地址 h…...

MySQL几种方法的数据库备份

MySQL几种方法的数据库备份_mysql备份的几种方式-CSDN博客 MySQL有几个方法来备份 最近博客一直想写点。可是不知道写什么&#xff0c;感觉自己近期的知识没有什么添加&#xff0c;今天想到了一篇能够写的博客。曾经试过依据data目录备份MySQL。可是从来没有成功过。前几天帮助…...

CI/CD:GitLab-CI 自动化集成/部署 JAVA微服务的应用合集

CI/CD&#xff1a;GitLab-CI 自动化集成/部署 JAVA微服务的应用合集 CI/CD&#xff1a;GitLab-CI 自动化集成/部署 JAVA微服务的应用合集安装DockerGitLabGitLab-Runner阿里云容器仓库 GitLab-CIJava微服务的GitLab-CI应用 CI/CD&#xff1a;GitLab-CI 自动化集成/部署 JAVA微服…...

Flask 上传文件,requests通过接口上传文件

这是一个使用 Flask 框架实现文件上传功能的示例代码。该代码定义了两个路由&#xff1a; /upload&#xff1a;处理文件上传请求。在该路由中&#xff0c;我们首先从请求中获取上传的文件&#xff0c;然后将文件保存到本地磁盘上&#xff0c;并返回一个字符串表示上传成功。 /…...

kvm webvirtcloud 如何添加直通物理机的 USB 启动U盘

第一步&#xff1a;查看USB设备ID 在物理机上输入 lsusb 命令 rootubuntu:/media/usb1# lsusb Bus 002 Device 002: ID 0781:5581 SanDisk Corp. Ultra Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 004: ID 0424:2514 Microchip Technolo…...

html- a标签包裹img标签, 点击图片无法跳转问题记录及解决方法

html- a标签包裹img标签, 点击图片无法跳转问题记录及解决方法 首先明确我们的目标, 就是a标签包裹的内容, 都能点击后以后直接跳转 可以通过更改html结构和css解决: <div v-for"(item, index) in newsData" :key"index" class"flexcol">&…...

Halcon转OpenCV实例--保险丝颜色识别(附源码)

导 读 本文主要介绍Halcon转OpenCV实例--保险丝颜色识别(附源码)。 实例来源 实例来源于Halcon例程color_fuses.hdev--classify fuses by color 下面是Halcon实例代码和实现效果: * color_fuses.hdev: classify fuses by colordev_update_window (off)* ***** step: set up …...

制造业中的微小缺陷检测——应用场景分析与算法选择(YoloV8/CANet)

一、缺陷检测任务 缺陷检测的任务通常可以分为三个主要阶段&#xff0c;包括缺陷分类、缺陷定位和缺陷分割。 1.缺陷分类 缺陷分类是检测过程的第一步&#xff0c;目的是将检测到的缺陷区域分类为不同的类别&#xff0c;通常是根据缺陷的性质或类型进行分类。分类的类别包括…...

支持多校 微信课表小程序源码 排课小程序源码 支持导入课表 情侣课表 背景设置

练手Lab课程表小程序源码是一个基于thinkphp系统进行开发的前后端分离系统。 源码功能介绍 1、情侣功能 2、情侣间留言 3、情侣间互相设置课程表背景 4、自己日、周课程表背景设置 5、教务系统课程表导入 6、导入别人分享的课表 7、导入别人分享的单课 8、多校支持 9…...

谷歌计划从Chrome119起测试IP隐私保护功能

目前&#xff0c;谷歌正为Chrome浏览器测试一项新的“IP保护”功能。因为该公司认为用户IP地址一旦被黑客滥用或秘密跟踪&#xff0c;都可能导致用户隐私信息泄露。 而这项功能可通过代理服务器屏蔽用户的IP地址&#xff0c;以增强用户的隐私性&#xff0c;这样就可以尽量在确…...

【技能树笔记】网络篇——练习题解析(九)

目录 前言 一、OSPF双栈 1.1 OSPFv3 LSA 1.2 OSPFv3 二、ISIS双栈 2.1 ISISv6 2.2 ISIS高级特性 三、BGP双栈 四、PIM双栈 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filot…...

c++ qt连接操作sqlite

qt客户端编程,用到数据库的场景不多,但是部分项目还是需要数据库来保存同步数据,客户端用到的数据库,一般是sqlite。 Qt提供了数据库模块,但是qt本身的数据库模块并不好用,会有各种问题, 建议大家不要,可以自己封装数据库的操作。本篇博客介绍qt连接操作sqlite。 sqlit…...

Qt之自定义QStringListModel设置背景色和前景色

一.效果 二.实现 QStringListModel里只实现了Qt::EditRole和Qt::DisplayRole,不能直接设置背景色和前景色,所以我们要继承QStringListModel,重写其中的data和setData方法,使其支持Qt::ForegroundRole和Qt::BackgroundRole。 QHStringListModel.h #ifndef QHSTRINGLISTMO…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)

+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...

鸿蒙Navigation路由导航-基本使用介绍

1. Navigation介绍 Navigation组件是路由导航的根视图容器&#xff0c;一般作为Page页面的根容器使用&#xff0c;其内部默认包含了标题栏、内容区和工具栏&#xff0c;其中内容区默认首页显示导航内容&#xff08;Navigation的子组件&#xff09;或非首页显示&#xff08;Nav…...

JavaScript 标签加载

目录 JavaScript 标签加载script 标签的 async 和 defer 属性&#xff0c;分别代表什么&#xff0c;有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...