C# UDP通信实现(提供源码下载)


初学C#,写了一个简单的UDP通信。


1.UDP基础知识


UDP 是User Datagram Protocol的简称, 中文名是用户数据包协议,是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。它是IETF RFC 768是UDP的正式规范。

     UDP报头

  UDP报头由4个域组成,其中每个域各占用2个字节,具体如下:

  源端口号

  目标端口号

  数据报长度

  校验值

  UDP协议使用端口号为不同的应用保留其各自的数据传输通道。UDP和TCP协议正是采用这一机制实现对同一时刻内多项应用同时发送和接收数据的支持。数据发送一方(可以是客户端或服务器端)将UDP数据报通过源端口发送出去,而数据接收一方则通过目标端口接收数据。有的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为UDP报头使用两个字节存放端口号,所以端口号的有效范围是从0到65535。一般来说,大于49151的端口号都代表动态端口。

  数据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节。不过,一些实际应用往往会限制数据报的大小,有时会降低到8192字节。

  UDP协议使用报头中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此UDP协议可以检测是否出错。这与TCP协议是不同的,后者要求必须具有校验值。

  许多链路层协议都提供错误检查,包括流行的以太网协议,也许想知道为什么UDP也要提供检查和。其原因是链路层以下的协议在源端和终端之间的某些通道可能不提供错误检测。虽然UDP提供有错误检测,但检测到错误时,UDP不做错误校正,只是简单地把损坏的消息段扔掉,或者给应用程序提供警告信息。


UDP协议的几个特性

  (1) UDP是一个无连接协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

  (2) 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。

  (3) UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。

  (4) 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

  (5)UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。

  (6)UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。 

  虽然UDP是一个不可靠的协议,但它是分发信息的一个理想协议。例如,在屏幕上报告股票市场、在屏幕上显示航空信息等等。UDP也用在路由信息协议RIP(Routing Information Protocol)中修改路由表。在这些应用场合下,如果有一个消息丢失,在几秒之后另一个新的消息就会替换它。UDP广泛用在多媒体应用中,例如,Progressive Networks公司开发的RealAudio软件,它是在因特网上把预先录制的或者现场音乐实时传送给客户机的一种软件,该软件使用的RealAudio audio-on-demand protocol协议就是运行在UDP之上的协议,大多数因特网电话软件产品也都运行在UDP之上。


2.程序界面




3.流程图



4.代码


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;


using System.Net;
using System.Net.Sockets;
using System.Threading;


namespace UDP通信程序
{
    public partial class UDPserverForm : Form
    {
        private UdpClient udpserver;//UDP服务器
        private Thread udpListenThread;//UDP监听线程
        private IPEndPoint remoteIpAndPort;//远程IP地址和端口
        private delegate void displayMessageDelegate();//委托
        
        
        public UDPserverForm()
        {
            InitializeComponent();            
            udpListenThread = new Thread(new ThreadStart(udpListen));//创建监听线程
            udpListenThread.IsBackground = true;//设为后台线程
            udpListenThread.Start();//启动监听线程
            //↓ 显示本机内网IPv4地址 win7系统
            //this.textBoxHostIp.Text = Dns.GetHostEntry(Dns.GetHostName()).AddressList[3].ToString();


            for(int i = 0; i < Dns.GetHostEntry(Dns.GetHostName()).AddressList.Length; i++)
            {
                //MessageBox.Show(Dns.GetHostEntry(Dns.GetHostName()).AddressList[i].ToString());
                //↓ 获取本机的IPv4地址,不知道怎么获取,只找到了这个办法
                if (Dns.GetHostEntry(Dns.GetHostName()).AddressList[i].AddressFamily.ToString().Equals("InterNetwork"))
                {
                    this.textBoxHostIp.Text = Dns.GetHostEntry(Dns.GetHostName()).AddressList[i].ToString();
                    break;
                }
            }
        }


