Delphi开发环境中应用层网络协议的实现
下面本人简要介绍一下在Delphi环境下,直接采用winsock套接字编程,应用SNTP协议开发出具有网络时间校准功能的应用,以此来说明如何在编程实践中实现应用层网络协议,相信感兴趣的读者能从中举一反三。
一、程序原理:
1、 SNTP协议的运作机制 SNTP(简单网络时间协议)是在UDP协议基础上发展出来的应用协议,目前广泛应用于整个INTERNET上计算机时钟的同步,依据同步源的性能及网络路径的差异,提供1~50ms的较准精度(资料来源:RFC-2030)。
SNTP报文封装见图三,有关IP报头和UDP报头的详细结构这里不再赘述,需要知道的是针对我们的SNTP应用,UDP报头的源端口和目的端口值均应设定为123,SNTP数据紧跟在UDP报头后(数据格式见图二),SNTP协议应用分服务器端和客户端,根据我们的应用需求只要考虑SNTP客户端单播模式(UNICAST MODE)的运作:在该模式下,客户端初始化NTP数据报,将SNTP数据头的VN(协议版本号)值设置成3(即Version3);Mode值设置成3(即客户端模式);数据区的Transmit Timestamp(传输时间戳)值设置成客户机当前时间;然后向一个特定的单播模式时间服务器发出该数据报并接受服务器的回答,收到回答报文后从其中的传输时间戳里获取同步时间。需要注意的是请求数据报初始化时其传输时间戳必须被设定为客户机当前时间,这样可以通过计算,消除服务器客户机之间的传播延迟,有效地将同步精度控制在10ms级范围内。
2、用WINSOCK API实现SNTP 首先,调用socket(domain,type,protocol)建立套接字,其中domain 为 PF_INET(Internet域);type 为SOCK_DGRAM(UDP数据报); protpcol为IPPROTO_IP(IP协议)。其次,调用sendto(socket_id,buf,buflen,flags,to,tolen)发送SNTP客户端请求,其中,buf为SNTP数据;to为时间服务器名字,它是一结构,包含域名、端口号、32位主机地址等;socket_id为套接字号 。接着,调用recvfrom (socket_id,buf,buflen,flags,from,fromlen)接收SNTP数据回答,其中buf为SNTP数据回答。最后,将回答数据中的传输时间戳(Transmit Timestamp)转换成Delphi的时间格式,并调用WINDOWS API函数SetLocalTime同步本地计算机时间,结束时关闭套接字。
二、编程步骤:
在IDE中建立一个新工程,缺省的Form上放一个label控件、一个button控件,在button控件的OnClick事件句柄中键入执行同步时间函数及label控件时间显示代码,另外在单元文件中建立MySyncTime等5个有关时间同步函数(过程),最后,编译该工程。
三、完整的单元文件如下:
| unit Unit1;
interface uses var implementation {$R *.DFM} |
| // 用于转换本机网络字节顺序; lr = packed record l1, l2, l3, l4 : byte; end; var MySocket:Tsocket; fiMaxSockets:integer; MyAddr: TSockAddrIn; UDPSize:integer; const Port=123;// SNTP端口号必须为123; procedure TForm1.Button1Click(Sender: TObject); procedure TForm1.FormCreate(Sender: TObject); file://初始化套接字; end; |
| procedure TForm1.MyConnect(host:string);//建立套接字,域名解析; var fsPeerAddress:string; function ResolveHost(const psHost: string; var psIP: string): u_long;//主机名解析成IP地址; var pa: PChar; sa: TInAddr; aHost: PHostEnt; begin psIP := psHost; if CompareText(psHost, 'LOCALHOST') = 0 then begin sa.S_un_b.s_b1 := #127; sa.S_un_b.s_b2 := #0; sa.S_un_b.s_b3 := #0; sa.S_un_b.s_b4 := #1; psIP := '127.0.0.1'; Result := sa.s_addr; end else begin Result := inet_addr(PChar(psHost)); if Result = u_long(INADDR_NONE) then begin aHost := GetHostByName(PChar(psHost)); pa := aHost^.h_addr_list^; sa.S_un_b.s_b1 := pa[0]; sa.S_un_b.s_b2 := pa[1]; sa.S_un_b.s_b3 := pa[2]; sa.S_un_b.s_b4 := pa[3]; psIP := IntToStr(Ord(sa.S_un_b.s_b1)) + '.' + IntToStr(Ord(sa.S_un_b.s_b2)) + '.' + IntToStr(Ord(sa.S_un_b.s_b3)) + '.' + IntToStr(Ord(sa.S_un_b.s_b4)); Result := sa.s_addr; end; end; end; begin MySocket:=socket(PF_INET,SOCK_DGRAM, IPPROTO_IP);//建立套接字,采用UDP/IP协议; if MySocket = INVALID_SOCKET then begin raise Exception.Create('套接字建立失败!'); end; try with MyAddr do begin file://时间服务器名字; sin_family := PF_INET; sin_port := HToNS(Port); sin_addr.S_addr := ResolveHost(host, fsPeerAddress); end; except On E: Exception do begin if MySocket <> INVALID_SOCKET then begin CloseSocket(MySocket); end;; raise; end; end; end; |
| procedure TForm1.MySend( s:string); file://发送 请求时间数据报; begin SendTo(MySocket, s[1], Length(s), 0,Myaddr , sizeof(Myaddr)); end; function TForm1.MyReceive; file://接收服务器时间数据报; var AddrVoid: TSockAddrIn; fsUDPBuffer:string; i:integer; begin SetLength(fsUDPBuffer, UDPSize); i:= SizeOf(AddrVoid) ; result := Copy(fsUDPBuffer,1,Recvfrom(Mysocket, fsUDPBuffer[1], Length(fsUDPBuffer), 0, AddrVoid , i) ); end; function flip(var n : longint) : longint; file://网络字节顺序与本机字节顺序转换; var n1, n2 : lr; begin n1 := lr(n); n2.l1 := n1.l4; n2.l2 := n1.l3; n2.l3 := n1.l2; n2.l4 := n1.l1; flip := longint(n2); end; function tzbias : double; // 获取本地时间区与UTC时间偏差; var tz : TTimeZoneInformation; begin GetTimeZoneInformation(tz); result := tz.Bias / 1440; end; const maxint2 = 4294967296.0; |
| // 将DELPHI的 TDateTime 格式转换成为 NTP 时间戳(timestamp)格式 ; procedure dt2ntp(dt : tdatetime; var nsec, nfrac : longint); var d, d1 : double; |
| // 将NTP 时间戳(timestamp)格式转换成为DELPHI的 TDateTime 格式; function ntp2dt(nsec, nfrac : longint) : tdatetime; var d, d1 : double; begin d := nsec; if d < 0 then d := maxint2 + d - 1; d1 := nfrac; |
| function TForm1.MySyncTime(host:string):TDateTime;//获取时间服务器上的标准时间,同时同步本地时间; var ng : TNTPGram; s : string; SysTimeVar : TSystemTime; begin fillchar(ng, sizeof(ng), 0); file://将 SNTP数据报初始化; ng.head1 := $1B; // 设置SNTP数据报头为SNTP 协议版本3,模式3(客户机),即二进制00011011; dt2ntp(now, ng.Xmit1, ng.xmit2);//将本机时间转换为数据报时间格式; ng.Xmit1 := flip(ng.xmit1); ng.Xmit2 := flip(ng.xmit2); setlength(s, sizeof(ng)); move(ng, s[1], sizeof(ng)); try MyConnect(host); MySend(s); s := MyReceive; move(s[1], ng, sizeof(ng)); result := ntp2dt(flip(ng.xmit1), flip(ng.xmit2));// 将收到的数据报时间格式转换为本机时间; DateTimeToSystemTime( result, SysTimeVar) ; SetLocalTime( SysTimeVar ); file://同步本地时间; MyDisconnect; except MyDisconnect; showmessage('时间同步失败!'); application.Terminate; end; |
| procedure TForm1.MyDisconnect; file://关闭套接字; begin if MySocket <> INVALID_SOCKET then begin CloseSocket(MySocket); end; end; end. |
程序在Delphi3+ win98se 上通过。
附部分SMTP时间服务器网址:
| augean.eleceng.adelaide.edu.au* bernina.ethz.ch biofiz.mf.uni-lj.si* black-ice.cc.vt.edu chime.utoronto.ca* churchy.udel.edu (128.4.1.5) clepsydra.dec.com clock.psu.edu clock.tricity.wsu.edu (192.31.216.30) constellation.ecn.uoknor.edu cuckoo.nevada.edu* delphi.cs.ucla.edu dominator.eecs.harvard.edu |
Delphi开发环境中应用层网络协议的实现的更多相关文章
- Neutron VxLAN + Linux Bridge 环境中的网络 MTU
1. 基础知识 1.1 MTU 一个网络接口的 MTU 是它一次所能传输的最大数据块的大小.任何超过MTU的数据块都会在传输前分成小的传输单元.MTU 有两个测量层次:网络层和链路层.比如,网络层 ...
- 将linux用在开发环境中
我是如何将linux用在开发环境中的 1.为什么不直接安装Linux在主机 一直想深入学习一下linux的使用,于是将家里的笔记本装了linux系统,但是要将自己的系统打造一个适合开发的环境确实是一件 ...
- 如何将linux用在开发环境中的
如何将linux用在开发环境中的 1.我为什么要写这篇文章 一直想深入学习一下linux的使用,于是将家里的笔记本装了linux系统,但是要将自己的系统打造一个适合开发的环境确实是一件费心费力的事,而 ...
- C# 动态加载组件后怎么在开发环境中调试
动态加载组件 那就是简单的Assembly.Load动态加载dll而以.这网上资料也有不少.基本的思路基本上就是在本地上一个指定目录如[plugs]存在着一堆dll文件.主程序在初始运行时一般会把指定 ...
- Qemu搭建ARM vexpress开发环境(三)----NFS网络根文件系统
Qemu搭建ARM vexpress开发环境(三)----NFS网络根文件系统 标签(空格分隔): Qemu ARM Linux 经过上一篇<Qemu搭建ARM vexpress开发环境(二)- ...
- Idea开发环境中搭建Maven并且使用Maven打包部署程序
1.配置Maven的环境变量 a.首先我们去maven官网下载Maven程序,解压到安装目录,如图所示: b.配置M2_HOME的环境变量,然后将该变量添加到Path中 备注:必须要有JAVA_HOM ...
- Wabpack系列:在webpack+vue开发环境中使用echarts导致编译文件过大怎么办?
现象,在一个webpack+vue的开发环境中,npm install echarts --save了echarts,然后在vue文件中直接使用 import echarts from 'echart ...
- 开发环境中biztalk项目设置注意事项(转)
适用版本:biztalk 2006 适用环境:开发测试环境 在开发过程中,在开发环境中,一定会是一个对项目不断的修改.编译.部署.测试,查看测试结果,发现有问题,然后回到开发环境再修改.编译.部署 ...
- Spark Streaming揭秘 Day28 在集成开发环境中详解Spark Streaming的运行日志内幕
Spark Streaming揭秘 Day28 在集成开发环境中详解Spark Streaming的运行日志内幕 今天会逐行解析一下SparkStreaming运行的日志,运行的是WordCountO ...
随机推荐
- 二维码生成delphi版
二维码生成delphi版 生成二维码的软件,代码从C语言转换过来(源地址:http://fukuchi.org/works/qrencode/),断断续续的差不多花了一周时间来转换和调试.在转换过程中 ...
- linux convert mp3 to wav and opus to wav
link : https://www.cyberciti.biz/faq/convert-mp3-files-to-wav-files-in-linux/ Install mpg321 or mpg ...
- linux网络管理基本命令
1.last 显示所有用户的登录情况 2.lastlog 显示那些用户有登录过,那些用户从来没登录过 3.traceroute 探测我指定网站的路径,来跟踪路由 .如:traceroute www ...
- streaming优化:spark.default.parallelism调整处理并行度
官方是这么说的: Cluster resources can be under-utilized if the number of parallel tasks used in any stage o ...
- 012.Zabbix的IT服务
一 IT服务简介 IT服务能体现宏观度量和管理基础设施的总体情况的可用性,从而体现总体的趋势,发现并解决IT基础设施暴露的问题. 二 IT服务的添加 2.1 IT services--services ...
- Vue.js组件遇到的那些坑
对于绝大多数开发人员来讲,组件是使用Vue.js不能逃避的话题,当然实际开发也会有很多需要注意的地方,一下均为实际操作中遇到的坑,接下来逐一为大家分享: 1.定义并注册组件必须要在声明Vue实例之前, ...
- hiho1270 建造基地([Offer收割]编程练习赛1)
题目意思很简单, 一道类似于背包的问题,问恰好超过背包容量的最小取值.并且需要计算n次,每计算一次都要将所有的物品的价值w[i] /= t,最后输出背包n次的总使用容量. 如果无法实现输出”No An ...
- C++ 队列(queue)堆栈(stack)实现基础
Queue 在C++中只要#include<queue>即可使用队列类,其中在面试或笔试中常用的成员函数如下(按照最常用到不常用的顺序) 1. push 2. pop 3. size 4. ...
- BZOJ2911 : [Poi1997]The Number of Symmetrical Choices
新建源汇S,T,根据题意可以建出一个DAG 设f[x][y]为从x走到y的回文路径的方案数,则 边界条件: f[x][x]=1 对于一条边x->y,若a[x]==a[y],则f[x][y]=1 ...
- win10 下 配置php环境变量
注意,只需要配置到目录即可: