C#网络编程入门之UDP

一、概述

UDP和TCP是网络通讯常用的两个传输协议,C#一般可以通过Socket来实现UDP和TCP通讯,由于.NET框架通过UdpClient、TcpListener 、TcpClient这几个类对Socket进行了封装,使其使用更加方便, 本文就通过这几个封装过的类讲解一下相关应用。

 

二、UDP基本应用

与TCP通信不同,UDP通信是不分服务端和客户端的,通信双方是对等的。为了描述方便,我们把通信双方称为发送方和接收方。

发送方:

首先创建一个UDP对象:

            string locateIP = "127.0.0.1";    //本机IP

            int locatePort = 9001;            //发送端口

            IPAddress locateIpAddr = IPAddress.Parse(locateIP);

            IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort);

            UdpClient  udpClient = new UdpClient(locatePoint);

发送数据:

            string remoteIP = "127.0.0.1";             //目标机器IP

            int remotePort = 9002;                     //接收端口          

            IPAddress remoteIpAddr = IPAddress.Parse(remoteIP);

            IPEndPoint remotePoint = new IPEndPoint(remoteIpAddr, remotePort);

            byte[] buffer = Encoding.UTF8.GetBytes(“hello”);

            udpClient.Send(buffer, buffer.Length, remotePoint);

以上就完成了一个发送任务,一个较完整的发送代码如下: 

    public partial class FormServer : Form
    {
        private UdpClient udpClient = null;

        private void btnConnect_Click(object sender, EventArgs e)
        {
            string locateIP = "127.0.0.1";
            int locatePort = 9001;
            IPAddress locateIpAddr = IPAddress.Parse(locateIP);
            IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort);
            udpClient = new UdpClient(locatePoint);

            this.groupWork.Enabled = true;
        }

        private void Send_Click(object sender, EventArgs e)
        {
            string text = this.txtSend.Text.Trim();
            string remoteIP = "127.0.0.1";
            int remotePort = 9002;
            byte[] buffer = Encoding.UTF8.GetBytes(text);

            if (udpClient != null)
            {
                IPAddress remoteIp = IPAddress.Parse(remoteIP);
                IPEndPoint remotePoint = new IPEndPoint(remoteIp, remotePort);
                udpClient.Send(buffer, buffer.Length, remotePoint);
            }

            Debug.WriteLine("Send OK");
        }
    }
View Code

 

接收端: 

首先创建一个UDP对象:

            string locateIP = "127.0.0.1";

            int locatePort = 9002;

            IPAddress locateIpAddr = IPAddress.Parse(locateIP);

            IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort);

            UdpClient  udpClient = new UdpClient(locatePoint);

接收数据: 

    IPEndPoint remotePoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 1);

     var received = udpClient.Receive(ref remotePoint);

     string info = Encoding.UTF8.GetString(received);

     string from=$” {remotePoint.Address}:{remotePoint.Port}”;

注意两点:

1、remotePoint是获得发送方的IP信息,定义时可以输入任何合法的IP和端口信息;

2、Receive方法是阻塞方法,所以需要在新的线程内运行,程序会一直等待接收数据,当接收到一包数据时程序就返回,要持续接收数据需要重复调用Receive方法。

一个较完整的接收端代码如下: 

   public partial class FormClent : Form
   {
        private UdpClient udpClient = null; 

        private void btnConnect_Click(object sender, EventArgs e)
        {
            string locateIP = "127.0.0.1";
            int locatePort = 9002;
            IPAddress locateIpAddr = IPAddress.Parse(locateIP);
            IPEndPoint locatePoint = new IPEndPoint(locateIpAddr, locatePort);
            udpClient = new UdpClient(locatePoint);
            IPEndPoint remotePoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 1);

            Task.Run(() =>
            {
                while (true)
                {
                    if (udpClient != null)
                    {
                        var received = udpClient.Receive(ref remotePoint);
                        string info = Encoding.UTF8.GetString(received);
                        string from=$” {remotePoint.Address}:{remotePoint.Port}”; 
                    }
                }
            }); 
        }
   }
View Code

 

三、丢包和乱序问题

   当发送端发送一包数据时,不管对方是否接收都是发送成功的,UDP协议本身并不会对发送的可靠性进行验证。(这里的可靠性是指是否接收到,如果对方接收到数据包,其内容还是可靠的,这个在链路层进行了保证。)同时,由于网络延时等因素,先发送的包并不能确定先被接收到,所以由于这两个原因,UDP通信存在丢包和乱序的情况。

   某些业务场景下,比如实时状态监控,可能对丢包和乱序情况并不敏感, 可以不用处理,但大部分情况下还是介意丢包的,简单的处理办法就是把包的头部固定长度的空间拿出来存放核对信息,比如包编号,如果有缺失,可以要求发送方重发,也可以进行排序。

 

四、将数据接收包装为事件

 我们对UdpClent又进行一次封装,启用一个线程进行接收数据,将接收到的数据包通过事件发布出来,这样使用起来就更方便了。

namespace Communication.UDPClient
{
    public class UdpStateEventArgs : EventArgs
    {      
        public IPEndPoint remoteEndPoint;      
        public byte[] buffer = null;
    }

    public delegate void UDPReceivedEventHandler(UdpStateEventArgs args);

    public class UDPClient
    {
        private UdpClient udpClient;
        public event UDPReceivedEventHandler UDPMessageReceived;

        public UDPClient(string locateIP, int locatePort)
        {
            IPAddress locateIp = IPAddress.Parse(locateIP);
            IPEndPoint locatePoint = new IPEndPoint(locateIp, locatePort);
            udpClient = new UdpClient(locatePoint);

            //监听创建好后,创建一个线程,开始接收信息
            Task.Run(() =>
            {
                while (true)
                {
                    UdpStateEventArgs udpReceiveState = new UdpStateEventArgs();

                    if (udpClient != null)
                    {
                        IPEndPoint remotePoint = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 1);
                        var received = udpClient.Receive(ref remotePoint);
                        udpReceiveState.remoteEndPoint = remotePoint;
                        udpReceiveState.buffer = received;
                        UDPMessageReceived?.Invoke(udpReceiveState);
                    }
                    else
                    {
                        break;
                    }
                }
            });
        }
    }
}

具体使用办法:

        private void btnConnect_Click(object sender, EventArgs e)
        {
            string locateIP = "127.0.0.1";
            int locatePort = 9002;           
            UDPClient udpClient = new UDPClient(locateIP, locatePort);
            udpClient.UDPMessageReceived += UdpClient_UDPMessageReceived;           
        }

        private void UdpClient_UDPMessageReceived(UdpStateEventArgs args)
        {
            var remotePoint = args.remoteEndPoint;
            string info = Encoding.UTF8.GetString(args.buffer); 
        }

 限于篇幅,我们只封装了数据接收,实际使用时需要把发送功能也封装进去,使这个类同时具备发送和接收功能,发送功能的封装比较简单就不贴代码了。

 

传送门:

C#网络编程入门系列包括三篇文章:

(一)C#网络编程入门之UDP

(二)C#网络编程入门之TCP

(三)C#网络编程入门之HTTP

posted @ 2020-05-27 14:33  seabluescn  阅读(14121)  评论(1编辑  收藏  举报