使用UDPClient 编写聊天程序
UDPClient 类使用 UDP 与网络服务通讯。UDP 的优点是简单易用,并且能够同时向多个地址广播消息。但由于 UDP 协议是一个无连接协议,因此发送到远程终结点的 UDP 数据文报不一定能够到达,也不一定能够以发送的相同顺序到达。使用 UDP 的应用程序必须准备处理丢失的和顺序有误的数据文报。
若要使用 UDP 发送数据文报,必须知道承载所需服务的网络设备的网络地址以及该服务用于通讯的 UDP 端口号。
特殊网络地址用于支持基于 IP 的网络上的 UDP 广播消息。下面探讨的内容以 Internet 上使用的 IP 版本 4 地址族作为示例。
IP 版本 4 地址使用 32 位指定网络地址。对于使用 255.255.255.0 网络掩码的 C 类地址,这些位被分为四个八位字节。当以十进制数表示时,这四个八位字节构成熟悉的以点分隔的四部分表示法,如 192.168.100.2。前两个八位字节(此示例中为 192.168)构成网络号;第三个八位字节 (100) 定义子网;最后一个八位字节 (2) 是主机标识符。
将 IP 地址的所有位均设置为 1(即 255.255.255.255)可构成有限的广播地址。将 UDP 数据文报发送到此地址可将消息传递到该广播网络上的任何主机。由于路由器从不转发发送到此地址的消息,因此只有已连接的网络上的主机才可看到这些广播。
通过将部分地址的所有位全都设置为 1,可以将广播定向到特定的网络部分。例如,若要将广播发送到以 192.168 打头的 IP 地址标识的网络上的所有主机,请将地址的子网和主机部分全都设置为 1,如 192.168.255.255。若要将广播限制在单个子网,则只将主机部分设置全都为 1,如 192.168.100.255。
UdpClient 类可向任何网络广播地址广播,但它无法侦听发送到网络的广播。必须使用 Socket 类才能侦听网络广播。
当所有接收者都位于单个网络中时,或者当许多客户端需要接收广播时,广播地址将起作用。当接收者为网络的一小部分时,应将消息发送到多路广播组,在那里只有加入此组的客户端才能接收到消息。范围从 224.0.0.2 到 244.255.255.255 的 IP 地址保留为主机组地址。IP 号 224.0.0.0 被保留,而 224.0.0.1 分配给所有 IP 主机的固定组。
下面的示例使用 UdpClient 侦听端口 8080 上的多路广播地址组 10.0.0.1 的 UDP 数据文报广播。它接收消息字符串并将消息写入控制台。
例:在IDE新建一个工程项目,窗体类命名为FormChat,在窗体上放一个控件ListBox,用于接收消息,放一个TextBox控件用于输入要发送的消息,放一个Button用于发送消息。全源代码如下:
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;
namespace WinUDPChat
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class FormChat : System.Windows.Forms.Form
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;
private static UdpClient m_Client;
//private static UdpClient m_Server;
private static int ListenerPort = 8080;
private static int SenderPort = 8080;
private static int LocalPort;
private static int RemotePort;
private static string m_szHostName;
private static IPAddress m_GroupAddress_C;
private static IPAddress m_GroupAddress_S;
private static IPHostEntry m_LocalHost;
private static IPEndPoint m_RemoteEP;
private System.Windows.Forms.Button button_sendMSG;
private System.Windows.Forms.TextBox textBox_msg;
private System.Windows.Forms.ListBox listBox_msg;
private Thread th;
public FormChat()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
}
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.button_sendMSG = new System.Windows.Forms.Button();
this.textBox_msg = new System.Windows.Forms.TextBox();
this.listBox_msg = new System.Windows.Forms.ListBox();
this.SuspendLayout();
//
// button_sendMSG
//
this.button_sendMSG.Location = new System.Drawing.Point(96, 234);
this.button_sendMSG.Name = "button_sendMSG";
this.button_sendMSG.TabIndex = 0;
this.button_sendMSG.Text = "发送";
this.button_sendMSG.Click += new System.EventHandler(this.button_sendMSG_Click);
//
// textBox_msg
//
this.textBox_msg.Location = new System.Drawing.Point(80, 197);
this.textBox_msg.Name = "textBox_msg";
this.textBox_msg.TabIndex = 1;
this.textBox_msg.Text = "";
//
// listBox_msg
//
this.listBox_msg.ItemHeight = 12;
this.listBox_msg.Location = new System.Drawing.Point(21, 22);
this.listBox_msg.Name = "listBox_msg";
this.listBox_msg.Size = new System.Drawing.Size(248, 136);
this.listBox_msg.TabIndex = 2;
//
// FormChat
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.listBox_msg);
this.Controls.Add(this.textBox_msg);
this.Controls.Add(this.button_sendMSG);
this.Name = "FormChat";
this.Text = "聊天室";
this.Load += new System.EventHandler(this.FormChat_Load);
this.Closed += new System.EventHandler(this.FormChat_Closed);
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new FormChat());
}
private void FormChat_Load(object sender, System.EventArgs e)
{
LocalPort = SenderPort;
RemotePort = ListenerPort;
m_szHostName = Dns.GetHostName();
m_LocalHost = Dns.GetHostByName(m_szHostName);
Initialize();
th = new Thread(new ThreadStart(Listener));
th.Start();
}
public void Terminate()
{
m_Client.DropMulticastGroup(m_GroupAddress_C);
}
public void Initialize()
{
//
// 实例化 UdpCLient
//
m_Client = new UdpClient(LocalPort);
//
// 创建对方主机的终结点
//
m_GroupAddress_S = IPAddress.Parse("10.0.0.1"); //要发送到的计算机IP
m_RemoteEP = new IPEndPoint( m_GroupAddress_S, RemotePort );
}
public void Listener()
{
//
// 创建多路广播组对象
//
System.Net.IPHostEntry localhost =Dns.GetHostByName(Dns.GetHostName());
string local_IP= localhost.AddressList[0].ToString();//接收消息的本地IP,用于监听
m_GroupAddress_C = IPAddress.Parse(local_IP);
//
// 联接组
//
try
{
m_Client.JoinMulticastGroup(m_GroupAddress_C, 100); //不添加到多路广播组也可以
}
catch(Exception err)
{
throw(err);//无法联接多路广播组
}
//
// 侦听器等待数据到来
// 并用缓冲区保存它。
Thread.Sleep(2000); // 确保 client2 正在接收
Encoding ASCII = Encoding.ASCII;
// while(!m_Done)
while(true)
{
IPEndPoint endpoint = null;
//endpoint = new IPEndPoint(m_GroupAddress_C,LocalPort);//这句代码不要也可以
Byte[] data = m_Client.Receive(ref endpoint);
String strData = ASCII.GetString(data);
listBox_msg.Items.Add(strData);
}
}
private void FormChat_Closed(object sender, System.EventArgs e)
{
try
{
try
{
if(th!=null)
{
if(th.IsAlive)
{
th.Abort();
}
th=null;
}
}
catch
{
try
{
System.Threading.Thread.ResetAbort();
}
catch
{}
}
Terminate();
m_Client.Close();
}
catch
{
}
}
private void button_sendMSG_Click(object sender, System.EventArgs e)
{
Byte [] buffer = null;
Encoding ASCII = Encoding.ASCII;
string s = textBox_msg.Text;
buffer = new Byte[s.Length + 1];
//
// 将数据发送给远程对方主机
//
int len = ASCII.GetBytes( s.ToCharArray(), 0, s.Length, buffer, 0);
int ecode = m_Client.Send(buffer, len, m_RemoteEP);
}
}
}