最近断断续续在学习Unix高级环境编程以及Win32 API的VC编程。看起来好像很奇怪,也是因为常年对程序开发的拒绝,现在又急需深入学习,于是选择了Win编程作为快速入门进行实际应用开发的垫脚石,而Unix高级环境编程等作为基石慢慢铺垫。当然,学程序绝对不是一蹴而就的事情,我基础尚可,就是缺乏太多的实际操作,往往在程序方面半途而废,所以想用大量的Win编程实例来丰富自己的编程经验,寻找程序设计的感觉,可能也是由于Win编程的上手会更方便,也能快速开发出一些程序来。总体这门敲门砖也并不是那么容易,不管学习什么,总需要坚持加“逼迫”,一改过于懒散的毛病。

好了,废话很多。这篇的博文作为一个开端,用于学习的记录,因为主攻还是网络方向,所以从比较简单的Time Protocol这个应用层协议开始,描述部分Socket编程以及相关时间函数及数据结构。

Time Protocol的RFC地址是:http://www6.ietf.org/rfc/rfc868.txt

内容很简单,简要描述一下。

1.可以使用TCP或者UDP作为传输层协议

2.端口号都为37

3.返回值为自1900年1月1日0点0分(午夜)起至现在的秒数,是一个32位的数字(UTC时间)

4.通过TCP获取时间的流程:

a.服务端在37端口监听

b.客户端连接到服务端的37端口,建立连接

c.服务端发送时间数据

d.客户端收到时间

e.客户端关闭连接

f.服务端关闭连接

UDP流程就略过了。了解了整个过程以及返回数据形式就可以开动了。自己的练习中完成了一个可以自动/手动按照不同时区获取服务器时间并更新本地时间的简单程序。界面参考:

当然这里不是要把整个程序都描述下来,而是几个关键点。

1.Winsock

Windows很霸道(一向的),虽然使用了标准的socket库,但还是加入了自己的东西,揉捏成了Winsock,如果按照Unix下进行socket编程是无法通过了。这里仅仅是简单流程操作,winsock暂时还无法深入,慢慢会把学习的内容都写下来。

必要的头文件:#include <winsock2.h>

必要的库:ws2_32.lib

以上两者是相关的,代表winsock的第二个版本的头文件和lib,必须要添加。

然后是简略的socket连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WSADATA wsaData;//WSADATA结构包含了winsock的相关信息

WSAStartup(MAKEWORD(2, 2), &amp;wsaData)//进程初始化winsock dll

SOCKET so = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);//建立socket,TCP
SOCKADDR_IN sa;//IP协议存放地址的结构

sa.sin_family = AF_INET;//地址类型 AF_INET代表Internet协议
sa.sin_addr.S_un.S_addr = inet_addr("132.163.4.101");//服务端的IP地址,使用inet_addr将字符串转换为IN_ADDR结构
sa.sin_port = htons(IPPORT_TIMESERVER);//服务端的well-known port:37,需要转换为网络字节序,所以用了htons函数

if(connect(so, (sockaddr *)&sa, sizeof(sa)) == SOCKET_ERROR) { //使用建立好的插口建立与服务器的连接
return;
}
u_long ulTime = 0;
recv(so, (char *)&ulTime, sizeof(u_long), 0);//接收服务器返回的数据,这里为时间
ulTime = ntohl(ulTime);//转换为主机字节序

/*转换时间再输出 见下*/

closesocket(so);//关闭socket
WSACleanup();//终止winsock

1.1 网络字节序及主机字节序的问题

扩展阅读:http://xbull.blogbus.com/logs/6129392.html

1.2 socket的结构及相关函数
扩展阅读:MSDN搜索socket function

2.时间转换

获取到时间是一个u_long类型的数字,如RFC所描述的,是从1900年1月1日起至现在的秒数,自然我们不能直接使用,需要转换成SYSTEMTIME类型,从而分离出年月日。

结构如下:

1
2
3
4
5
6
7
8
9
10
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

它可以是UTC时间也可以是本地时间。
但这样貌似还是没有办法把秒数时间用起来,所以还需要一个结构FILETIME

1
2
3
4
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME;

它是包含了高位和低位的时间,总共64位,值从UTC时间1601年1月1日起的100-nanosecond(1s=1000*1000ns);
FILETIME与SYSTEMTIME之间有函数可以相互转换,FileTimeToSystemTime();
这样就好办了。

首先把获取到的时间与FILETIME“同步”一下

1
2
UINT64 uiCurtime, uiBaseTime, uiResult;
uiBaseTime  = (UINT)HIGHTIME<<32+LOWTIME;

好吧,我还没有定义HIGHTIME和LOWTIME,这两个是什么?
1900年1月1日午夜的高低位时间,经过高位左移后组成64位基础时间
那么怎么获取这两个值,你当然也可以从网上找到,我自己写了一下获取的函数。很简单,将SYSTEMTIME设置为1900年1月1日0点0分0秒,通过函数SystemTimeToFileTime()转换再输出。这里就不贴出来了。自己写一下吧,当作练习。
继续……

1
2
3
uiCurTime = (UINT64)ulTime * (UINT64)10000000;//把返回值转为100-ns
uiResult = uiBaseTime + uiCurTime;//不用解释了吧
FileTimeToSystemTime((LPFILETIME)&uiResult, &st);

这样就完成了。再利用st输出结构中成员。

2.1 时区
可以会有细心的朋友会发现,这样获取了时间只不过是UTC,时差怎么办?如果要中国的或者其他时区的时间还需要做点运算。
如果自己解决这个问题,还是挺麻烦,不仅仅是简单的小时加减,日期、不同的月份、年、闰年以及国际时间变更线等。还好Win32 API提供了SysTimeToTzSpecificLocalTime()函数以及TIME_ZONE_INFORMATION数据结构来转换不同时区的时间。

2.2 本地时间和系统时间
如果要获取本地系统的时间,可以使用GetLocalTime以及GetSystemTime这两个函数,前者是当前设定时区的时间,后者是系统UTC时间。

Time Protocol以及Daytime Protocol

最后回到TCP/IP协议中来,本案例是以Time协议为讲解对象,但经常使用Linux会知道xinetd提供daytime的服务,port为13,支持UDP和TCP,同样可以返回服务器时间,与time协议有什么不同呢?
大家可以阅读RFC867,很重要的一点就是它返回了服务端时间的ASCII字符串,例如:Tuesday, February 22, 1982 17:37:43-PST。现在基本并不是常用的服务了,可以用作一些简单的测试,比如代替PING测试连接的有效性。

总结:

阅读本帖,你会发现我并不是把源代码全部写出来(甚至大多数都没写),目的是让读者自己去探索学习,学习Win32编程或者其他,很关键的是自学,学会搜索以及阅读英文文档。如果你同我一样正在努力学习培养自己的编程经验欢迎留言交流。

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明。
本文标题:Time Protocol协议及应用
本文地址:http://hitigon.im/time-protocol-and-application/