一、认识UDP协议
UDP是User Datagram Protocol的简写,全称是“用户数据报协议”。与UDP关系最密切的另一协议应该算TCP了,它们都是TCP/IP协议簇中的一员,并且都位于TCP/IP网络参考模型中的传输层。UDP和TCP最大的区别是,UDP是一种无连接的协议,而TCP是一种面向连接的协议。在网络中使用UDP协议,犹如日常生活中的打电报,发送方只需知道对方的位置,然后把报文发送出去就可以了。发送报文前,发送方不与对方建立连接,并且在发送报文后,也不管对方是否接收到;而在网络中使用TCP,就如同日常生活中的打电话,在通话之前,首先要拨号、震铃、然后等对方拿电话后,方可通话(传送接收数据)。其中拨号、震铃和等待对方拿电话的过程,就是连接的过程,也就是所谓的“握手”。
UDP这种不面向连接的网络协议,既有其优点,也有其不足。优点具体如下:
1. 基于UDP协议的网络应用程序,实现起来比较简单,并且基于UDP协议的网络应用程序在运行时,由于受到环境影响较小,所以不容易出错。
2. UDP协议占用网络资源较少,数据处理较快,所以在网络中传送对安全性要求不是十分高数据时,其优点比较明显。所谓对安全性要求不高的数据,是指那些不重要的数据,或者是即使丢失若干数据,也不影响其整体的数据,如音频数据等。目前很多流行的网络应用程序都是基于UDP协议的,如OICQ、ICQ等。
当然UDP协议的缺点也是非常明显的,由于其不是面向连接的网络协议,并且在数据发送后,在发送方并不确认对方是否接收到。这样就可能导致传送的数据在网络中丢失,尤其在网络条件并不很好的情况下,丢失数据包的现象就更多。所以传送重要数据一般不采用UDP协议。
二、简介Visual C#发送、接收UDP数据包使用的主要类及其用法
用Visual C#实现UDP协议,最为常用,也是最为关键的类就是UdpClient,UdpClient位于命名空间System.Net.Sockets中,Visual C#发送、接收UDP数据包都是通过UdpClient类的。表01和表02是UdpClient类中常用方法和属性及其简要说明。
方法 |
说明 |
Close |
关闭 UDP 连接 |
Connect |
建立与远程主机的连接 |
DropMulticastGroup |
退出多路广播组 |
JoinMulticastGroup |
将 UdpClient 添加到多路广播组 |
Receive |
返回已由远程主机发送的 UDP 数据文报 |
Send |
将 UDP 数据文报发送到远程主机 |
表01:UdpClient类中常用方法及其说明。
属性 |
说明 |
Active |
获取或设置一个值,该值指示是否已建立了与远程主机的连接 |
Client |
获取或设置基础网络套接字 |
表02:UdpClient类中常用方法及其说明。
1. Visual C#使用UdpClient类发送UDP数据包
在具体使用中,一般分成二种情况:
(1)知道远程计算机IP地址:
"Send"方法的调用语法如下:
public int Send (
byte[] dgram ,
int bytes ,
IPEndPoint endPoint
) ;
参数说明:
dgram 要发送的 UDP 数据文报(以字节数组表示)。
bytes 数据文报中的字节数。
endPoint 一个 IPEndPoint,它表示要将数据文报发送到的主机和端口。
返回值 已发送的字节数。
下面使用UdpClient发送UDP数据包的具体的调用例子:
IPAddress HostIP = new IPAddress.Parse ( "远程计算机IP地址" ) ;
IPEndPoint host = new IPEndPoint ( HostIP , 8080 ) ;
UdpClient.Send ( "发送的字节" , "发送的字节长度" , host ) ;
(2)知道远程计算机名称::
知道远程计算机名称后,利用"Send"方法直接把UDP数据包发送到远程主机的指定端口号上了,这种调用方式也是最容易的,语法如下:
public int Send (
byte[ ] dgram ,
int bytes ,
string hostname ,
int port
) ;
参数说明:
dgram 要发送的 UDP 数据文报(以字节数组表示)。
bytes 数据文报中的字节数。
hostname 要连接到的远程主机的名称。
port 要与其通讯的远程端口号。
返回值 已发送的字节数。
2. Visual C#使用UdpClient类接收UDP数据包
接收UDP数据包使用的是UdpClient中的“Receive”方法。此方法的调用语法如下:
public byte [] Receive (
ref IPEndPoint remoteEP
) ;
参数
remoteEP 是一个 IPEndPoint类的实例,它表示网络中发送此数据包的节点。
如果指定了远程计算机要发送到本地机的端口号,也可以通过侦听本地端口号来实现对数据的获取,下面就是通过侦听本地端口号“8080”来获取信息代码:
server = new UdpClient ( ) ;
receivePoint = new IPEndPoint (new IPAddress ( "127.0.0.1" ) , 8080 ) ;
byte[] recData = server.Receive ( ref receivePoint ) ;
三、Visual C#实现UDP协议之网络对时系统的体系结构及功能简介
在局域网中有很多应用软件为了协同工作,需要保证客户机上时间统一,而为了实现这一点,通常的做法是客户机从一个时间相对正确的服务器读取时间,以此来校正本地时间。如经常看到的GPS对时系统等。本节编写的局域网上对时系统的主要的功能是保证局域网上计算机时间、日期的统一。网络对时程序是体系结构分成服务器端程序和客户端程序二个部分,具体的作法是:在同一个网段上,固定一台计算机作为对时的服务器,在这个网段的所有计算机都可以读取这台服务器上的时间和日期,并依此服务器上的时间和日期为基准,来确定本地的时间和日期。在服务器端程序需要达到以下功能:
l 能够接收局域网中任一台客户机的请求
l 记录请求客户机的计算机名称,和请求时间
l 准确发送服务器端的时间和日期
客户端程序要达到以下功能:
l 能够设定服务器的主机或者IP地址
l 能够接收服务器端发送的时间、日期信息
l 能够以接收的服务器端时间、日期为基准,校正本地时间
因此在具体用Visual C#实现网络对时系统时就包括二个部分:服务器端程序和客户端程序。下面首先介绍Visual C#实现网络对时系统中服务器端程序的具体步骤。
四、Visual C#实现网络对时系统之服务器端程序的具体步骤
服务器端程序比客户端程序相对要简单,主要因是服务器端程序的工作比较简单,就是接收客户端的对时请求、发送服务器端的时间数据。而于客户端不仅要传送和接收数据,还要把服务器端的时间提取出来,并以此来修改本地计算机的时间、日期。下面是用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文件的开头,用下列导入命名空间代码替代系统缺省的导入命名空间代码。
using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.Net ;
using System.Net.Sockets ;
using System.Threading ;
//程序中使用到线程
using System.Text ;
//程序中使用到编码
9. 切换到【Form1.cs(设计)】窗口,并从【工具箱】中的【Windows窗体组件】中往窗体中拖入下列组件,并执行相应操作:
一个Label组件,显示对时服务器正在运行信息
一个ListBox组件,名称为listBox1,用以显示客户端和服务器端交流的日志
一个Button组件,名称为button1,并在其拖入窗体后,双击,则系统会在Form1.cs文件中产生其Click事件对应的处理代码。
10. 在【解决方案资源管理器】窗口中,双击Form1.cs文件,进入Form1.cs文件的编辑界面。在Form1.cs中的class代码区添加下列代码,下列代码是定义程序中使用的全局变量和创建全局使用的实例:
private UdpClient server ;
private IPEndPoint receivePoint ;
private int port = 8080 ;
//定义端口号
private int ip = 127001 ;
//设定本地IP地址
private Thread startServer ;
11. 以下面代码替代系统产生的InitializeComponent过程。
private void InitializeComponent ( )
{
this.listBox1 = new System.Windows.Forms.ListBox ( ) ;
this.label1 = new System.Windows.Forms.Label ( ) ;
this.button1 = new System.Windows.Forms.Button ( ) ;
this.SuspendLayout ( ) ;
this.listBox1.ItemHeight = 12 ;
this.listBox1.Location = new System.Drawing.Point ( 14 , 40 ) ;
this.listBox1.Name = "listBox1" ;
this.listBox1.Size = new System.Drawing.Size ( 268 , 220 ) ;
this.listBox1.TabIndex = 0 ;
this.label1.ForeColor = System.Drawing.Color.Red ;
this.label1.Location = new System.Drawing.Point ( 44 , 10 ) ;
this.label1.Name = "label1" ;
this.label1.Size = new System.Drawing.Size ( 210 , 24 ) ;
this.label1.TabIndex = 1 ;
this.label1.Text = "UDP对时服务器端正在运行......" ;
this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat ;
this.button1.Location = new System.Drawing.Point ( 106 , 278 ) ;
this.button1.Name = "button1" ;
this.button1.Size = new System.Drawing.Size ( 75 , 34 ) ;
this.button1.TabIndex = 2 ;
this.button1.Text = "清除信息" ;
this.button1.Click += new System.EventHandler ( this.button1_Click ) ;
this.AutoScaleBaseSize = new System.Drawing.Size ( 6 , 14 ) ;
this.ClientSize = new System.Drawing.Size ( 300 , 329 ) ;
this.Controls.AddRange ( new System.Windows.Forms.Control[] {
this.button1 ,
this.listBox1 ,
this.label1} ) ;
this.MaximizeBox = false ;
this.Name = "Form1" ;
this.Text = "UDP对时服务器端" ;
this.Load += new System.EventHandler ( this.Form1_Load ) ;
this.ResumeLayout ( false ) ;
}
至此,【UDP对时服务器端】项目的界面设计和功能实现的前期工作就完成了,设计界面如图01所示:
图01:【UDP对时服务器端】项目的设计界面
12. 在Form1.cs文件中的InitializeComponent过程的后面添加下面代码,下列代码是定义过程“start_server”。此过程的功能是获取客户端对时请求数据,并向客户端发送服务器当前时间和日期。
public void start_server ( )
{
while ( true )
{
//接收从远程主机发送到本地8080端口的数据
byte[] recData = server.Receive ( ref receivePoint ) ;
ASCIIEncoding encode = new ASCIIEncoding ( ) ;
//获得客户端请求数据
string Read_str = encode.GetString ( recData ) ;
//提取客户端的信息,存放到定义为temp的字符串数组中
string[] temp = Read_str.Split ( "/".ToCharArray ( ) ) ;
//显示端口号的请求信息
listBox1.Items.Add ( "时间:"+ DateTime.Now.ToLongTimeString ( ) + " 接收信息如下:" ) ;
listBox1.Items.Add ( "客户机:" + temp[0] ) ;
listBox1.Items.Add ( "端口号:" + temp[1] ) ;
//发送服务器端时间和日期
byte[] sendData =encode.GetBytes ( System.DateTime.Now.ToString ( ) ) ;
listBox1.Items.Add ( "发送服务器时间!" ) ;
//对远程主机的指定端口号发送服务器时间
server.Send ( sendData , sendData.Length , temp[0] , Int32.Parse ( temp[1] ) ) ;
}
}
请注意:上述代码中约定客户机程序发送对时请求信息到服务器的8080端口号。服务器端程序接收发送到本地8080端口号的数据就完成了数据接收。为了能够让服务器端程序知道是那台客户机提出请求和要把对时信息发送到客户机的那个端口号上,客户端程序对发送的对时请求信息进行了设计。客户端的对时请求信息结构为:
计算机名称 + / + 客户机接收信息端口号
这样如果客户端计算机名称为:majinhu,接收服务器端时间数据的端口号是8080,则客户端程序发送的对时请求数据就为:majinhu/8080。
服务器端程序在接收到客户端对时请求数据,并进行分析后,就能够通过UdpClient类的Send方法准确的把服务器端当前的时间和日期发送到客户端指定的端口号上。这样客户端程序通过读取指定的端口号,就能够获得服务器端当前的时间和日期,从而以此来修正客户端的时间和日期了。
13. 在“start_server”过程之后面添加下面代码,下列代码是定义“run”过程。“run”过程的作用是创建一个线程实例,并以“start_server”过程来初始化线程实例。之所以采用线程是因为服务器端程序需要不间断读取发送到8080端口号,并且Receive方法是一个阻塞式方法。采用线程就是为了保证服务器端程序正常运行:
public void run ( )
{
//利用本地8080端口号来初始化一个UDP网络服务
server = new UdpClient ( port ) ;
receivePoint = new IPEndPoint ( new IPAddress ( ip ) , port ) ;
//开一个线程
startServer = new Thread ( new ThreadStart ( start_server ) ) ;
//启动线程
startServer.Start ( ) ;
}
14. 在Form1.cs中的Main函数之后添加下列代码,下列代码是定义“Form1_Load”事件,在此事件中将调用“run”过程,这样当服务器端程序运行后,就启动网络对时服务:
private void Form1_Load ( object sender , System.EventArgs e )
{
//启动对时服务
run ( ) ;
}
15. 在Form1.cs文件中的“Form1_Load”事件之后,添加下列代码,下列代码是定义button1的“Click”事件,此事件的作用是清除服务器端程序显示的日志信息:
private void button1_Click ( object sender , System.EventArgs e )
{
//清除服务器端程序日志
listBox1.Items.Clear ( ) ;
}
16. 用下列代码替换Form1.cs中的Dispose方法。下列代码的功能是手动收集程序中使用的资源:
protected override void Dispose ( bool disposing )
{
try
{
//关闭线程
startServer.Abort ( ) ;
//清除资源
server.Close ( ) ;
}
catch
{
} ;
if ( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose ( disposing ) ;
}
至此,在上述步骤都正确完成,【UDP对时服务器端】项目的全部工作就完成了。图02【UDP对时服务器端】运行后的界面,在日志信息中记录了对时请求客户机的名称,发送对时数据的端口号以及客户端请求的时间:
图02:【UDP对时服务器端】项目的运行界面
|