Visual C#实现网络对时系统——UDP协议的实际应用(下)
五、Visual C#实现网络对时系统之客户端程序的具体步骤 客户端的程序比服务器端程序要复杂些,因为客户端程序不仅要往服务器端发送对时请求信息,接收服务器端反馈的日期和时间数据,还要提取这些时间和日期信息,并以此来修改本地端的日期和时间。参照上面实现网络对时系统服务器端程序,实现请求信息的发送和接收应相对要容易许多。所以客户端程序的关键就是根据获得的服务器端日期、时间数据来修改本地的日期、时间。在.Net FrameWork SDK 3705版本中并没有提供修改本地日期和时间的函数和类库,本文的实现办法是引入WinAPI函数,通过对应的WinAPI函数来更正本地时间和日期的,具体可参阅以下第十三和第十八步。 以下是Visual C#实现网络对时系统之客户端程序的具体实现步骤: 1. 启动Visual Studio .Net。 2. 选择菜单【文件】|【新建】|【项目】后,弹出【新建项目】对话框。 3. 将【项目类型】设置为【Visual C#项目】。 4. 将【模板】设置为【Windows应用程序】。 5. 在【名称】文本框中输入【UDP对时客户端】。 6. 在【位置】的文本框中输入【E:\VS.NET项目】,然后单击【确定】按钮。 7. 【解决方案资源管理器】窗口中,双击Form1.cs文件,进入Form1.cs文件的编辑界面。 8. 在Form1.cs文件的开头,用下列导入命名空间代码替换Form1.cs中缺省的导入命名空间代码。 using System.Collections ; using System.ComponentModel ; using System.Windows.Forms ; using System.Data ; using System.Net ; using System.Net.Sockets ; using System.Runtime.InteropServices ; //程序引入WinAPI函数要使用到 9. 把Visual Studio .Net的集成开发环境的当前窗口切换到【Form1.cs(设计)】窗体设计界面,并从【工具箱】中的【Windows窗体组件】中往窗体中拖入下列组件,并执行相应操作: 三个TextBox组件,分别用来输入服务器的IP地址,和显示本地时间、服务器的时间 二个Button组件,分别是button1和button2,在设计界面中分别双击button1和button2,系统会自动产生它们对应的Click事件处理代码。 三干个Label组件。 10. 【解决方案资源管理器】窗口中,双击Form1.cs文件,进入Form1.cs文件的编辑界面。在Form1.cs中的namespace代码区添加下列代码,下列代码的功能是在程序中定义系统时间的结构体。 [ StructLayout ( LayoutKind.Sequential )] public class SystemTime { public short year ; public short Month ; public short DayOfWeek ; public short Day ; public short Hour ; public short Minute ; public short Second ; public short Milliseconds ; } //定义系统时间的结构 11. 在Form1.cs中的class代码区添加下列代码,下列代码的功能是定义程序中使用的全局变量。 private UdpClient client ; //创建UDP网络服务 private IPEndPoint receivePoint ; private int port = 8080 ; //定义接收服务器端程序发送对时信息对应的端口号 private string timeString = DateTime.Now.ToString ( ) ; //存放时间日期信息字符串 private DateTime temp ; //定义一个时间类型,用以修改当前时间和日期 12. 并以下面代码替换Form.cs中由系统产生的InitializeComponent过程。 private void InitializeComponent ( ) { this.button1 = new System.Windows.Forms.Button ( ) ; this.button2 = new System.Windows.Forms.Button ( ) ; this.textBox1 = new System.Windows.Forms.TextBox ( ) ; this.textBox2 = new System.Windows.Forms.TextBox ( ) ; this.label1 = new System.Windows.Forms.Label ( ) ; this.label2 = new System.Windows.Forms.Label ( ) ; this.label3 = new System.Windows.Forms.Label ( ) ; this.textBox3 = new System.Windows.Forms.TextBox ( ) ; this.SuspendLayout ( ) ; this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat ; this.button1.Location = new System.Drawing.Point ( 128 , 128 ) ; this.button1.Name = "button1" ; this.button1.Size = new System.Drawing.Size ( 112 , 40 ) ; this.button1.TabIndex = 0 ; this.button1.Text = "获取" ; this.button1.Click += new System.EventHandler ( this.button1_Click ) ; this.button2.FlatStyle = System.Windows.Forms.FlatStyle.Flat ; this.button2.Location = new System.Drawing.Point ( 128 , 184 ) ; this.button2.Name = "button2" ; this.button2.Size = new System.Drawing.Size ( 112 , 40 ) ; this.button2.TabIndex = 1 ; this.button2.Text = "对时" ; this.button2.Click += new System.EventHandler ( this.button2_Click ) ; this.textBox1.Location = new System.Drawing.Point ( 120 , 56 ) ; this.textBox1.Name = "textBox1" ; this.textBox1.Size = new System.Drawing.Size ( 200 , 21 ) ; this.textBox1.TabIndex = 2 ; this.textBox1.Text = "" ; this.textBox2.Location = new System.Drawing.Point ( 120 , 88 ) ; this.textBox2.Name = "textBox2" ; this.textBox2.Size = new System.Drawing.Size ( 200 , 21 ) ; this.textBox2.TabIndex = 3 ; this.textBox2.Text = "" ; this.label1.Location = new System.Drawing.Point ( 48 , 56 ) ; this.label1.Name = "label1" ; this.label1.TabIndex = 4 ; this.label1.Text = "本地时间:" ; this.label2.Location = new System.Drawing.Point ( 40 , 88 ) ; this.label2.Name = "label2" ; this.label2.Size = new System.Drawing.Size ( 88 , 24 ) ; this.label2.TabIndex = 5 ; this.label2.Text = "服务器时间:" ; this.label3.Location = new System.Drawing.Point ( 16 , 24 ) ; this.label3.Name = "label3" ; this.label3.Size = new System.Drawing.Size ( 112 , 23 ) ; this.label3.TabIndex = 6 ; this.label3.Text = "设定服务器地址:" ; this.textBox3.Location = new System.Drawing.Point ( 120 , 24 ) ; this.textBox3.Name = "textBox3" ; this.textBox3.Size = new System.Drawing.Size ( 200 , 21 ) ; this.textBox3.TabIndex = 7 ; this.textBox3.Text = "" ; this.AutoScaleBaseSize = new System.Drawing.Size ( 6 , 14 ) ; this.ClientSize = new System.Drawing.Size ( 352 , 245 ) ; this.Controls.AddRange ( new System.Windows.Forms.Control[] { this.textBox3 , this.textBox2 , this.textBox1 , this.button2 , this.button1 , this.label1 , this.label2 , this.label3} ) ; this.MaximizeBox = false ; this.Name = "Form1" ; this.Text = "UDP对时客户端" ; this.ResumeLayout ( false ) ; } 至此【UDP对时客户端】项目的界面设计和程序功能实现的前期工作就基本完成了,其设计界面如图03所示:
13. 在Form1.cs中的InitializeComponent过程之后,添加下列代码,下列代码的功能是在程序中导入WinAPI函数——SetSystemTime,这个函数位于文件Kernel32.dll。程序就是通过此函数来更正系统时间的。 [ DllImport ( "Kernel32.dll" )] private static extern bool SetSystemTime ( SystemTime time ) ; //引入API函数 14. 并把它添加到在导入WinAPI函数代码之后,再添加下列代码,下列代码是定义“start_client”过程。此过程的功能是向服务器端传送对时请求,并获取从服务器端反馈来的时间日期数据。 { client = new UdpClient ( port ) ; IPAddress a = IPAddress.Parse ( "127001" ) ; receivePoint = new IPEndPoint ( a , port ) ; IPAddress HostIP ; bool continueLoop = true ; while ( continueLoop ) { string hostName = Dns.GetHostName ( ) ; System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding ( ) ; //定义发送到服务器端的请求信息 //请求信息是一个字符串,为客户端名称和接收服务器反馈信息的端口号//组成的字符串 string sendString = hostName + "/" + port.ToString ( ) ; byte[] sendData = encode.GetBytes ( sendString ) ; //判断使用者输入的是IP地址还是计算机名称 try { HostIP = IPAddress.Parse ( textBox3.Text ) ; } catch { //如果输入的是计算机名称,则按照执行下列代码。 //发送请求信息 client.Send ( sendData , sendData.Length , textBox3.Text , 8080 ) ; //接收来自服务器端的信息 byte[] recData = client.Receive ( ref receivePoint ) ; timeString = encode.GetString ( recData ) ; client.Close ( ) ; continueLoop=false ; return ; } //输入的是IP地址,则执行下列代码 IPEndPoint host = new IPEndPoint ( HostIP ,8080 ) ; //发送请求信息 client.Send ( sendData , sendData.Length , host ) ; //接收来自服务器端的信息 byte[] recData1 = client.Receive ( ref receivePoint ) ; //获取服务器端的时间和日期 timeString = encode.GetString ( recData1 ) ; client.Close ( ) ; //退出循环 continueLoop=false ; } } 如果“start_client”过程正确调用,就把服务器端的时间和日期保存到timeString字符串中了。 15.用下列代码替换Form1.cs中button1的“Click”事件的处理代码。下列代码的功能是调用“start_client”过程,获取并显示服务器端程序的时间和日期信息。 private void button1_Click ( object sender , System.EventArgs e ) { start_client ( ) ; textBox1.Text = DateTime.Now.ToString ( ) ; //显示客户端当前时间和日期 textBox2.Text = timeString ; //显示服务器当前时间和日期 } 16.用下列代码替换Form1.cs中button2的“Click”事件对应的处理代码。下列代码的功能是根据获取的服务器时间和日期数据来更正客户端时间和日期。 private void button2_Click ( object sender , System.EventArgs e ) { start_client ( ) ; //把接收来的数据转换时间日期格式 try { temp = DateTime.Parse ( timeString ) ; } catch { MessageBox.Show ( "错误时间" ) ; return ; } //根据得到的时间日期,来定义时间、日期 SystemTime st= new SystemTime ( ) ; st.year= ( short )temp.Year ; st.Month= ( short )temp.Month ; st.DayOfWeek= ( short )temp.DayOfWeek ; st.Day= ( short )temp.Day ; st.Hour=Convert.ToInt16 ( temp.Hour ) ; if ( st.Hour>=12 ) { st.Hour-= ( short )8 ; } else if ( st.Hour >= 8 ) { st.Hour-= ( short )8 ; } else { st.Hour+= ( short )16 ; } st.Minute=Convert.ToInt16 ( temp.Minute ) ; st.Second=Convert.ToInt16 ( temp.Second ) ; st.Milliseconds=Convert.ToInt16 ( temp.Millisecond ) ; //修改本地端的时间和日期 if ( SetSystemTime ( st ) ) { MessageBox.Show ( DateTime.Now.ToString ( ) ,"修改成功" ) ; } else MessageBox.Show ( "不成功!" ,"不成功" ) ; } 至此,在正确完成上述步骤,全部保存后,【网络对时客户端】项目的全部工作就完成了。 六、运行网络对时系统,实现网络对时 首先要确认确认网络对时系统中的服务器端程序已经运行和其IP地址或主机名。然后在客户机上运行网络对时系统中的客户端程序,在正确输入运行网络对时系统服务器端程序对应的主机名或者IP地址后,单击客户端程序中【获取】按钮,则在程序的文本框中显示服务器当前时间和日期和客户端当前的时间和日期。若发现二种存在差异,单击【对时】按钮,则将以服务器当前时间和日期来修正客户机的时间和日期。修改成功则弹出【修改成功】提示框,反之则弹出【不成功】提示框,图04是【UDP对时客户端】项目根据服务器端当前时间和日期信息成功更改本地时间和日期后的界面:
七、总结 本文介绍了用Visual C#实现一个网络实际应用系统——对时系统的具体过程。通过本文的介绍,不仅获得了一个实用的网络对时系统,还掌握了UDP协议的相关知识和在Visual C#中实现UDP的具体方法。UDP由于其自身的缺点注定在某些领域无法利用它,但在可以利用它的领域,UDP以其快捷、简单、实用的特点正在受到更多程序员的欢迎。尤其在现代,网络运行态势越来越好的情况下,可以预见的是UDP在网络中的应用情景将更广阔。 |