charlesbeacon发送间隔封包时怎么调间隔 ?

原文地址:
是在Mac下常用的截取网络封包的工具,在做iOS开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析。Charles通过将自己设置成系统的网络访问代理服务器,使得所有的网络访问请求都通过它来完成,从而实现了网络封包的截取和分析。
Charles是收费软件,可以免费试用30天。试用期过后,未付费的用户仍然可以继续使用,但是每次使用时间不能超过30分钟,并且启动时将会有10秒种的延时。
安装Charles
去Charles的官方网站()下载最新版的Charles安装包,是一个dmg后缀的文件。打开后将Charles拖到Application目录 下即完成安装。
安装SSL证书
如果你需要截取分析SSL协议相关的内容。那么需要安装Charles的CA证书。具体步骤如下:
去&&下载CA证书文件。解压该zip文件后,双击其中的.crt文件,这时候在弹出的菜单中选择“总是信任”,如下所示:
从钥匙串访问中即可看到添加成功的证书。如下所示:
将Charles设置成系统代理
之前提到,Charles是通过将自己设置成代理服务器来完成封包截取的,所以使用Charles的第一步是将其设置成系统的代理服务器。
启动Charles后,第一次Charles会请求你给它设置系统代理的权限。你可以输入登录密码授予Charles该权限。你也可以忽略该请求,然后在需要将Charles设置成系统代理时,选择菜单中的 &Proxy& -& &Mac OS X Proxy&来将Charles设置成系统代理。如下所示:
之后,你就可以看到源源不断的网络请求出现在Charles的界面中。
Charles主界面介绍
Charles主要提供2种查看封包的视图,分别名为“Structure”和&Sequence&。
Structure视图将网络请求按访问的域名分类。Sequence视图将网络请求按访问的时间排序。
大家可以根据具体的需要在这两种视图之前来回切换。
对于某一个具体的网络请求,你可以查看其详细的请求内容和响应内容。如果响应内容是JSON格式的,那么Charles可以自动帮你将JSON内容格式化,方便你查看。
过滤网络请求
通常情况下,我们需要对网络请求进行过滤,只监控向指定目录服务器上发送的请求。对于这种需求,我们有2种办法。
在主界面的中部的Filter栏中填入需要过滤出来的关键字。例如我们的服务器的地址是:,那么只需要在Filter栏中填入yuantiku即可。
在Charles的菜单栏选择&Proxy&-&&Recording Settings&,然后选择Include栏,选择添加一个项目,然后填入需要监控的协议,主机地址,端口号。这样就可以只截取目标网站的封包了。如下图所示:
通常情况下,我们使用方法1做一些临时性的封包过滤,使用方法2做一些经常性的封包过滤。
截取iPhone上的网络封包
Charles通常用来截取本地上的网络封包,但是当我们需要时,我们也可以用来截取其它设备上的网络请求。下面我就以iPhone为例,讲解如何进行相应操作。
Charles上的设置
要截取iPhone上的网络请求,我们首先需要将Charles的代理功能打开。在Charles的菜单栏上选择“Proxy”-&&Proxy Settings&,填入代理端口8888,并且勾上&Enable transparent HTTP proxying& 就完成了在Charles上的设置。如下图所示:
iPhone上的设置
首先我们需要获取Charles运行所在电脑的IP地址,打开Terminal,输入ifconfig en0, 即可获得该电脑的IP,如下图所示:
在iPhone的 “设置”-&“无线局域网“中,可以看到当前连接的wifi名,通过点击右边的详情键,可以看到当前连接上的wifi的详细信息,包括IP地址,子网掩码等信息。在其最底部有“HTTP代理”一项,我们将其切换成手动,然后填上Charles运行所在的电脑的IP,以及端口号8888,如下图所示:
设置好之后,我们打开iPhone上的任意需要网络通讯的程序,就可以看到Charles弹出iPhone请求连接的确认菜单(如下图所示),点击“Allow”即可完成设置。
截取SSL信息
Charles默认并不截取SSL的信息,如果你想对截取某个网站上的所有SSL网络请求,可以在该请求上右击,选择SSL proxy,如下图所示:
这样,对于该Host的所有SSL请求可以被截取到了。
模拟慢速网络
在做iPhone开发的时候,我们常常需要模拟慢速网络或者高延迟的网络,以测试在移动网络下,应用的表现是否正常。Charles对此需求提供了很好的支持。
在Charles的菜单上,选择&Proxy&-&&Throttle Setting&项,在之后弹出的对话框中,我们可以勾选上“Enable Throttling”,并且可以设置Throttle Preset的类型。如下图所示:
如果我们只想模拟指定网站的慢速网络,可以再勾选上图中的&Only for selected hosts&项,然后在对话框的下半部分设置中增加指定的hosts项即可。
修改网络请求内容
有些时候为了调试服务器的接口,我们需要反复尝试不同参数的网络请求。Charles可以方便地提供网络请求的修改和重发功能。只需要在以往的网络请求上点击右键,选择“Edit”,即可创建一个可编辑的网络请求。如下所示:
我们可以修改该请求的任何信息,包括url地址,端口,参数等,之后点击“Execute”即可发送该修改后的网络请求(如下图所示)。Charles支持我们多次修改和发送该请求,这对于我们和服务器端调试接口非常方便。
通过Charles软件,我们可以很方便地在日常开发中,截取和调试网络请求内容,分析封包协议以及模拟慢速网络。用好Charles可以极大的方便我们对于带有网络请求的App的开发和调试。
参考链接:
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:110694次
积分:1593
积分:1593
排名:第11486名
原创:26篇
转载:152篇
评论:11条网络封包分析工具Charles(2)
在做iPhone开发的时候,我们常常需要模拟慢速网络或者高延迟的网络,以测试在移动网络下,应用的表现是否正常。Charles对此需求提供了很好的支持。 在
在做iPhone开发的时候,我们常常需要模拟慢速网络或者高延迟的网络,以测试在移动网络下,应用的表现是否正常。Charles对此需求提供了很好的支持。在Charles的菜单上,选择”Proxy”–&“Throttle Setting”项,在之后弹出的对话框中,我们可以勾选上“Enable Throttling”,并且可以设置Throttle Preset的类型。如下图所示:如果我们只想模拟指定网站的慢速网络,可以再勾选上图中的”Only for selected hosts”项,然后在对话框的下半部分设置中增加指定的hosts项即可。修改网络请求内容有些时候为了调试服务器的接口,我们需要反复尝试不同参数的网络请求。Charles可以方便地提供网络请求的修改和重发功能。只需要在以往的网络请求上点击右键,选择“Edit”,即可创建一个可编辑的网络请求。如下所示:我们可以修改该请求的任何信息,包括url地址,端口,参数等,之后点击“Execute”即可发送该修改后的网络请求(如下图所示)。Charles支持我们多次修改和发送该请求,这对于我们和服务器端调试接口非常方便。总结通过Charles软件,我们可以很方便地在日常开发中,截取和调试网络请求内容,分析封包协议以及模拟慢速网络。用好Charles可以极大的方便我们对于带有网络请求的App的开发和调试。
上一篇我的2014 - 所有经历, 都该被感激
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
核心技术类目
访问:791693次
积分:22703
积分:22703
排名:第101名
博客之星拉票
我参加了博客之星评选,如果你喜欢我的博客,求投票~~
姓名:余龙泽
生日:1994.6
学历:本科
专注:iOS、cocos2d-x、算法
学校:哈尔滨工业大学
爱好:乒乓球、lol
学习的路上, 与君共勉。如有疑惑, 欢迎小窗。
iOS-设计模式
阅读:1508
iOS开发- 实战篇
阅读:74247
编写高质量代码(c++)
阅读:3731
ios开发学习点滴
文章:45篇
阅读:101418
剑指POJ-ACM学习
文章:10篇
阅读:7293
一步步学算法
文章:21篇
阅读:87512
(责任编辑:赵红霞)
------分隔线----------------------------
FliptileTime Limit:2000MSMemory Limit:65536KTotal Submissions:3629Accepted:1400De...
今天沈阳斌子在做APP时,客户的需求变更是在原有的程序上加入...
MySQLdb的使用。1.create table:#-*- coding:utf-8 -*- @data: @filenam...
一、建立FORM窗体,加一个按钮控件,加一个DATAGRIDVIEW控件。二、...
原文地址:/agileblog/p/3615250.html关于爬虫乱码有...
Ubuntu 14.10 64bitbind 80端口失败,提示:Bind error!: Permission denied起初...页游安全攻与防
----------------------------------------------------------------
网页游戏的安全问题,在刚入职接触的时候,写过两篇比较浅显的文章《》和《》。算算时间,距离现在也有一年多了,虽然页游安全总体上并没有显著变化,没有新的攻击方法,也没有新的防御方法,我个人的工作重心也由页游安全转向了手游安全,但出于完美主义的偏执,还是希望写一篇覆盖完整的页游安全文章,希望能给页游产业一点帮助。
-----------------------------------------------------------------------------
一、协议安全(swf安全):自动封包 (重点)
二、自动游戏+加速
三、内存安全:内存修改
四、存档安全:存档修改
五、帐号安全/充值安全:盗号/低价充值
------------------------------------------------------------------------------------------------------
一、协议安全(swf安全):自动封包 (重点)
页游,最最核心的就是客户端(swf)与服务端的游戏通信了。游戏通信产生的封包,内容是否可识别,可篡改,可重放,处理逻辑是否有漏洞,都决定了这款游戏是否有重大的漏洞。
我们知道页游前端和后台的通信一般有两者方式,一种是http连接,一种是socket连接,前者适用于小型页游,例如内嵌在QQ平台的QQ农场,后者适用于大型页游。
不同的通信方式,产生的数据包格式也不一样,像HTTP AMF的可以使用charles来抓包查看,像sockets的可以使用WPE抓包查看。
以socket 通信为例,协议采用自定义格式,一般由两部分组成,包头与包体,包头一般是固定长度,包体为可变长度。包头一般是一些基本信息,例如包长度,版本号,命令号,用户ID,序列号等;包体就是操作命令对应的接收参数,参数个数不同,参数类型不同会导致包体长度不同。
只要摸清楚协议算法,即包是如何生成的,就可以构造数据包与服务器自由通话,这一后果是非常严重的。自由通话意味着你不需要老老实实的在客户端操作,一条数据包就能代替你一连串的操作,例如发送一条数据包完成一个任务,常用于快速升级,淘宝上的页游代练绝大多数都是采用的这种方式;自由通话更意味着你可以绕过客户端的逻辑判断,传任意参数给服务端。说到这里,你可能觉得只要服务端能正常处理来自客户端的参数,不出逻辑错误,不出配置错误,就万事大吉。这种想法很常见,例如上海宝开公司的某个开发就说过,我们的游戏逻辑判断都在服务端,我们没有外挂。我推测这个人应该不怎么上外网。
理想是丰满的,现实是骨感的,怎么能保证后台不将逻辑写错,策划运营不将配置弄错呢,特别是在高强度的通宵加班后。你可能说靠测试呀,中国页游行业,配给给游戏的测试人员是非常少的,相应的测试时间也是远远不足的,并且测试技术也非常需要提高,总的来说,在页游行业,能做完整协议测试的公司不多。但玩家,特别是从事外挂制作代练服务的打金工作室会&帮你&好好地彻底地做协议测试。他们会先反编译客户端上的SWF文件(缓存中的,内存中的)得到协议生成算法,制作成封包工具,遍历每个协议号,每个参数输入,让你的错误无从遁形。
我见过一个非常聪明的外挂制作者,在外挂中添加了脚本分享平台,号召大家共同摸索,将有问题的封包以脚本的方式上传以供大家下载,这种集思广益真的很妙,脚本的分享会给&找bug&精神奖励,将其帐号公布出来以供大家瞻仰,因此乐意分享的人很多。而且作者还很负责的有脚本审核机制,并支持快捷的查询。这是什么样的用户体验呀,这就是页游外挂界的app store
看到这里,你或许想,我保护好SWF文件,不让其逆向不就行了吗?有需求,就有满足需求的地方,市面上有不少给SWF提供加密服务的收费产品,例如Amayeta SWF Encrypt 和 DComSoft SWF Protector&,因为收费,没用过这些产品,不知道具体原理,但据了解,最常用SWF加密方式,就是破坏SWF标准文件头,通过向SWF的二进制文件的文件头写入无意义的数据,从而导致反编译软件无法正常解析SWF文件。下图是使用反编译器打开加密的SWF文件,会提示无法解析
我们可以对比一下采用这种加密方式的swf文件头内容:
(1)未加密swf文件
&正常的SWF文件,文件头部是由一个三字节的标识符开始,为0&46、0&57、0&53(&FWS&)或者0&43、0&57、0&53(&CWS&)其中之一。&FWS&标识符说明该文件是未压缩的SWF文件,&CWS&标识符则说明该文件前8个字节之后(即文件长度字段之后)的全部数据为开源的标准ZLIB方式压缩
(2)加密后的SWF文件
很明显,文件头部变成了无意义的符号。
实现函数示例(参考)
有加密就有解密,加密的SWF文件需要还原,虽然反编译不了加密后的SWF文件,但可以反编译解密文件找到解密代码来还原加密SWF文件的文件头。(参考&/Article/406.html&)
很明显,这种SWF加密方法没什么作用。于是大家想着如何用一种无法反编译的实现方法来隐藏加密算法,例如利用Alchemy能够编译C/C++代码为AS字节码但无法被反编译的特性来隐藏加密算法。其实我怀疑目前有工具将Alchemy还原成C了,例如ASV(actionscript view 2012)就号称是目前最强悍的SWF解密工具,网上都有该工具的团购消息了。
我不懂SWF的加密解密,但我知道有一条万能守则,任何加密在内存中都是解密状态的。当无法解密的时候,就从内存中查找导出吧,我们可以先使用SWF Memory Dumper从浏览器内存中导出解密并解压缩的SWF文件,再用传播程度都烂大街的硕思闪客反编译得到源码。这一方法对游戏协议安全来说,是非常非常非常悲剧的。如下图所示,包的组成结构,包中各个字段的生成算法都可以通过逆向解密SWF文件获得!
例如下图为游戏协议结构
&例如下图就是游戏协议对应的命令号
例如下图为游戏配置表
一切的一切,都注定了要想完全解决协议安全问题,只有靠AS混淆了。目前有一些收费软件提供AS混淆,例如&。但奇怪的是,没有多少页游公司实施AS混淆。
写到这里,或许你会幻想游戏协议算法不会被逆向得知,那不就完事大吉了吗?再次申明,现实是残忍的,即使不知道协议算法,照样可以修改游戏封包。我们可以通过耐心的反复操作来对比封包的不同,定位到修改点。一般使用WPE工具,修改包体。WPE工具的关键就是过滤器,查找到符合指定特征的封包,将特定的位置替换为修改的值。如下图所示,
除了封包篡改,最简单的连封包结构都不要猜测的就是封包重放作弊了,例如打怪是一条封包,只要反复重放该封包,就能轻易刷取经验值了。
好的协议设计,一定要考虑到防御封包篡改和封包重放,最简单的方法是在封包的某个字段加入一个序列号,该序列号包含包体完整性检验值,时间因素值,来区分封包是否是重放的,是否有被篡改。
有了好的协议设计,协议是否安全了呢?协议的实现方法在客户端SWF中,这一事实又讲安全问题引回到SWF被逆向的问题上,只要被成功逆向,一切努力都打水漂了。虽然防御艰难,但也不能放弃,一种方法不行,可以用多种方法。总的来说,为了协议安全,可以做如下措施(不仅仅从技术上):
1.好的协议设计,防重放与篡改&
2.SWF加密 ,注意加密算法的安全,最好AS混淆
3.耐心的做协议检测,开发在提交测试前,做协议测试;测试在完成了功能测试,性能测试后也要搞好协议测试;策划运营对配置表做认真的检查;
4.设计一套监控系统,监控游戏中的收益与消费,大量的刷取物品肯定会在数值变化中体现出来;
5.留意游戏论坛,游戏QQ群里是否有新漏洞的披露,外挂是否有更新;
6.频繁更换加密算法,与外挂制作者PK更新速度
二、自动游戏+加速
自动游戏,简单的说就是模拟鼠标或键盘对游戏UI的操作,代替你做重复的工作。最简单的自动游戏脚本可以使用按键精灵来制作,先对正常操作进行录制,然后编辑,设置热键,最后回放即可。程序实现中一般会有以下几个关键函数
(1)模拟键盘
VOID keybd_event(
BYTE bVk, // 虚拟键码
BYTE bScan, // 扫描码
DWORD dwFlags,
ULONG_PTR dwExtraInfo // 附加键状态
(2)模拟鼠标
VOID mouse_event(
DWORD dwFlags, // motion and click options
DWORD dx, // horizontal position or change
DWORD dy, // vertical position or change
DWORD dwData, // wheel movement
ULONG_PTR dwExtraInfo // application-defined information
自动游戏的作弊方式常见于对战刷怪类游戏,自动识别地图中怪物出现的位置,自动出招打怪,自动拾取掉落宝物。往往还会配合加速外挂,总的来说,就是靠达成那种不知疲倦(脚本操作)、准确度高(自动识别地图中UI特征)、快速(对页游就是加速flash的动画播放速度)的操作方式来快速升级。
自动游戏类型外挂的防御比较简单,增加人机识别的因素,类似于论坛避免批量注册,采用只有人类才能识别的验证码(题外话,不少网站的验证码其实可以机器识别),例如对于对战类游戏,记录每次对战的频率和操作时间特性,对异常的操作弹出图片验证,中断自动游戏。
在实施图片验证的时候,要考虑到两个要素:
一是图片是否真正的机器难以识别,要预防简单的像素采集技术;
二是图片库是否及时更新,要预防图片库的遍历。
而加速外挂,也常见于对战类游戏。改变操作速度有两种情况。一种是使用变速齿轮之类的加速外挂加快flash动画播放速度,一种是速度值为游戏中的某个变量值,修改了对应的数值。
对于加快flash动画播放速度的加速外挂,我们可以通过对比客户端服务端时间是否同步来检测,当检测到异常的时候,可以弹出图片验证,中断加速。
对于速度由游戏中数值控制的,做好协议安全,使其无法改封包中对应的数值;做好SWF反逆向保护,使其无法修改源码中控制速度的逻辑。
三、内存安全:内存修改
修改游戏在内存中的数值是经典的单机游戏作弊方法,同样也适用于网页游戏,原因很简单,不可能每个来自客户端的数据,服务端都做验证。(看看页游公司开发前端和后台的比例吧!)以社交类网页游戏为例,其中会内嵌不少小游戏,这些小游戏可能是用来赚取游戏经验或货币等数值的,很显然,这种类型的小游戏基本就是主逻辑在客户端的单机游戏,只是最后将游戏分数上传给服务器,服务器再根据游戏分数的不同来下发相应数额的游戏货币。我们完全可以在积分上传前,在内存中查找并修改该数值。 如下图所示,用cheat engine去查找IE进程中游戏分数。
(cheat engine是我最喜欢的内存修改工具,手游上的内存修改工具和这个比起来简直是胎儿版的。该工具支持自定义格式的内存搜索,具备强大的反汇编功能,更妙的是可以直接生成外挂,特别赞的是竟然采用了游戏通关的方式来教授工具使用,我的博客中有于通关方法)
程序实现一般会有以下几个关键函数
(1)读取进程数据ReadProcessMemory
(2)&查找,查找算法可以按数值类型、扫描类型及内存扫描方式来实现
例如数值类型有二进制,1字节,2字节,4字节(游戏最常用),8字节,浮点数,双浮点数,文本,字节数组,自定义(这个最牛);
例如扫描类型可以支持精确查找,模糊查找(比...大,比...小,两者之间),数值变化趋势(数值处于增加中,数值出于减少中,数值没有变动,与首次扫描数值相同,数值增加了某个指定值,数值减少了某个指定值);
例如内存扫描方式有自定义扫描起始与终止地址,同时扫描只读内存,深度扫描,快速扫描,扫描时暂停游戏
(3)写数据WriteProcessMemory
内存修改的防御,有以下几种建议:
(1)重要数值在内存中拆分存放,使其无法简单定位到(会使得游戏逻辑变复杂)
(2)默认可以修改,在服务端控制收益上线。(考虑到成本,为目前主流控制方法)
四、存档安全:存档修改
在flas在flash单机游戏时代,修改本地存档文件,是游戏作弊的重要方式,相信有不少人就用过Flash存档修改器。
& & & & & & & 随着页游兴起到现在的页游繁盛,依赖于存档进行逻辑判断的设计减少了,但这块也不能完全忽略掉。总会有一些功能是需要调用本地存档的。例如登录模块中,记住密码功能,会将密码信息存储在本地,以IE浏览器为例,在C:\Documents and Settings\(你的Windows用户名)\Application Data\Macromedia&\Flash Player\#SharedObjects\(一些随机数字和字母)\ 文件夹下就可以看到存储密码的SOL文件,可以使用minerva工具查看,如下图所示,密码明文明文存储的,SOL文件是永久性保存的,除非手动清除,如果玩家在公共环境下登录,就会有盗号威胁。
&也有些开发意识到了这个问题,而采用加密存储方式,一般采用md5(其实md5不是真正的加密算法)。md5解密的在线网站非常多,如下图所示,密码通过两次md5后存储,我们可以在/&查到。
&所以建议存档加密,采用自定义的加密算法,例如md5后转置再md5,等等。
五、帐号安全/充值安全:盗号/低价充值
帐号安全和充值安全不仅页游如此,所有游戏,甚至所有线上应用都如此。如果说开是个大的话题,我仅仅介绍页游中常见的威胁与防御。
(一)、帐号安全
1.外挂盗号
例如下面号称可以无限刷取游戏货币的外挂,实际上在用户输入帐号和密码后,将信息发送给盗号者的邮箱。
2. 社工盗号
在游戏中获取信任,以为对方代练等好处为引诱盗取帐号。或通过获取个人信息,从密保问题下手进行盗号。
3.从游戏的帐号管理中心等web入口下手,进行盗号。
例如登录模块没有验证码或验证码实现机制,使用字典扫描批量盗号
4.传输嗅探盗号
5.利用帐号申诉流程漏洞进行盗号
例如有些申诉打分机制存在提供多次充值证明就可以取回密码的方式,先充值,再盗号。
1.安全意识宣传
2.弱口令检测
3.异地登录提醒
4.登录行为监控
5.设计好帐号相关功能,例如申诉流程
(二)、充值安全
在网上发帖慌称发现充值漏洞,骗取贪心网友给自己指定的帐号进行充值
2.利用手机充值漏洞
使用快过期的手机废卡进行充值,大多数的充值不会再次检测手机卡是否存活状态
3.利用宽带充值漏洞
盗取宽带帐号进行充值,由于不会实际影响游戏收益,顶多会出现受害者损失严重的情况下投诉带来的不好影响。
4. 真正的充值漏洞
比如说广泛采用的点卡充值,可能存在点卡被重放使用的漏洞
1.安全意识宣传
2.充值相关功能的安全检测
------------------------------
总的来说,页游的各种外挂问题很普遍,端游有的它都有,但安全防御不如端游,这很大程度上是因为页游的开发周期短,生存周期也短,例如较长的神仙道游页才两年了,甚至大多数页游,只是为了短时间洗用户抢钱,因此不会投入人力物力在外挂防御方面,或许第三方的安全服务会有点市场吧,比如说他们乐意购买支持多项目的AS混淆工具。总之,页游是个浮躁的市场,只有生命周期强大的游戏,才会注意到外挂问题。
您对本文章有什么意见或着疑问吗?请到您的关注和建议是我们前行的参考和动力&&
您的浏览器不支持嵌入式框架,或者当前配置为不显示嵌入式框架。领略Internet
领略Internet
&&整理编译&&
Internet-全世界计算机透过不同协议交换信息的大型连结体-近几年重新定义了个人计算的几个领域。虽然拨接信息服务和电子邮件系统在Internet流行开来之前就已经存在,但它们通常局限于文字模式,并且根本没有连结而是各自分隔的。例如,每一种信息服务都需要拨不同的电话号码,用不同的使用者ID和密码登录。每一种电子邮件系统仅允许在特定系统的缴款使用者之间发送和接收邮件。
现在,往往只需要拨单一支电话就可以连结整个Internet,而且可以和有电子邮件地址的人进行全球通信。特别是在World
Web上,超文字、图形和多媒体(包括声音、音乐和视讯)的使用已经扩展了在线信息的范围和功能。
如果要提供涵盖Windows中所有与Internet相关程序设计问题的彻底介绍,可能还需要再加上几本书才够。所以,本章实际上主要集中在如何让小型的Microsoft
Windows应用程序能够有效地从Internet上取得信息的两个领域。这两个领域分别是Windows
Sockets (Winsock) API和Windows
Internet(WinInet)API支持的文件传输协议(FTP:File Transfer
Protocol)的部分。Windows
SocketsSocket是由University of
California在Berkeley分校开发的概念,用于在UNIX操作系统上添加网络通讯支持。那里开发的API现在称为「Berkeley
socket interface」。Sockets和TCP/IP
Socket通常(但不专用于)与主宰Internet通信的传输控件协议/因特网协议(TCP/IP:Transmission
Control Protocol/Internet
Protocol)牵连在一起。因特网协定(IP:Internet
Protocol),作为TCP/IP的组成部分之一,用来将数据打包成「数据封包(datagram)」,该资料封包包含用于标识数据来源和目的地的表头信息。而传输控制协议(TCP:Transmission
Protocol)则提供了可靠的传输和检查IP数据封包正确性的方法。
在TCP/IP下,通讯端点由IP地址和端口号定义。IP地址包括4个字节,用于确定Internet上的服务器。IP地址通常按「由点连结的四个小于255的数字」的格式显示,例如「209.86.105.231」。埠号确定了特定的服务或服务器提供的服务。其中一些埠号已经标准化,以提供众所周知的服务。
当Socket与TCP/IP合用时,Socket就是TCP/IP的通讯端点。因此,Socket指定了IP地址和端口号。网络时间服务下面给出的范例程序与提供时间协议(Time
Protocol)的Internet服务器相连结。此程序将获得目前准确的日期和时间,并用此信息设定您的PC时钟。在美国,国家标准和技术协会(National Institute of Standards
and Technology)(以前称为国家标准局(National Bureau of
Standards))负责维护准确时间,该时间与世界各地的机构相联系。准确时间可用于无线电广播、电话号码、计算机拨号电话号码以及Internet,关于这些的所有文件都位于网站(网域名称「bldrdoc」指的是Boulder、Colorado、NIST
Time的位置和Frequency Division)。我们只对NIST Network Time
Service感兴趣,其详细的文件位于。此网页列出了十个提供NIST时间服务的服务器。例如,第一个名称为time-a.timefreq.bldrdoc.gov,其IP地址为132.163.135.130。(我曾经编写过一个使用非Internet
NIST计算机拨接服务的程序,并发表于《PC
Magazine》,您也可以在Ziff-Davis的网站中找到。此程序对于想学习如何使用Windows
Telephony API的人很有帮助。)在Internet上有三个不同的时间服务,每一个都由Request for
Comment(RFC)描述为Internet标准。日期协议(Daytime
Protocol)(RFC-867)提供了一个ASCII字符串用于指出准确的日期和时间。该ASCII字符串的准确格式并不标准,但人们可以理解其中的含义。时间协议(RFC-868)提供了一个32位的数字,用来表示从日至今的秒数。该时间是UTC(不考虑字母顺序,它表示世界时间坐标(Coordinated
Universal Time)),它类似于所谓的格林威治标准时间(Greenwich
Time)或者GMT-英国格林威治时间。第三个协议称为网络时间协议(Network
Time Protocol)(RFC-1305),该协议很复杂。
对于我们的目的,即包括分析Socket和不断更新PC时钟,时间协议RFC-868已经够用了。RFC-868只是一个两页的简短文件,主要是说用TCP获得准确时间的程序应该有如下步骤:
连结到提供此服务的服务器埠37。
接收32位的时间。
关闭连结。
现在我们已经知道了编写存取时间服务的Socket应用程序的每个细节。NETTIME程序Windows Sockets API,通常也称为WinSock,与Berkeley Sockets
API兼容,因此,可以想象UNIX
Socket程序代码可以顺利地拿到Windows上使用。Windows下更进一步的支持由对Berkeley
Socket扩充的功能提供,其函数的形式是以WSA(「WinSock
API」)为前缀。相关的概述和参考位于/Platform SDK/Networking and
Distributed Services/Windows Sockets Version 2。NETTIME,如程序23-1所示,展示了使用WinSock API的方法。
/*----------------------------------------------------------------------------
NETTIME.C -- Sets System Clock from Internet Services
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include &windows.h&
#include &resource.h&
WM_SOCKET_NOTIFY (WM_USER + 1)
CALLBACK WndProc
(HWND, UINT, WPARAM, LPARAM) ;
CALLBACK MainDlg
(HWND, UINT, WPARAM, LPARAM) ;
CALLBACK ServerDlg
(HWND, UINT, WPARAM, LPARAM) ;
ChangeSystemTime
(HWND hwndEdit, ULONG ulTime) ;
FormatUpdatedTime
(HWND hwndEdit, SYSTEMTIME * pstOld,
SYSTEMTIME * pstNew) ;
void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...) ;
HINSTANCE hI
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT (&NetTime&) ;
hInst = hI
wndclass.style
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
wndclass.hbrBackground
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT (&Set System Clock from Internet&),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
WS_BORDER | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
// Create the modeless dialog box to go on top of the window
hwndModeless = CreateDialog (hInstance, szAppName, hwnd, MainDlg) ;
// Size the main parent window to the size of the dialog box.
Show both windows.
GetWindowRect (hwndModeless, &rect) ;
AdjustWindowRect (&rect, WS_CAPTION | WS_BORDER, FALSE) ;
SetWindowPos (hwnd, NULL, 0, 0, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOMOVE) ;
ShowWindow (hwndModeless, SW_SHOW) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
// Normal message loop when a modeless dialog box is used.
while (GetMessage (&msg, NULL, 0, 0))
if (hwndModeless == 0 || !IsDialogMessage (hwndModeless, &msg))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
switch (message)
WM_SETFOCUS:
SetFocus (hwndModeless) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
BOOL CALLBACK MainDlg (
HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam)
static char
szIPAddr[32] = { &132.163.135.130& } ;
static HWND
hwndButton, hwndE
static SOCKET
static struct
static TCHAR
szOKLabel[32] ;
iError, iS
unsigned long
wEvent, wE
switch (message)
WM_INITDIALOG:
hwndButton = GetDlgItem (hwnd, IDOK) ;
hwndEdit = GetDlgItem (hwnd, IDC_TEXTOUT) ;
return TRUE ;
WM_COMMAND:
switch (LOWORD (wParam))
IDC_SERVER:
DialogBoxParam (hInst, TEXT (&Servers&), hwnd, ServerDlg, (LPARAM) szIPAddr) ;
return TRUE ;
// Call &WSAStartup& and display description text
if (iError = WSAStartup (MAKEWORD(2,0), &WSAData))
EditPrintf (hwndEdit, TEXT (&Startup error #%i.\r\n&), iError) ;
return TRUE ;
EditPrintf (hwndEdit, TEXT (&Started up %hs\r\n&),
WSAData.szDescription);
// Call &socket&
sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
if (sock == INVALID_SOCKET)
EditPrintf (hwndEdit, TEXT (&Socket creation error #%i.\r\n&), WSAGetLastError ()) ;
WSACleanup () ;
return TRUE ;
EditPrintf (hwndEdit, TEXT (&Socket %i created.\r\n&), sock) ;
// Call &WSAAsyncSelect&
if (SOCKET_ERROR == WSAAsyncSelect (sock, hwnd, WM_SOCKET_NOTIFY, FD_CONNECT | FD_READ))
EditPrintf ( hwndEdit, TEXT (&WSAAsyncSelect error #%i.\r\n&), WSAGetLastError ()) ;
closesocket (sock) ;
WSACleanup () ;
return TRUE ;
// Call &connect& with IP address and time-server port
sa.sin_family
= AF_INET ;
sa.sin_port
= htons (IPPORT_TIMESERVER) ;
sa.sin_addr.S_un.S_addr = inet_addr (szIPAddr) ;
connect(sock, (SOCKADDR *) &sa, sizeof (sa)) ;
// &connect& will return SOCKET_ERROR because even if it
// succeeds, it will require blocking. The following only
// reports unexpected errors.
if (WSAEWOULDBLOCK != (iError = WSAGetLastError ()))
EditPrintf (hwndEdit, TEXT (&Connect error #%i.\r\n&), iError) ;
closesocket (sock) ;
WSACleanup () ;
return TRUE ;
EditPrintf (hwndEdit, TEXT (&Connecting to %hs...&), szIPAddr) ;
// The result of the &connect& call will be reported
// through the WM_SOCKET_NOTIFY message.
// Set timer and change the button to &Cancel&
SetTimer (hwnd, ID_TIMER, 1000, NULL) ;
GetWindowText (hwndButton, szOKLabel, sizeof (szOKLabel) /sizeof (TCHAR)) ;
SetWindowText (hwndButton, TEXT (&Cancel&)) ;
SetWindowLong (hwndButton, GWL_ID, IDCANCEL) ;
return TRUE ;
closesocket (sock) ;
sock = 0 ;
WSACleanup () ;
SetWindowText (hwndButton, szOKLabel) ;
SetWindowLong (hwndButton, GWL_ID, IDOK) ;
KillTimer (hwnd, ID_TIMER) ;
EditPrintf (hwndEdit, TEXT (&\r\nSocket closed.\r\n&)) ;
return TRUE ;
IDC_CLOSE:
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
DestroyWindow (GetParent (hwnd)) ;
return TRUE ;
return FALSE ;
EditPrintf (hwndEdit, TEXT (&.&)) ;
return TRUE ;
WM_SOCKET_NOTIFY:
wEvent = WSAGETSELECTEVENT (lParam) ;
// ie, LOWORD
wError = WSAGETSELECTERROR (lParam) ;
// ie, HIWORD
// Process two events specified in WSAAsyncSelect
switch (wEvent)
// This event occurs as a result of the &connect& call
FD_CONNECT:
EditPrintf (hwndEdit, TEXT (&\r\n&)) ;
if (wError)
EditPrintf (hwndEdit, TEXT (&Connect error #%i.&), wError) ;
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
return TRUE ;
EditPrintf (hwndEdit, TEXT (&Connected to %hs.\r\n&), szIPAddr) ;
// Try to receive data. The call will generate an error
// of WSAEWOULDBLOCK and an event of FD_READ
recv (sock, (char *) &ulTime, 4, MSG_PEEK) ;
EditPrintf (hwndEdit, TEXT (&Waiting to receive...&)) ;
return TRUE ;
// This even occurs when the &recv& call can be made
KillTimer (hwnd, ID_TIMER) ;
EditPrintf (hwndEdit, TEXT (&\r\n&)) ;
if (wError)
EditPrintf (hwndEdit, TEXT (&FD_READ error #%i.&), wError) ;
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
return TRUE ;
// Get the time and swap the bytes
iSize = recv (sock, (char *) &ulTime, 4, 0) ;
ulTime = ntohl (ulTime) ;
EditPrintf (hwndEdit,
TEXT (&Received current time of %u seconds &)
TEXT (&since Jan. 1 1900.\r\n&), ulTime) ;
// Change the system time
ChangeSystemTime (hwndEdit, ulTime) ;
SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
return TRUE ;
return FALSE ;
return FALSE ;
BOOL CALLBACK ServerDlg (
HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static char * szS
static WORD
wServer = IDC_SERVER1 ;
szLabel [64] ;
switch (message)
WM_INITDIALOG:
szServer = (char *) lP
CheckRadioButton (hwnd, IDC_SERVER1, IDC_SERVER10, wServer) ;
return TRUE ;
WM_COMMAND:
switch (LOWORD (wParam))
IDC_SERVER1:
IDC_SERVER2:
IDC_SERVER3:
IDC_SERVER4:
IDC_SERVER5:
IDC_SERVER6:
IDC_SERVER7:
IDC_SERVER8:
IDC_SERVER9:
IDC_SERVER10:
wServer = LOWORD (wParam) ;
return TRUE ;
GetDlgItemTextA (hwnd, wServer, szLabel, sizeof (szLabel)) ;
strtok (szLabel, &(&) ;
strcpy (szServer, strtok (NULL, &)&)) ;
EndDialog (hwnd, TRUE) ;
return TRUE ;
EndDialog (hwnd, FALSE) ;
return TRUE ;
return FALSE ;
void ChangeSystemTime (HWND hwndEdit, ULONG ulTime)
LARGE_INTEGER
SYSTEMTIME
stOld, stN
GetLocalTime (&stOld) ;
stNew.wYear
stNew.wMonth
stNew.wDay
stNew.wHour
stNew.wMinute
stNew.wSecond
stNew.wMilliseconds
SystemTimeToFileTime (&stNew, &ftNew) ;
li = * (LARGE_INTEGER *) &ftN
li.QuadPart += (LONGLONG)
ftNew = * (FILETIME *) &
FileTimeToSystemTime (&ftNew, &stNew) ;
if (SetSystemTime (&stNew))
GetLocalTime (&stNew) ;
FormatUpdatedTime (hwndEdit, &stOld, &stNew) ;
EditPrintf (hwndEdit, TEXT (&Could NOT set new date and time.&)) ;
void FormatUpdatedTime (
HWND hwndEdit, SYSTEMTIME * pstOld, SYSTEMTIME * pstNew)
TCHAR szDateOld [64], szTimeOld [64], szDateNew [64], szTimeNew [64] ;
GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
pstOld, NULL, szDateOld, sizeof (szDateOld)) ;
GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE |
TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
pstOld, NULL, szTimeOld, sizeof (szTimeOld)) ;
GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
pstNew, NULL, szDateNew, sizeof (szDateNew)) ;
GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE |
TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
pstNew, NULL, szTimeNew, sizeof (szTimeNew)) ;
EditPrintf (hwndEdit, TEXT (&System date and time successfully changed &)
TEXT (&from\r\n\t%s, %s.%03i to\r\n\t%s, %s.%03i.&),
szDateOld, szTimeOld, pstOld-&wMilliseconds,
szDateNew, szTimeNew, pstNew-&wMilliseconds) ;
void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...)
szBuffer [1024] ;
va_listpArgL
va_start (pArgList, szFormat) ;
wvsprintf (szBuffer, szFormat, pArgList) ;
va_end (pArgList) ;
SendMessage (hwndEdit, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
SendMessage (hwndEdit, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
SendMessage (hwn dEdit, EM_SCROLLCARET, 0, 0) ;
NETTIME.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include &resource.h&
#include &afxres.h&
/////////////////////////////////////////////////////////////////////////////
SERVERS DIALOG DISCARDABLE
20, 20, 274, 202
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION &NIST Time Service Servers&
FONT 8, &MS Sans Serif&
DEFPUSHBUTTON
&OK&,IDOK,73,181,50,14
PUSHBUTTON
&Cancel&,IDCANCEL,150,181,50,14
&time-a.timefreq.bldrdoc.gov (132.163.135.130) NIST, Boulder, Colorado&,
IDC_SERVER1,&Button&,BS_AUTORADIOBUTTON,9,7,256,16
&time-b.timefreq.bldrdoc.gov (132.163.135.131) NIST, Boulder, Colorado&,
IDC_SERVER2,&Button&,BS_AUTORADIOBUTTON,9,24,256,16
&time-c.timefreq.bldrdoc.gov (132.163.135.132) Boulder, Colorado&,
IDC_SERVER3,&Button&,BS_AUTORADIOBUTTON,9,41,256,16
&utcnist.colorado.edu (128.138.140.44) University of Colorado, Boulder&,
IDC_SERVER4,&Button&,BS_AUTORADIOBUTTON,9,58,256,16
&time.nist.gov (192.43.244.18) NCAR, Boulder, Colorado&,
IDC_SERVER5,&Button&,BS_AUTORADIOBUTTON,9,75,256,16
&time-a.nist.gov (129.6.16.35) NIST, Gaithersburg, Maryland&,
IDC_SERVER6,&Button&,BS_AUTORADIOBUTTON,9,92,256,16
&time-b.nist.gov (129.6.16.36) NIST, Gaithersburg, Maryland&,
IDC_SERVER7,&Button&,BS_AUTORADIOBUTTON,9,109,256,16
&time-nw.nist.gov (131.107.1.10) Microsoft, Redmond, Washington&,
IDC_SERVER8,&Button&,BS_AUTORADIOBUTTON,9,126,256,16
&utcnist.reston.mci.net (204.70.131.13) MCI, Reston, Virginia&,
IDC_SERVER9,&Button&,BS_AUTORADIOBUTTON,9,143,256,16
& (209.0.72.7) Datum, San Jose, California&,
IDC_SERVER10,&Button&,BS_AUTORADIOBUTTON,9,160,256,16
NETTIME DIALOG DISCARDABLE
0, 0, 270, 150 STYLE WS_CHILD FONT 8, &MS Sans Serif&
DEFPUSHBUTTON
&Set Correct Time&,IDOK,95,129,80,14
PUSHBUTTON
&Close&,IDC_CLOSE,183,129,80,14
PUSHBUTTON
&Select Server...&,IDC_SERVER,7,129,80,14
IDC_TEXTOUT,7,7,253,110,ES_MULTILINE | ES_AUTOVSCROLL |
ES_READONLY | WS_VSCROLL | NOT WS_TABSTOP
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by NetTime.rc
IDC_TEXTOUT
IDC_SERVER1
IDC_SERVER2
IDC_SERVER3
IDC_SERVER4
IDC_SERVER5
IDC_SERVER6
IDC_SERVER7
IDC_SERVER8
IDC_SERVER9
IDC_SERVER10
IDC_SERVER
在结构上,NETTIME程序建立了一个依据NETTIME.RC中的NETTIME所建立的非系统模态对话框。程序重新定义了窗口的尺寸,以便非系统模态对话框可以覆盖程序的整个窗口显示区域。对话框包括一个只读编辑区(程序用于写入文字信息)、一个「Select
Server」按钮、一个「Set Correct
Time」按钮和一个「Close」按钮。「Close」按钮用于终止程序。
MainDlg中的szIPAddr变量用于储存服务器地址,内定是字符串「132.163.135.130」。「Select
Server」按钮启动依据NETTIME.RC中的SERVERS模板建立的对话框。szIPAddr变量作为最后一个参数传递给DialogBoxParam。「Server」对话框列出了10个服务器(都是从NIST网站上逐字复制来的),这些服务器提供了我们感兴趣的服务。当使用者单击一个服务器时,ServerDlg将分析按钮文字,以获得相应的IP地址。新地址储存在szIPAddr变量中。当使用者按下「Set Correct
Time」按钮时,按钮将产生一个WM_COMMAND消息,其中wParam的低字组等于IDOK。MainDlg中的IDOK处理是大部分Socket初始行为发生的地方。使用Windows Sockets
API时,任何Windows程序必须呼叫的第一个函数是:
iError = WSAStartup (wVersion, &WSAData) ;
NETTIME将第一个参数设定为0x0200(表示2.0版本)。传回时,WSAData结构包含了Windows
Sockets实作的相关信息,而且NETTIME将显示szDescription字符串,并简要提供了一些版本信息。然后,NETTIME如下呼叫socket函数:
sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
第一个参数是一个地址种类,表示此处是某种Internet地址。第二个参数表示数据以数据流的形式传回,而不是以数据封包的形式传回(我们需要的数据只有4个字节长,而数据封包适用于较大的数据块)。最后一个参数是一个协议,我们指定使用的Internet协议是TCP。它是RFC-868所定义的两个协议之一。socket函数的传回值储存在SOCKET型态的变量中,以便后面的Socket函数的呼叫。
NETTIME下面呼叫的WSAAsynchSelect是另一个Windows特有的Socket函数。此函数用于避免因Internet响应过慢而造成应用程序当住。在WinSock文件中,有些函数与「阻碍性(blocking)」有关。也就是说,它们不能保证立即把控件权传回给程序。WSAAsyncSelect函数强制阻碍性的函数转为非阻碍性的,即在函数执行完之前把控件传回给程序。函数的结果以消息的形式报告给应用程序。WSAAsyncSelect函数让应用程序指定消息和接收消息的窗口的数值。通常,函数的语法如下:
WSAAsyncSelect (sock, hwnd, message, iConditions) ;
为此任务,NETTIME使用程序定义的一个消息,该消息称为WM_SOCKET_NOTIFY。它也用WSAAsyncSelect的最后一个参数来指定消息发送的条件,特别在连结和接收资料时(FD_CONNECT
| FD_READ)。
NETTIME呼叫的下一个WinSock函数是connect。此函数需要一个指向Socket地址结构的指针,对于不同的协议来说,此Socket地址结构是不同的。NETTIME使用为TCP/IP设计的结构版本:
struct sockaddr_in
struct in_addr sin_
sin_zero[8];
其中in_addr是用于指定Internet地址,它可以用4个字节,或者2个无正负号短整数,或者1个无正负号长整数来表示。
NETTIME将sin_family字段设定为AF_INET,用于表示地址种类。将sin_port设定为埠号,这里是时间协议的埠号,RFC-868显示为37。但不要像我最初时那样,将此字段设为37。当大多数数字通过Internet时,结构的这个端口号字段必须是「big
endian」的,即最高的字节排第一个。Intel微处理器是little
endian。幸运的是,htons(「host-to-network
short」)函数使字节翻转,因此NETTIME将sockaddr_in结构的sin_port字段设定为:
htons (IPPORT_TIMESERVER)
WINSOCK2.H中将常数定义为37。NETTIME用inet_addr函数将储存在szIPAddr字符串中的服务器地址转化为无正负号长整数,该整数用于设定结构的sin_addr字段。如果应用程序在Windows
98下呼叫connect,而且目前Windows没有连结到Internet,那么将显示「拨号联机」对话框。这就是所谓的「自动拨号」。在Windows
4.0中没有实作「自动拨号」,因此如果在NT环境下执行,那么在执行NETTIME之前,就必须先连结上Internet。
connect函数通常已经会阻碍着后面程序的执行,这是因为连结成功以前需要花些时间。然而,由于NETTIME呼叫了WSAAsyncSelect,所以connect不会等待连结,事实上,它会立即传回SOCKET_ERROR的值。这并不是出现了错误,这只是表示现在还没有联机成功而已。NETTIME也不会检查这个传回值,只是呼叫WSAGetLastError而已。如果WSAGetLastError传回WSAEWOULDBLOCK(即函数的执行通常要受阻,但这里并没有受阻),那就一切都还很正常。NETTIME将「Set
Time」按钮改成「Cancel」,并设定了一个1秒的定时器。WM_TIMER的处理方式只是在程序窗口中显示句点,以告诉使用者程序仍在执行,系统没有当掉。
连结最终完成时,MainDlg由WM_SOCKET_NOTIFY消息-NETTIME在WSAAsyncSelect函数中指定的程序自订消息所通知。lParam的低字组等于FD_CONNECT,高字组表示错误。这时的错误可能是程序不能连结到指定的服务器。NETTIME还列出了其它9个服务器,供您选择,让您可以试试其它的服务器。
如果一切顺利,那么NETTIME将呼叫recv(「receive:接收」)函数来读取数据:
recv (sock, (char *) &ulTime, 4, MSG_PEEK) ;
这意味着,用4个字节来储存ulTime变量。最后一个参数表示只是读此数据,并不将其从输入队列中删除。像connect函数一样,recv传回一个错误代码,以表示函数通常受阻,但这时没有受阻。理论上来说(当然这不大可能),函数至少能传回数据的一部分,然后透过再次呼叫以获得其余的32个字节值。那就是呼叫recv函数时带有MSG_PEEK选项的原因。
与connect函数类似,recv函数也产生WM_SOCKET_NOTIFY消息,这时带有FD_READ的事件代码。NETTIME通过再次呼叫recv来对此响应,这时最后的参数是0,用于从队列中删除数据。我将简要讨论一下程序处理接收到的ulTime的方法。注意,NETTIME通过向自己发送WM_COMMAND消息来结束处理,该消息中wParam等于IDCANCEL。对话框程序通过呼叫closesocket和WSACleanup来响应。再次呼叫NETTIME接收的32位的ulTime值是从日开始的0:00
UTC秒数。但最高顺序的字节是第一个字节,因此该值必须通过ntohl(「network-to-host
long」)函数处理来调整字节顺序,以便Intel微处理器能够处理。然后,NETTIME呼叫ChangeSystemTime函数。
ChangeSystemTime首先取得目前的本地时间-即,使用者所在时区和日光节约时间的目前系统时间。将SYSTEMTIME结构设定为日午夜(0时)。并将这个SYSTEMTIME结构传递给SystemTimeToFileTime,将此结构转化为FILETIME结构。FILETIME实际上只是由两个32位的DWORD一起组成64位的整数,用来表示从日至今间隔为100奈秒(nanosecond)的间隔数。
ChangeSystemTime函数将FILETIME结构转化为LARGE_INTEGER。它是一个union,允许64位的值可以被当成两个32位的值使用,或者当成一个__int64数据型态的64位整数使用(__int64数据型态是Microsoft编译器对ANSI
C标准的扩充)。因此,此值是日到日之间间隔为100奈秒的间隔数。这里,添加了日至今间隔为100奈秒的间隔数-ulTime的10,000,000倍。
然后通过呼叫FileTimeToSystemTime将作为结果的FILETIME值转换回SYSTEMTIME结构。因为时间协议传回目前的UTC时间,所以NETTIME通过呼叫SetSystemTime来设定时间,SetSystemTime也依据UTC。基于显示的目的,程序呼叫GetLocalTime来获得更新时间。最初的本地时间和新的本地时间一起传递给FormatUpdatedTime,这个函数用GetTimeFormat函数和GetDateFormat函数将时间转化为ASCII字符串。如果程序在Windows
NT下执行,并且使用者没有取得设定时间的权限,那么SetSystemTime函数可能失败。如果SetSystemTime失败,则NETTIME将发出一个新时间未设定成功的消息来指出问题所在。WinInet 和 FTPWinInet(「Windows
Internet」)API是一个高阶函数集,帮助程序写作者使用三个常见的Internet协议,这三个协议是:用于World
Wide Web全球信息网的超文字传输协议(HTTP:Hypertext Transfer
Protocol)、文件传输协议(FTP:File Transfer
Protocol)和另一个称为Gopher的文件传输协议。WinInet函数的语法与常用的Windows文件函数的语法类似,这使得使用这些协议就像使用本地磁盘驱动器上的文件一样容易。WinInet
API的文件位于/Platform SDK/Internet, Intranet, Extranet
Services/Internet Tools and Technologies/WinInet API。下面的范例程序将展示如何使用WinInet
API的FTP部分。许多有网站的公司也都有「匿名FTP」服务器,这样使用者可以在不输入使用者名称和密码的情况下下载文件。例如,如果您在Internet
Explorer的地址栏输入,那么您就可以浏览FTP服务器上的目录并下载文件。如果进入//ProgWin/UpdDemo,那么您将在我的匿名FTP服务器上发现与待会要提到的范例程序一块使用的文件列表。
虽然现今FTP服务对大多数的Web使用者来说并不是那么方便使用,但它仍然相当有用。例如,应用程序能利用FTP从匿名FTP服务器上取得数据,这些取得数据的运作程序几乎完全在台面下处理,而不需要使用者操心。这就是我们将讨论的UPDDEMO(「update
demonstration:更新范例」)程序的构想。FTP API概况
使用WinInet的程序必须在所有呼叫WinInet函数的源文件中包括表头文件WININET.H。程序还必须连结WININET.LIB。在Microsoft
Visual C++中,您可以在「Project
Settings」对话框的「Link」页面卷标中指定。执行时,程序将和WININET.DLL动态链接库连结。
在下面的论述中,我不会详细讨论函数的语法,因为某些函数有很多选项,这让它变得相当复杂。要掌握WinInet,您可以将UPDDEMO原始码当成食谱来看待。这时最重要的是了解有关的各个步骤以及FTP函数的范围。要使用Windows Internet
API,首先要呼叫InternetOpen。然后,使用WinInet支持的任何一种协议。InternetOpen给您一个Internet作业句柄,并储存到HINTERNET型态的变量中。用完WinInet
API以后,应该通过呼叫InternetCloseHandle来关闭句柄。
要使用FTP,您接下来就要呼叫InternetConnect。此函数需要使用由InternetOpen建立Internet作业句柄,并且传回FTP作业的句柄。您可将此句柄作为名称开头为Ftp的所有函数的第一个参数。InternetConnect函数的参数指出要使用的FTP,还提供了服务器名称,例如,。此函数还需要使用者名称和密码。如果存取匿名FTP服务器,这些参数可以设定为NULL。如果应用程序呼叫InternetConnect时PC并没有连结到Internet,Windows
98将显示「拨号联机」对话框。当使用FTP的应用程序结束时,呼叫InternetCloseHandle来关闭句柄。
这时可以开始呼叫有Ftp前缀的函数。您将发现这些函数与标准的Windows文件I/O函数很相似。为了避免与其它协议重复,一些以Internet为前缀的函数也可以处理FTP。下面四个函数用于处理目录:
fSuccess = FtpCreateDirectory
(hFtpSession, szDirectory) ;
fSuccess = FtpRemoveDirectory
(hFtpSession, szDirectory) ;
fSuccess = FtpSetCurrentDirectory (hFtpSession, szDirectory) ;
fSuccess = FtpGetCurrentDirectory (hFtpSession, szDirectory, &dwCharacterCount) ;
注意,这些函数很像我们所熟悉的Windows提供用于处理本地文件系统的CreateDirectory、RemoveDirectory、SetCurrentDirectory和GetCurrentDirectory函数。
当然,存取匿名FTP的应用程序不能建立或删除目录。而且,程序也不能假定FTP目录具有和Windows文件系统相同的目录结构型态。特别是用相对路径名设定目录的程序,不能假定关于新的目录全名的一切。如果程序需要知道最后所在目录的整个名称,那么呼叫了SetCurrentDirectory之后必须再呼叫GetCurrentDirectory。GetCurrentDirectory的字符串参数至少包含MAX_PATH字符,并且最后一个参数应指向包含该值的变量。
下面两个函数让您删除或者重新命名文件(但不是在匿名FTP服务器上):
fSuccess = FtpDeleteFile (hFtpSession, szFileName) ;
fSuccess = FtpRenameFile (hFtpSession, szOldName, szNewName) ;
经由先呼叫FtpFindFirstFile,可以查找文件(或与含有万用字符的文件名样式相符的多个文件)。此函数很像FindFirstFile函数,甚至都使用了相同的WIN32_FIND_DATA结构。该文件为列举出来的文件传回了一个句柄。您可以将此句柄传递给InternetFindNextFile函数以获得额外的文件名称信息。最后通过呼叫InternetCloseHandle来关闭句柄。
要打开文件,可以呼叫FtpFileOpen。这个函数传回一个文件句柄,此句柄可以用于InternetReadFile、InternetReadFileEx、InternetWrite和InternetSetFilePointer呼叫。最后可以通过呼叫最常用的InternetCloseHandle函数来关闭句柄。
最后,下面两个高级函数特别有用:FtpGetFile呼叫将文件从FTP服务器复制到本地内存,它合并了FtpFileOpen、FileCreate、InternetReadFile、WriteFile、InternetCloseHandle和CloseHandle呼叫。FtpGetFile的另一个参数是一个旗标,如果本地已经存在同名文件,那么该旗标将导致函数呼叫失败。FtpPutFile与此函数类似,用于将文件从本地内存复制到FTP服务器。更新展示程序UPDDEMO,如程序23-2所示,展示了用WinInet
FTP函数在第二个线程执行期间从匿名FTP服务器上下载文件的方法。
/*---------------------------------------------------------------------------
UPDDEMO.C -- Demonstrates Anonymous FTP Access
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
&windows.h&
&wininet.h&
&process.h&
&resource.h&
// User-defined messages used in WndProc
WM_USER_CHECKFILES
(WM_USER + 1)
WM_USER_GETFILES
(WM_USER + 2)
// Information for FTP download
(&/ProgWin/UpdDemo&)
(&UD??????.TXT&)
// Structures used for storing filenames and contents
typedef struct
TCHAR * szF
FILEINFO ;
typedef struct
FILELIST ;
// Structure used for second thread
typedef struct
// Declarations of all functions in program
WndProc (HWND, UINT, WPARAM, LPARAM) ;
DlgProc (HWND, UINT, WPARAM, LPARAM) ;
FtpThread (PVOID) ;
ButtonSwitch (HWND, HWND, TCHAR *) ;
FILELIST *
GetFileList (VOID) ;
Compare (const FILEINFO *, const FILEINFO *) ;
// A couple globals
HINSTANCE hI
szAppName[] = TEXT (&UpdDemo&) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
hInst = hI
wndclass.style
wndclass.lpfnWndProc
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
wndclass.hbrBackground
= GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox (
NULL, TEXT (&This program requires Windows NT!&),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT (&Update Demo with Anonymous FTP&),
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
// After window is displayed, check if the latest file exists
SendMessage (hwnd, WM_USER_CHECKFILES, 0, 0) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wP
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static FILELIST *
static int
cxClient, cyClient, cxChar, cyC
PAINTSTRUCT
SCROLLINFO
SYSTEMTIME
szFilename [MAX_PATH] ;
switch (message)
WM_CREATE:
cxChar = LOWORD (GetDialogBaseUnits ()) ;
cyChar = HIWORD (GetDialogBaseUnits ()) ;
return 0 ;
= LOWORD (lParam) ;
= HIWORD (lParam) ;
= sizeof (SCROLLINFO) ;
= SIF_RANGE | SIF_PAGE ;
= plist ? plist-&iNum - 1 : 0 ;
= cyClient / cyC
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
return 0 ;
WM_VSCROLL:
= sizeof (SCROLLINFO) ;
= SIF_POS | SIF_RANGE | SIF_PAGE ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
switch (LOWORD (wParam))
case SB_LINEDOWN: si.nPos += 1 ;
case SB_LINEUP:
si.nPos -= 1 ;
case SB_PAGEDOWN: si.nPos += si.nP
case SB_PAGEUP:
si.nPos -= si.nP
case SB_THUMBPOSITION: si.nPos = HIWORD (wParam) ;
return 0 ;
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
WM_USER_CHECKFILES:
// Get the system date & form filename from year and month
GetSystemTime (&st) ;
wsprintf (szFilename, TEXT (&UD%04i%02i.TXT&), st.wYear, st.wMonth) ;
// Chec if so, read all the files
if (GetFileAttributes (szFilename) != (DWORD) -1)
SendMessage (hwnd, WM_USER_GETFILES, 0, 0) ;
return 0 ;
// Otherwise, get files from Internet.
// But first check so we don't try to copy files to a CD-ROM!
if (GetDriveType (NULL) == DRIVE_CDROM)
MessageBox (hwnd, TEXT (&Cannot run this program from CD-ROM!&),
szAppName, MB_OK | MB_ICONEXCLAMATION) ;
return 0 ;
// Ask user if an Internet connection is desired
if (IDYES == MessageBox (hwnd, TEXT (&Update information from Internet?&),
szAppName, MB_YESNO | MB_ICONQUESTION))
// Invoke dialog box
DialogBox (hInst, szAppName, hwnd, DlgProc) ;
// Update display
SendMessage (hwnd, WM_USER_GETFILES, 0, 0) ;
return 0 ;
WM_USER_GETFILES:
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
// Read in all the disk files
plist = GetFileList () ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
// Simulate a WM_SIZE message to alter scroll bar & repaint
SendMessage (hwnd, WM_SIZE, 0, MAKELONG (cxClient, cyClient)) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
SetTextAlign (hdc, TA_UPDATECP) ;
si.cbSize = sizeof (SCROLLINFO) ;
si.fMask = SIF_POS ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
if (plist)
for (i = 0 ; i & plist-&iN i++)
(hdc, cxChar, (i - si.nPos) * cyChar, NULL) ;
(hdc, 0, 0, plist-&info[i].szFilename,
lstrlen (plist-&info[i].szFilename)) ;
(hdc, 0, 0, TEXT (&: &), 2) ;
TextOutA (hdc, 0, 0, plist-&info[i].szContents,
strlen (plist-&info[i].szContents)) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
BOOL CALLBACK DlgProc (
HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static PARAMS
switch (message)
WM_INITDIALOG:
params.bContinue = TRUE ;
params.hwnd =
_beginthread (FtpThread, 0, ms) ;
return TRUE ;
WM_COMMAND:
switch (LOWORD (wParam))
// button for user to abort download
params.bContinue = FALSE ;
return TRUE ;
// button to make dialog box go away
EndDialog (hwnd, 0) ;
return TRUE ;
return FALSE ;
/*---------------------------------------------------------------------------
FtpThread: Reads files from FTP server and copies them to local disk
-----------------------------------------------------------------------------*/
void FtpThread (PVOID parg)
hIntSession, hFtpSession, hF
hwndStatus, hwndB
szBuffer [64] ;
WIN32_FIND_DATA
hwndStatus
= GetDlgItem (pparams-&hwnd, IDC_STATUS) ;
hwndButton
= GetDlgItem (pparams-&hwnd, IDCANCEL) ;
// Open an internet session
hIntSession = InternetOpen (szAppName, INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL, INTERNET_FLAG_ASYNC) ;
if (hIntSession == NULL)
wsprintf (szBuffer, TEXT (&InternetOpen error %i&), GetLastError ()) ;
ButtonSwitch (hwndStatus, hwndButton, szBuffer) ;
_endthread () ;
SetWindowText (hwndStatus, TEXT (&Internet session opened...&)) ;
// Check if user has pressed Cancel
if (!pparams-&bContinue)
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
_endthread () ;
// Open an FTP session.
hFtpSession = InternetConnect (hIntSession, FTPSERVER, INTERNET_DEFAULT_FTP_PORT,
NULL, NULL, INTERNET_SERVICE_FTP, 0, 0) ;
if (hFtpSession == NULL)
InternetCloseHandle (hIntSession) ;
wsprintf (szBuffer, TEXT (&InternetConnect error %i&),
GetLastError ()) ;
ButtonSwitch (hwndStatus, hwndButton, szBuffer) ;
_endthread () ;
SetWindowText (hwndStatus, TEXT (&FTP Session opened...&)) ;
// Check if user has pressed Cancel
if (!pparams-&bContinue)
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
_endthread () ;
// Set the directory
bSuccess = FtpSetCurrentDirectory (hFtpSession, DIRECTORY) ;
if (!bSuccess)
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
wsprintf (
szBuffer, TEXT (&Cannot set directory to %s&),
DIRECTORY) ;
ButtonSwitch (hwndStatus, hwndButton, szBuffer) ;
_endthread () ;
SetWindowText (hwndStatus, TEXT (&Directory found...&)) ;
// Check if user has pressed Cancel
if (!pparams-&bContinue)
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
_endthread () ;
// Get the first file fitting the template
hFind = FtpFindFirstFile (hFtpSession, TEMPLATE, &finddata, 0, 0) ;
if (hFind == NULL)
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, TEXT (&Cannot find files&)) ;
_endthread () ;
// Check if user has pressed Cancel
if (!pparams-&bContinue)
InternetCloseHandle (hFind) ;
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
_endthread () ;
// Copy file from internet to local hard disk, but fail
// if the file already exists locally
wsprintf (szBuffer, TEXT (&Reading file %s...&), finddata.cFileName) ;
SetWindowText (hwndStatus, szBuffer) ;
FtpGetFile (
hFtpSession,
finddata.cFileName, finddata.cFileName, TRUE,
FILE_ATTRIBUTE_NORMAL, FTP_TRANSFER_TYPE_BINARY, 0) ;
while (InternetFindNextFile (hFind, &finddata)) ;
InternetCloseHandle (hFind) ;
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, TEXT (&Internet Download Complete&));
/*----------------------------------------------------------------------------
ButtonSwitch:
Displays final status message and changes Cancel to OK
-------------------------------------------------------------------------*/
VOID ButtonSwitch (HWND hwndStatus, HWND hwndButton, TCHAR * szText)
if (szText)
SetWindowText (hwndStatus, szText) ;
SetWindowText (hwndStatus, TEXT (&Internet Session Cancelled&)) ;
SetWindowText (hwndButton, TEXT (&OK&)) ;
SetWindowLong (hwndButton, GWL_ID, IDOK) ;
/*---------------------------------------------------------------------------
GetFileList: Reads files from disk and saves their names and contents
-----------------------------------------------------------------------------*/
FILELIST * GetFileList (void)
WIN32_FIND_DATA
hFind = FindFirstFile (TEMPLATE, &finddata) ;
if (hFind == INVALID_HANDLE_VALUE)
return NULL ;
// Open the file and get the size
hFile = CreateFile (finddata.cFileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL) ;
if (hFile == INVALID_HANDLE_VALUE)
iSize = GetFileSize (hFile, NULL) ;
if (iSize == (DWORD) -1)
CloseHandle (hFile) ;
// Realloc the FILELIST structure for a new entry
plist = realloc (plist, sizeof (FILELIST) + iNum * sizeof (FILEINFO));
// Allocate space and save the filename
plist-&info[iNum].szFilename = malloc (lstrlen (finddata.cFileName) +sizeof (TCHAR)) ;
lstrcpy (plist-&info[iNum].szFilename, finddata.cFileName) ;
// Allocate space and save the contents
plist-&info[iNum].szContents = malloc (iSize + 1) ;
ReadFile (hFile, plist-&info[iNum].szContents, iSize, &dwRead, NULL);
plist-&info[iNum].szContents[iSize] = 0 ;
CloseHandle (hFile) ;
while (FindNextFile (hFind, &finddata)) ;
FindClose (hFind) ;
// Sort the files by filename
qsort (plist-&info, iNum, sizeof (FILEINFO), Compare) ;
plist-&iNum = iN
/*---------------------------------------------------------------------------
Compare function for qsort
----------------------------------------------------------------------------*/
int Compare (const FILEINFO * pinfo1, const FILEINFO * pinfo2)
return lstrcmp (pinfo2-&szFilename, pinfo1-&szFilename) ;
UPDDEMO.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include &resource.h&
#include &afxres.h&
/////////////////////////////////////////////////////////////////////////////
UPDDEMO DIALOG DISCARDABLE
20, 20, 186, 95
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION &Internet Download&
FONT 8, &MS Sans Serif&
PUSHBUTTON
Cancel&,IDCANCEL,69,74,50,14
&&,IDC_STATUS,7,29,172,21
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by UpdDemo.rc
#define IDC_STATUS
UPDDEMO使用的文件名称是UDyyyymm.TXT,其中yyyy是4位阿拉伯数字的年数(当然适用于2000),mm是2位阿拉伯数字的月数。这里假定程序可以享有每个月都有更新文件的好处。这些文件可能是整个月刊,而由于阅读效率上的考虑,让程序将其下载到本地储存媒体上。
因此,WinMain在呼叫ShowWindow和UpdateWindow来显示UPDDEMO主窗口以后,向WndProc发送程序定义的WM_USER_CHECKFILES消息。WndProc通过获得目前的年、月并检查该年月UDyyyymm.TXT文件所在的内定目录来处理此消息。这种文件的存在意义在于UPDDEMO会被完全更新(当然,事实并非如此。一些过时的文件将漏掉。如果要做得更完整,程序得进行更广泛的检测)。在这种情况下,UPDDEMO向自己发送一个WM_USER_GETFILES消息,它通过呼叫GetFileList函数来处理。这是UPDDEMO.C中稍长的一个函数,但它并不是特别有用,它所做的全部工作就是将所有的UDyyyymm.TXT文件读到动态配置的FILELIST型态结构中,该结构是在程序顶部定义的,然后让程序在其显示区域显示这些文件的内容。
如果UPDDEMO没有最新的文件,那么它必须透过Internet进行更新。程序首先询问使用者这样做是否「OK」。如果是,程序将显示一个简单的对话框,其中只有一个「Cancel」按钮和一个ID为IDC_STATUS的静态文字区。下载时,此静态文字区向使用者提供状态报告,并且允许使用者取消过于缓慢的更新作业。此对话程序的名称是DlgProc。
DlgProc很短,它建立了一个包括自身窗口句柄的PARAMS型态的结构以及一个名称为bContinue的BOOL变量,然后呼叫_
beginthread来执行第二个执行绪。
FtpThread函数透过使用下面的呼叫来完成实际的传输:InternetOpen、InternetConnect、FtpSetCurrentDirectory、FtpFindFirstFile、InternetFindNextFile、FtpGetFile和InternetCloseHandle(三次)。如同大多数程序代码,该线程函数如果略过错误检查、让使用者了解下一步的操作情况以及允许使用者随意取消整个显示的那些步骤,那么它将变得简洁许多。FtpThread函数透过用hwndStatus句柄呼叫SetWindowText来让使用者知道进展情况,这里指的是对话框中间的静态文字区。线程可以依照下面的三种方式之一来终止:
第一种,FtpThread可能遇到从WinInet函数传回的错误。如果是这样,它将清除并编排错误字符串的格式,然后将此字符串(连同对话框文字区句柄和「Cancel」按钮的句柄一起)传递给ButtonSwitch。ButtonSwitch是一个小函数,它显示了文字字符串,并将「Cancel」按钮转换成「OK」按钮-不只是按钮上的文字字符串的转换,还包括控件ID的转换。这样就允许使用者按下「OK」按钮来结束对话框。
第二种方式,FtpThread能在没有任何错误的情况下完成任务,其处理方法和遇到错误时的方法一样,只不过对话框中显示的字符串为「Internet
Download Complete」。
第三种方式,使用者可以在程序中选择取消下载。这时,DlgProc将PARAMS结构的bContinue字段设定为FALSE。FtpThread频繁地检查该值,如果bContinue等于FALSE,那么函数将做好应该进行的收拾工作,并以NULL文字参数呼叫ButtonSwitch,此参数表示显示了字符串「Internet
Cancelled」。同样,使用者必须按下「OK」按钮来关闭对话框。
虽然UPDDEMO取得的每个文件只能显示一行,但我(本书的作者)可以用这个程序来告诉您(本书的读者)本书的更新内容以及其它信息,您也可以在网站上发现更详细的信息。因此,UPDDEMO成为我向您传送信息的方法,并且可以让本书的内容延续到最后一页之后。

我要回帖

更多关于 封包发送器 的文章

 

随机推荐