深入理解 Cowboy WebSocket:使用 Erlang/OTP 构建高效的即时通讯(IM)应用
深入理解 Cowboy WebSocket:使用 Erlang/OTP 构建高效的即时通讯(IM)应用
引言
实时通信技术在现代 Web 应用中扮演着核心角色,而 WebSocket 作为其中的关键技术,已成为即时通讯(IM)系统不可或缺的一部分。Cowboy,这个基于 Erlang/OTP 的轻量级 HTTP 服务器框架,以其强大且用户友好的 WebSocket 功能,为开发者提供了构建高效 IM 应用的利器。本文将深入分析如何利用 Cowboy WebSocket 来打造高性能的即时通讯解决方案。
Cowboy 高效的理由
构建在现代Web应用基础上的Cowboy,利用Erlang/OTP框架的强大功能,提供了一系列的高效特性,这些特性使其成为开发高性能即时通讯(IM)应用的理想选择:
-
轻量级架构:
Cowboy建立在Ranch之上,采用每连接一个进程的模型,这不仅简化了并发处理,还降低了内存占用,因为进程可以在处理多个请求时被重用。 -
高效的二进制处理:
与字符串相比,二进制数据处理在性能上更为高效和节省资源。Cowboy充分利用Erlang的二进制模式,优化了数据传输和处理。 -
智能连接管理:
Cowboy默认配置了足够大的最大活动连接数,有效防止了大量进程处理繁重任务时对系统资源和内存的过度消耗。对于短连接请求,通过设置{max_connections, infinity}
,可以极大提升性能。 -
HTTP/2的透明支持:
HTTP/2作为一种高效的Web服务协议,Cowboy为其提供了透明支持,包括保持连接打开、并发请求处理以及通过头部压缩减少请求大小等特性。 -
WebSocket的完全控制:
Cowboy的Websocket处理程序接口允许开发者完全控制Websocket连接,包括自定义协议实现和消息处理。 -
自动超时和连接关闭:
通过设置超时,Cowboy能够自动关闭空闲连接,避免不必要的资源占用。同时,Cowboy在回调返回后使连接进程进入休眠状态,进一步减少了内存使用。 -
长轮询和服务器发送事件支持:
Cowboy提供了接口支持长轮询和服务器发送事件,有助于实现高效的数据传输和实时通信。 -
RESTful API简化实现:
Cowboy提供的REST处理程序接口简化了在HTTP协议上REST API的实现,使开发者可以更专注于业务逻辑。 -
内存优化:
通过在回调返回后使连接进程进入休眠状态,Cowboy显著降低了内存占用,同时在CPU使用或延迟上可能有所增加,但这对于大量并发连接的服务器来说是一个可接受的权衡。 -
动态超时设置:
Cowboy允许开发者根据客户端网络状况动态设置WebSocket的idle timeout值,提供了更灵活的连接管理。 -
Websocket协议的广泛支持:
Cowboy支持Websocket协议的所有标准,包括通过Autobahn测试套件的验证,证明了其高性能和符合标准的实现。 -
压缩扩展:
Cowboy的Websocket实现包括permessage-deflate
和x-webkit-deflate-frame
压缩扩展,进一步减少了传输数据的大小。
通过这些高效的特性,Cowboy WebSocket 成为了构建高性能、低延迟的即时通讯应用的强大工具。开发者可以利用这些特性,构建出既快速又可靠的实时通信系统。
Websocket Handler 架构
IMBoy 的 websocket_handler.erl
模块通过实现 cowboy_websocket
行为来管理 WebSocket 连接。以下是关键组件的概览:
init/2
:初始化请求处理。websocket_init/1
:WebSocket 连接建立后的初始化操作。websocket_handle/2
:处理 WebSocket 接收到的消息。websocket_info/2
:处理从其他进程发送到 WebSocket 进程的消息。terminate/3
:关闭 WebSocket 连接时的资源清理。
websocket_handler.erl
代码解析
以下是对 websocket_handler.erl
代码片段的解析:
1. 模块定义与行为引入:
-module(websocket_handler).
-behavior(cowboy_websocket).
定义了名为 websocket_handler
的模块,并引入了名为 cowboy_websocket
的 behavior。
2. 导出函数:
-export([init/2]).
-export([websocket_init/1]).
-export([websocket_handle/2]).
-export([websocket_info/2]).
-export([terminate/3]).
这些函数分别对应 WebSocket 生命周期的不同阶段。
3. WebSocket 初始化握手 (init/2
):
在此阶段,我们从请求中提取关键信息(如设备 ID、版本号等),并根据这些信息配置 WebSocket。
3.1 客户端连接频率控制:
首先,检查客户端设备 ID 的连接频率。配置文件中设定了每秒 2 次、每分钟 20 次的限制。
case throttle:check(throttle_ws, DID) of{limit_exceeded, _, _} ->imboy_log:warning("DeviceID ~p exceeded api limit", [DID]),Req = cowboy_req:reply(429, Req0),{ok, Req, State0};_ ->% ... 代码省略
end.
3.2 WebSocket 子协议升级:
频率检查通过后,检查 sec-websocket-protocol
请求头,确保其为非空列表。IMBoy 采用列表中的第一个元素(例如 “text”),并设置响应头。
check_subprotocols([H | _Tail], Req0) ->Req = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, H, Req0),{cowboy_websocket, Req}.
3.3 校验 Authorization:
子协议检查通过后,校验 authorization
请求头中的 JWT 令牌。验证成功后,将当前用户 UID 写入状态参数 State
,供后续使用。
auth_after(Uid, Req, State, Opt) ->Timeout = idle_timeout(Uid),{cowboy_websocket, Req, State#{current_uid => Uid}, Opt#{idle_timeout := Timeout}}.
3.4 动态设置 WebSocket 的 idle timeout 值:
设想根据客户端网络状况动态计算 idle timeout 值(该功能尚未实现,但值得期待)。
% 设置用户 WebSocket 超时时间,默认为 60 秒
% Cowboy 默认在 128 秒后关闭空闲连接,此处设置为 60000
idle_timeout(_Uid) ->60000.
4. 连接初始化 (websocket_init/1
):
一旦 WebSocket 连接建立,可以执行一些初始化操作,例如记录用户上线、获取离线消息等。
5. 消息处理 (websocket_handle/2
):
在此处理客户端发送的各种消息。例如,对于 ping 消息,回复 pong;对于文本消息,根据消息类型调用相应的处理函数。
5.1 客户端消息确认方法:
- 消息格式为
CLIENT_ACK,type,msgid,did
,例如前缀"CLIENT_ACK,"
后跟消息类型、消息唯一 ID 和设备 ID。 - 检查缓存系统中是否有相关消息的计时器引用,如果有,取消计时器并删除缓存。
- 根据消息类型清理离线消息。
相关代码如下:
% 客户端确认消息
% 格式:CLIENT_ACK,type,msgid,did
websocket_handle({text, <<"CLIENT_ACK,", Tail/binary>>}, State) ->CurrentUid = maps:get(current_uid, State),try binary:split(Tail, <<",">>, [global]) of[Type, MsgId, DID] ->Key = {CurrentUid, DID, MsgId},% 缓存设置在 message_ds:send_next/5 中case imboy_cache:get(Key) ofundefined ->ok;{ok, TimerRef} ->erlang:cancel_timer(TimerRef),imboy_cache:flush(Key)end,% ... 根据消息类型处理
end.
5.2 处理 WebSocket 消息:
根据接收到的文本消息类型,调用不同的逻辑处理函数。
websocket_handle({text, Msg}, State) ->% ... 解码消息、获取当前用户 UID% 根据消息类型分发处理逻辑case cowboy_bstr:to_lower(Type) of<<"c2c">> -> % 单聊消息websocket_logic:c2c(MsgId, CurrentUid, Data);% ... 其他消息类型处理end;
% ... 其他处理分支
6. 信息处理 (websocket_info/2
):
处理从 Erlang 系统发送到 WebSocket 进程的消息,例如超时消息或关闭连接请求。
- 处理超时消息:
websocket_info({timeout, _Ref, Msg}, State) ->{reply, {text, Msg}, State, hibernate};
当超时发生时,回复文本消息,并保持挂起状态以节省资源。
- 服务端主动关闭连接处理:
websocket_info({close, CloseCode, Reason}, State) ->{reply, {close, CloseCode, Reason}, State};
websocket_info(stop, State) ->{stop, State};
7. 连接终止 (terminate/3
):
在连接终止时,根据关闭原因执行清理操作,如记录用户下线。
terminate(Reason, _Req, State) ->% ... 执行清理操作
end;
WebSocket vs AMQP vs MQTT
在选择适合 IM 应用的协议时,需考虑以下因素:
- 实时性:WebSocket 提供最低延迟和最高实时性。
- 复杂性:AMQP 提供丰富消息模式,但配置和实现较复杂。
- 轻量级:MQTT 适合资源受限环境,但全双工通信受限。
结论
Cowboy WebSocket 提供了高效、简洁的方法来实现实时 Web 通信,特别适合需要快速交互的 IM 应用。通过深入理解其实现原理和生命周期管理,开发者可以构建高性能的实时通信系统。
希望通过本文的分析和代码示例,能帮助不同经验水平的开发者更好地理解和使用 Cowboy WebSocket,从而在项目中实现高效、稳定的实时通信功能。
欢迎关注 IMBoy 开源项目 https://gitee.com/imboy-pub。
相关文章:
![](https://www.ngui.cc/images/no-images.jpg)
深入理解 Cowboy WebSocket:使用 Erlang/OTP 构建高效的即时通讯(IM)应用
深入理解 Cowboy WebSocket:使用 Erlang/OTP 构建高效的即时通讯(IM)应用 引言 实时通信技术在现代 Web 应用中扮演着核心角色,而 WebSocket 作为其中的关键技术,已成为即时通讯(IM)系统不可或缺的一部分。Cowboy,这个基于 Erla…...
![](https://i-blog.csdnimg.cn/direct/534dd10683484afe9428f15ff0e3b691.png)
算法的几种常见形式
算法(Algorithm) 算法(Algorithm)是指解决问题或完成任务的一系列明确的步骤或规则。在计算机科学中,算法是程序的核心部分,它定义了如何执行特定的任务或解决特定的问题。算法可以用多种方式来表示和实现…...
![](https://i-blog.csdnimg.cn/direct/8e4a24d480bc44e79cf683c9c0921787.png)
SpringBoot新手快速入门系列教程二:MySql5.7.44的免安装版本下载和配置,以及简单的Mysql生存指令指南。
我的教程都是亲自测试可行才发布的,如果有任何问题欢迎留言或者来群里我每天都会解答。 我们要如何选择MySql 目前主流的Mysql有5.0、8.0、9.0 主要区别 MySQL 5.0 发布年份:2005年特性: 基础事务支持存储过程、触发器、视图基础存储引擎…...
![](https://i-blog.csdnimg.cn/direct/e0ad5a564b3f43cb806258c6c33adbc9.webp)
Elasticsearch 更新指定字段
Elasticsearch 更新指定字段 准备条件查询数据更新指定字段更新子级字段 准备条件 以下查询操作都基于索引crm_clue来操作,索引已经建过了,本文主要讲Elasticsearch更新指定字段语句,下面开始写更新语句执行更新啦! 查询数据 查…...
![](https://www.ngui.cc/images/no-images.jpg)
Koa.js、Egg.js与Express.js:探析三大Node.js框架的异同
在Node.js的世界里,选择合适的框架对于构建高效、可维护的后端服务至关重要。Express.js、Koa.js 和 Egg.js 是三个备受欢迎的框架,它们各有特色,适用于不同的开发场景。本文旨在深入探讨这三个框架的区别,并通过代码示例帮助开发…...
![](https://i-blog.csdnimg.cn/direct/2091458f08e34c03b73e1081f7d4c92e.png)
【MYSQL】如何解决 bin log 与 redo log 的一致性问题
该问题问的其实就是redo log 的两阶段提交 为什么说redo log 具有崩溃恢复的能力 MySQL Server 层拥有的 bin log 只能用于归档,不足以实现崩溃恢复(crash-safe),需要借助 InnoDB 引擎的 redo log 才能拥有崩溃恢复的能力。所谓崩…...
![](https://i-blog.csdnimg.cn/direct/be1783897c5e4762b9e81c2b4c786397.png)
翻译语音识别在线的软件,分享4款实用的软件!
在全球化日益加速的今天,语言沟通已成为人们生活中不可或缺的一部分。无论是商务洽谈、学术交流还是日常交流,翻译语音识别技术都扮演着举足轻重的角色。今天,我们就来揭秘一下,那些能让你在语言沟通中如虎添翼的翻译语音识别软件…...
![](https://www.ngui.cc/images/no-images.jpg)
Qt 的Q_PROPERTY关键字
Qt 的Q_PROPERTY关键字 1. Q_PROPERTY 的由来2. 实现原理3. Q_PROPERTY 的特点4. Q_PROPERTY 的属性5. 应用说明示例代码示例代码连接信号和槽的多种方式处理信号和槽的注意事项 QT的元对象系统1. 元对象系统的由来2. 实现原理3. 元对象系统的特点4. 元对象系统的属性5. 应用说…...
![](https://www.ngui.cc/images/no-images.jpg)
github 下载提速的几种方法
1. 代理下载(无需注册) //toolwa.com/github/ //d.serctl.com/2. 转入 Gitee 加速 将项目镜像到 Gitee 中下载加速 3. 使用 Watt Toolkit 加速 Watt Toolkit //steampp.net/选择合适的版本下载 选择 github,一键加速 4.CDN 加速 (修改…...
![](https://img-blog.csdnimg.cn/img_convert/7092af42301962fc7bc85f3877d6d968.png)
【Oracle】实验三 Oracle数据库的创建和管理
【实验目的】 掌握Oracle数据库的创建方法使用DBCA创建数据库在数据库中装入SCOTT用户及其表 【实验内容】 使用DBCA创建数据库,名为MYDB,找到其初始化文件(文本型和服务器型文件都要找到),查看各类默认位置并记录下来(包括物理文件所在目…...
![](https://www.ngui.cc/images/no-images.jpg)
Linux rpm和ssh损坏修复
背景介绍 我遇到的问题可能和你的不一样。但是如果遇到错误一样也可以按此方案尝试修复。 我是想在Linux上安装Oracle,因为必须在离线环境下安装。就在网上搜一篇文章linux离线安装oracle,然后安装教程走,进行到安装oracle依赖包的时候执行了…...
![](https://i-blog.csdnimg.cn/direct/1cd38b3049284044aeeb1d7628264c69.png)
仕考网:公务员考试面试时间一般多长?
公务员考试主要分为笔试与面试两个阶段,其中面试是笔试通过的下一关,面试的具体安排通常由相关考试机构或招录单位负责发布并通知考生。 公务员面试的持续时间一般在30分钟至1小时之间,具体时长可能因地区和招录单位的不同而有所变化。常见的…...
![](https://i-blog.csdnimg.cn/direct/d6a7e2deb91644ce8c77168543940838.png)
C语言作业5(学生管理系统C语言)
成学生管理系统 1> 使用菜单完成 2> 有学生的信息录入功能:输入学生个数,并将学生的姓名、分数录入 3> 查看学生信息:输出所有学生姓名以及对应的分数 4> 求出学习最好的学生信息:求最大值 5> 按姓名将所有学…...
![](https://i-blog.csdnimg.cn/direct/aa4ddef250d543858434f6e9dc307f39.png)
OS Copilot:新手测评体验
文章目录 前言一、OS Copilot(阿里云操作系统智能助手)简介二、测评体验总结OS Copilot 产品体验评测OS Copilot 产品功能反馈 前言 本文简单分享一下自己使用OS Copilot测评体验。 一、OS Copilot(阿里云操作系统智能助手)简介 …...
![](https://i-blog.csdnimg.cn/direct/eab8f40f22194281a5976eba2668e31c.png)
PS 2024【最新】中文白嫖版!,安装教程,图文步骤
文章目录 软件介绍软件下载安装步骤 软件介绍 Photoshop,简称“PS” Adobe Photoshop,简称“PS”,是由Adobe Systems开发和发行的图像处理软件。Photoshop主要处理以像素所构成的数字图像。使用其众多的编修与绘图工具,可以有效地…...
![](https://www.ngui.cc/images/no-images.jpg)
bind方法的使用
在JavaScript或TypeScript中,this.data.setEventListener(this.onAddEvent.bind(this)); 和 this.data.setEventListener(this.onAddEvent); 之间的主要区别在于this关键字的绑定方式。 不使用.bind(this) 当你直接传递函数引用 this.onAddEvent给 setEventListene…...
![](https://i-blog.csdnimg.cn/direct/74b01b96a912416fb43618ee05ff9a2a.png)
MySQL数据库基本操作-DDL和DML
1. DDL解释 DDL(Data Definition Language),数据定义语言,该语言部分包括以下内容: 对数据库的常用操作对表结构的常用操作修改表结构 2. 对数据库的常用操作 功能SQL查看所有的数据库show databases;查看有印象的数据库show d…...
![](https://www.ngui.cc/images/no-images.jpg)
iOS 应用内存超过多少会收到系统内存警告 ?
iOS 应用内存超过多少会收到系统内存警告 ? 在 iOS 应用中,系统内存警告的触发是由 iOS 操作系统动态决定的,并不是一个固定的阈值。系统会根据当前设备的可用内存、正在运行的其他应用程序的内存需求以及当前应用程序的内存占用情况来判断是…...
![](https://i-blog.csdnimg.cn/direct/a302b6291829427e901bb3631739153a.png)
【分布式系统】Filebeat+Kafka+ELK 的服务部署
目录 一.实验准备 二.配置部署 Filebeat 三.配置Logstash 四.验证 一.实验准备 结合之前的博客中的实验 主机名ip地址主要软件es01192.168.80.101ElasticSearches02192.168.80.102ElasticSearches03192.168.80.103ElasticSearch、Kibananginx01192.168.80.104nginx、Logs…...
![](https://www.ngui.cc/images/no-images.jpg)
Qt Qwt 图表库详解及使用
文章目录 Qt Qwt 图表库详解及使用一、Qwt 概述二、安装 Qwt1. 下载和编译 Qwt2. 在项目中使用 Qwt三、Qwt 的基本使用1. 创建一个简单的折线图2. 添加图例和自定义样式四、Qwt 的交互功能1. 启用缩放和平移2. 启用数据点选择五、Qwt 的高级特性1. 实时数据更新2. 多轴绘图六、…...
![](https://i-blog.csdnimg.cn/direct/1e31467e71b34ac0b35c7329de47914c.png)
基于B站视频评论的文本分析,采用包括文本聚类分析、LDA主题分析、网络语义分析
研究主题 本研究旨在通过对B站视频评论数据进行文本分析,揭示用户评论的主题、情感倾向和语义结构,助力商业决策。主要技术手段包括Python爬虫、LDA主题分析、聚类分析和语义网络分析。首先,利用Python爬虫采集大量评论数据并进行预处理。运…...
![](https://www.ngui.cc/images/no-images.jpg)
【Qt】xml Dom复制
1. 功能 将A.xml文件中的copyNode节点全部复制到B.xml中的testRoot节点。 2. 代码 #include <QDomDocument> #include <QFile> #include <QIODevice> #include <QtXml>void copyNodeXml() {// 源文件DOMQDomDocument ADoc;// 加载源文件QFile fileA(…...
![](https://www.ngui.cc/images/no-images.jpg)
MySQL联合索引最左匹配原则
MySQL中的联合索引(也叫组合索引)遵循最左匹配原则,即在创建联合索引时,查询条件必须从索引的最左边开始,否则索引不会被使用。在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序。 例…...
![](https://www.ngui.cc/images/no-images.jpg)
2024最新最全面的软件测试自动化面试题(含答案)
1.如何把自动化测试在公司中实施并推广起来的? 选择长期的有稳定模块的项目 项目组调研选择自动化工具并开会演示demo案例,我们主要是演示selenium和robot framework两种。 搭建自动化测试框架,在项目中逐步开展自动化。 把该项目的自动化…...
![](https://i-blog.csdnimg.cn/direct/e90d5fdf05094801a2c11befedd05342.webp)
Linux磁盘-MBRGPT
作者介绍:简历上没有一个精通的运维工程师。希望大家多多关注作者,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux磁盘涉及到的命令不是很多,但是在实际运维中的作用却很大,因为Linux系统及业务都会承载到硬盘上…...
![](https://i-blog.csdnimg.cn/direct/a5d07b2c5efd49bdab9c5482cc7bd36d.png)
kind kubernetes(k8s虚拟环境)使用本地docker的镜像
kubernetes中,虽然下载镜像使用docker,但是存储在docker image里的镜像是不能被k8s直接使用的,但是kind不同,可以使用下面的方法,让kind kubernetes环境使用docker image里的镜像。 kind – Quick Start 例如&#x…...
![](https://i-blog.csdnimg.cn/direct/5d69f7f1624f4943a981b640e1566fe0.png)
kafka发送消息流程
配置props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class); public Map<String,Object> producerConfigs(){Map<String,Object> props new HashMap<>();props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,bootstrapServers…...
![](https://i-blog.csdnimg.cn/direct/d5dee11b5cb44cd1a57a88bb35b40a43.png)
使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-敌人生成器(八)
文章目录 开发思路敌人生成器代码分析属性配置 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击(一) 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-激光组件(二) 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-飞…...
![](https://i-blog.csdnimg.cn/direct/1a37df650af84638b9d2c66bc5236d53.png#pic_center)
Allegro中show elements不弹窗问题
今天allegro用的好好的,刚刚还可以正常使用show elements进行对象的详细信息查看的,突然就不好使了,具体表现为不弹窗。 找了好久找到一个类似问题的,具体的解决方法是: D:\Allegro\Cadence\SPB_Data\pcbenv在allegro的…...
![](https://i-blog.csdnimg.cn/direct/a220387fa28c42008b41ee72772c7cdf.png)
【C++】继承最全解析(什么是继承?继承有什么用?)
目录 一、前言 二、什么是继承 ? 💢继承的概念💢 💢继承的定义💢 🥝定义格式 🍇继承权限 三、基类与派生类对象的赋值转换 四、继承的作用域 五、派生类中的默认成员函数 💢…...
![](/images/no-images.jpg)
无锡本地网站有哪些/惠州seo外包平台
我在垂直链中有两个视图(viewA,viewB). viewA的宽高比应始终为1:1,而viewB的高度是动态的(可能为400dp,可能为700dp)预期成绩:当viewB的高度太大而无法容纳屏幕中的两个视图时,应减小viewA的宽度(保持宽高比).实际结果:如果viewB的高度太大,则…...
![](/images/no-images.jpg)
网站要多钱/百度网站的网址
zip:压缩:zip [-AcdDfFghjJKlLmoqrSTuvVwXyz$][-b][-ll][-n][-t][-][压缩文件][文件...][-i][-x]解压:unzip[选项] 压缩文件名.zip选项:-x 文件列表解压缩文件,但不包括指定的file文件。-v 查看压缩文件目录,但不解压。…...
![](/images/no-images.jpg)
环影视界wordpress企业主题/app推广注册招代理
序 本文主要研究一下redis的监控工具 redis-stat redis-stat是一个比较有名的redis指标可视化的监控工具,采用ruby开发,基于redis的info命令来统计,不影响redis性能。 docker运行docker run --name redis-stat -p 8080:63790 -d insready/red…...
![](https://img-blog.csdnimg.cn/20190813170821899.png)
专业网站设计专家/广州网络推广公司有哪些
有其他的方法可以一起交流学习n(*≧▽≦*)n 效果图:css样式自己设置哟 js: let index 0; function generateDom(menusData,menusDom) {for(let menu in menusData) {let li document.createElement(li);if(typeof(menusData[menu].subMenu)"undefined"…...
![](/images/no-images.jpg)
wordpress 英文/宁波seo关键词优化方法
敏捷领导力总结一下:您的敏捷转换被困住了。 您已经考虑了自己的原因,就像成为敏捷领导者一样,第1部分:定义原因 。 您已经开始衡量可能性。 您像要成为敏捷领导者,第2部分:与谁接触一样,对与谁…...
![](https://img-blog.csdnimg.cn/20210525213312276.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L09ubHlvbmVfMTMxNA==,size_16,color_FFFFFF,t_70)
重庆网站优化指导/成都网站快速优化排名
Windows消息被广泛用于各种事件的通知上面,如果你想操作窗口或者控件(UI元素其实也是一种窗口,如:按钮,编辑框,工具栏,树形控件等)的话,给它发送消息即可。消息也可以来至于其他应用程序。你也可以通过消息来实现系统通知,移动鼠标,按下键盘上的某键等操作。 正如我们前面所讨…...