UDP编程
在.NET中,基于UDP协议的网络程序设计可以通过以下4种方法来实现。
Winsock API
Winsock非托管API
Socket类
UdpClient类
前面两种都是直接利用操作系统或第三方提供的网络编程API实现,这要求编程人员必须对网络编程的底层知识有较好的了解。而Socket类实质上是Winsock API的一个包装器,使用Socket类进行网络程序设计与直接使用Winsock API类似。UdpClient类是基于Socket类的较高级别抽象,提供了较高级别的UDP服务。较前面三种方法具有直观易用等优势。因此,在.NET环境中基于UDP协议的网络程序设计可以直接用UdpClient类。
UdpClient类
与TcpClient和TcpListener类似,UdpClient也是构建于Socket类之上,提供了更高层次的UDP服务抽象,用于在阻止同步模式下发送和接收无连接 UDP 数据报,使用简单直观。
基于UdpClient的网络应用编程首先需要创建一个UdpClient类实例,接着通过调用其Connect方法连接到远程主机。当然,这两步也可以直接由指定远程主机名和端口号的UdpClient类构造函数完成。然后便可以利用Send和Receive方法来发送和接收数据。最后调用Close方法关闭UDP连接,并释放相关资源。
流程:
1.创建UdpClient实例
UdpClient提供了 3种构造函数的重载方式来创建UdpClient实例,根据传入参数的不同完成不同的创建形式,如下所述。
l UdpClient ( ),以缺省方式初始化UdpClient的新实例,IP地址和端口号皆由系统自动指定。
l UdpClient (AddressFamily),以指定的地址族初始化UdpClient的新实例。
l UdpClient (Int32),以指定的端口号初始化UdpClient的新实例。
l UdpClient (IPEndPoint),以指定的本地终结点初始化UdpClient类的新实例。
l UdpClient (Int32, AddressFamily),以指定的端口号和地址族初始化UdpClient的新实例。
l UdpClient (String, Int32),以指定的远程主机名和端口号初始化UdpClient的新实例,并建立默认远程主机。
其中,UdpClient (String, Int32)重载形式在完成UdpClient实例初始化的同时也完成了远程主机连接信息的指定。
2.指定连接信息
因为UDP是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接。但可以选择使用下面两种方法之一来指定默认远程主机:
l 使用远程主机名和端口号作为参数创建UdpClient类的实例。
l 创建UdpClient类的实例,然后调用Connect方法。
如果在创建UdpClient实例时没有指定远程主机信息,那么可以在发送数据前通过UdpClient的Connect方法先指定远程主机的地址和端口号,即指定连接信息。但是如果只需要接收数据,则不需要进行指定连接的操作。
对于连接信息的指定,主要包括三种方式,即直接在UdpClient的构造函数中指定,通过调用Connect方法指定和直接在Send方法中指定。而Connect方法又有三种重载形式,如下:
l UdpClient.Connect (IPEndPoint),使用指定的远程主机信息建立默认远程主机。
l UdpClient.Connect (IPAddress, Int32),使用指定的IP地址和端口号建立默认远程主机。
l UdpClient.Connect (String, Int32),使用指定的主机名和端口号建立默认远程主机。
下面的代码段实现了UdpClient实例创建和连接信息指定操作。
IPAddress m_ipA = IPAddress.Parse(m_hostIP);
m_EndPoint = new IPEndPoint(m_ipA, m_port);
m_client = new UdpClient( ); //创建UdpClient实例
m_client.Connect(m_EndPoint); //指定连接信息
3.数据发送和接收
UdpClient实例创建后便可以进行数据发送和接收操作,如图6.5所示。UdpClient中提供了Send方法来完成数据发送操作,其重载形式有如下三种。
l UdpClient.Send (Byte[], Int32),将UDP数据报发送到默认的远程主机。
l UdpClient.Send (Byte[], Int32, IPEndPoint),将UDP数据报发送到位于指定远程终结点的主机。
l UdpClient.Send (Byte[], Int32, String, Int32),将UDP数据报发送到指定的远程主机上的指定端口。
因此,数据发送操作既可以在先指定连接信息的情况下给出发送数据及其长度进行发送,也可以由Send方法来指定远程主机的端口信息以及发送数据和长度进行发送。如下所示。
m_client.Send(data, data.Length);//在指定了连接信息后,直接给出数据及其长度进行发送
在UdpClient中提供了Receive方法来完成数据的接收操作,其申明形式如下:
byte[] Receive ( ref IPEndPoint remoteEP )。
在接收缓冲区没有数据时,Receive 方法将阻止,直到数据报从远程主机到达为止。如果数据可用,则Receive方法将读取接收缓冲区的第一个数据报,并将数据部分作为字节数组返回。在返回数据的同时使用发送方的IPAddress和端口号来填充remoteEP参数。
如果在Connect方法中指定了默认的远程主机,则Receive方法将只接收来自该主机的数据报,其他所有数据报将被丢弃。因此,如果需要接收多播数据报,则在调用Receive方法之前不能利用Connect方法来指定连接信息,并且必须使用多播端口号来创建用于接收数据报的UdpClient。下面程序段实现了对远程主机所发送信息的接收操作。
IPEndPoint m_EndPoint;
byte[] data;
data = m_client.Receive(ref m_EndPoint);//接收数据,同时远程主机信息返回给m_EndPoint
4.关闭连接
使用UdpClient的最后一步是关闭连接,可以直接调用UdpClient的Close方法来实现。
基于UdpClient类的编程实例
在实现中,系统设计了三个类,其中客户端程序包括UDPClient类,服务器程序包括UDPServer类,而UDPComm类中实现了客户端程序和服务器程序所需要的共性操作。
using System;
using System.Collections.Generic;
using System.Text;
namespace UDPComm
{
public class UDPComm
{
public static byte[] EncodingASCII(string buf) //编码
{
byte[] data = Encoding.ASCII.GetBytes(buf + "\r\n");
return data;
}
public static string DecodingASCII(byte[] buf)//解码
{
string st = Encoding.ASCII.GetString(buf);
return st;
}
}
}
UDPClient:
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Text;
using UDPComm;
namespace UDPClient
{
class UDPClient
{
static void Main(string[] args)
{
string m_hostIP = "127.0.0.1";
int m_port = 6666;
UdpClient m_client;
bool rt = false;
byte[] data;
string m_SendData,m_ReturnData;
IPEndPoint m_EndPoint;
//从命令行提取服务器地址和侦听端口
if (args.Length < 2)
{
Console.WriteLine("Usage: UDPClient hostIP port");
}
else {
m_hostIP = args[0].ToString( );
m_port = int.Parse(args[1].ToString( ));
rt = true; }
if (rt)
{
IPAddress m_ipA = IPAddress.Parse(m_hostIP);
m_EndPoint = new IPEndPoint(m_ipA, m_port);
m_client = new UdpClient( );
m_client.Connect(m_EndPoint);
while (true)
{
Console.WriteLine("Input [ADD|DEL|REF|QUIT|Message]:");
m_SendData = Console.ReadLine( );
if (m_SendData.IndexOf("QUIT") > -1)//退出
m_SendData = "DEL";
if (m_SendData.IndexOf("REF") <= -1)//刷新显示
{
data = UDPComm.UDPComm.EncodingASCII(m_SendData);
m_client.Send(data, data.Length);
}
if (m_SendData.IndexOf("QUIT") > -1)
break;
data = m_client.Receive(ref m_EndPoint);//接收数据
m_ReturnData = UDPComm.UDPComm.DecodingASCII(data);
Console.WriteLine(m_ReturnData);
}
//退出
Console.WriteLine("Byte!");
m_client.Close( );
}
}
}
}
UDPServer:
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Text;
using UDPComm;
namespace UDPServer
{
class UDPServer
{
static UdpClient m_server;
static ArrayList mblist;
static void AddMember(IPEndPoint rep)//加入组
{ mblist.Add(rep);
byte[] data = UDPComm.UDPComm.EncodingASCII("OK");
m_server.Send(data, data.Length, rep);
}
static void DelMember(IPEndPoint rep)//离开组
{ mblist.Remove(rep);
byte[] data = UDPComm.UDPComm.EncodingASCII("OK");
m_server.Send(data, data.Length, rep);
}
static void SendToMember(string buf)//组内转发数据
{ foreach (IPEndPoint mb in mblist)
{ byte[] data = UDPComm.UDPComm.EncodingASCII(buf);
m_server.Send(data, data.Length, mb);
}
}
static void Main(string[] args)
{
string m_hostIP = "127.0.0.1";
int m_port = 6666;
IPEndPoint m_EndPoint;
ArrayList memberlist = new ArrayList( );
bool rt = false;
byte[] data;
string m_ReturnData;
//从命令行提取主机IP和端口
if (args.Length < 2)
{
Console.WriteLine("Usage: UDPServer hostIP port");
}
else {
m_hostIP = args[0].ToString( );
m_port = int.Parse(args[1].ToString( ));
rt = true;
}
if (rt)
{
mblist = new ArrayList( );//组成员列表
IPAddress m_ipA = IPAddress.Parse(m_hostIP);
m_EndPoint = new IPEndPoint(m_ipA,m_port);
m_server = new UdpClient(m_EndPoint);
Console.WriteLine("Ready for Connect......");
while (true)
{
data = m_server.Receive(ref m_EndPoint);//接收数据
m_ReturnData = UDPComm.UDPComm.DecodingASCII(data);
if (m_ReturnData.IndexOf("ADD")>-1)//加入组
{
AddMember(m_EndPoint);
Console.WriteLine(m_EndPoint.ToString( )+" has added to group!");
}
else if (m_ReturnData.IndexOf("DEL")>-1)//退出组
{
DelMember(m_EndPoint);
Console.WriteLine(m_EndPoint.ToString( ) + " has deleted from group!");
}
else
{
if (mblist.Contains(m_EndPoint)) //转发数据
{
SendToMember(m_ReturnData + "[" + m_EndPoint.ToString( ) + "]");
Console.WriteLine(m_ReturnData + "[" + m_EndPoint.ToString( ) + "]" + " has resented to members!");
}
}
}
m_server.Close( );
}
}
}
}