        private void udpListen()//监听,线程的实际代码
        {
            int i = 1;
            while (true)//循环,端口不可用自动加1
            {
                try
                {
                    udpserver = new UdpClient(int.Parse(this.textBoxPortNumber.Text));//创建UDP服务器,绑定要监听的端口
                    break;                
                }
                catch
                {
                    //就是将预设的端口号加1
                    this.textBoxPortNumber.Text = (int.Parse(this.textBoxPortNumber.Text)+i++).ToString();
                }
            }


            remoteIpAndPort = new IPEndPoint(IPAddress.Any, 0);//定义IPENDPOINT,装载远程IP地址和端口                    
            string receivedStr;//保存接收的数据字符的临时变量
            
 
            while (true)
            {
                try
                {
                    //将udpserver接受到指定的远程主机的数据包转换成字符串保存在临时变量
                    receivedStr =
                        System.Text.Encoding.UTF8.GetString(udpserver.Receive(ref remoteIpAndPort));


                    //定义委托
                    displayMessageDelegate dis = delegate()
                    {
                        string s = "\n[I received some data]";
                        byte[] b = System.Text.Encoding.UTF8.GetBytes(s);
                        this.udpserver.Send(b, b.Length, remoteIpAndPort);//接收到数据后返回 “[I received some data]”
                        this.textBoxRemoteIp.Text = remoteIpAndPort.Address.ToString();//远程主机的IP显示到窗体
                        this.textBoxRemotePort.Text = remoteIpAndPort.Port.ToString();//远程主机的端口号显示到窗体
                        this.richTextBoxRe.AppendText(string.Format("\n{0}", remoteIpAndPort));//显示远程回话(当昵称用)
                        this.richTextBoxRe.AppendText("\n"+receivedStr);//数据报显示到接收区域                        
                        this.richTextBoxRe.ScrollToCaret();//滚动到最下面,显示最新消息
                        //↓ 更新显示本地端口号(无实际意义)
                        this.textBoxPortNumber.Text = ((IPEndPoint)(this.udpserver.Client.LocalEndPoint)).Port.ToString();
                    };


                    this.Invoke(dis);//执行委托
                }
                catch
                {
                    break;
                }
            }
        }


        private void buttonClose_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }


        private void buttonSend_Click(object sender, EventArgs e)
        {
            try
            {
                //将发送框中的文本转化成byte数组
                byte[] b = System.Text.Encoding.UTF8.GetBytes(this.richTextBoxSend.Text);
                //发送到指定的远程主机(这里是谁发消息到udpserver,它就发给谁)
                this.udpserver.Send(b, b.Length, remoteIpAndPort);
                this.richTextBoxRe.AppendText("\n" + this.textBoxHostIp.Text + ":" + this.textBoxPortNumber.Text);
                this.richTextBoxRe.AppendText("\n"+this.richTextBoxSend.Text);
                this.richTextBoxSend.Clear();
                this.richTextBoxRe.ScrollToCaret();
            }
            catch
            {
                ;
            }
        }


        //修改本地监听端口号则重启线程
        private void buttonUpdatePortNumber_Click(object sender, EventArgs e)
        {
            udpserver.Close();//释放UDP连接
            udpListenThread.Abort();//kill线程
            //重建线程
            udpListenThread = new Thread(new ThreadStart(udpListen));
            udpListenThread.IsBackground = true;
            udpListenThread.Start();
        }
    }
}

5.程序用到的办法


1.TCP、UDP应用程序可以通过TCPClient、TCPListener 和 UDPClient 类进行编程而这些协议类也建立在System.Net.Sockets.Socket 类的基础上并无需理会数据传送的细节。(引用自这里,对UDP讲得比较详细)

2.编写UDP程序有两种技术,一种是直接用Socket类,另一种是用UdpClient类。UdpClient类对基础Socket进行了封装,发送和接受数据时不用考虑底层套接字收发的细节问题。(引用自这里,对UDP讲得比较详细)

3.编程时获取本机IP地址可以用【Dns.GetHostEntry(Dns.GetHostName()).AddressList】获取地址列表, IPAddress.AddressFamily 属性来获取想要的地址,对于 IPv4,返回 InterNetwork;对于 IPv6,返回 InterNetworkV6(引用自这里)

4.让c#检测系统端口是否占用,思考了半天,突然让我想到一般直接使用的话,会因为端口占用而抛出异常,为什么我不能利用一下这个异常呢!(引用自这里,不知是否源出处)


6.效果

使用TCP/UDP调试助手连接到UDP服务端




7.参考资料


各种类的讲解:http://www.doc88.com/p-845510860227.html

SOCKET方式:http://www.cnblogs.com/sufei/archive/2010/04/06/1705836.html

http://live86.blog.sohu.com/156179733.html

http://bbs.csdn.net/topics/310073704 bidisty的回帖


SOCKET方式通信:http://wenku.baidu.com/view/2958c721a5e9856a56126068.html

http://www.docin.com/p-259450007.html


8.代码下载地址




  • 18
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值