C#实现UDP的单播、广播和多播

  • Post author:
  • Post category:其他


一、UDP 的单播、广播、组播

1、含义

(1)单播:用于两个主机之间端对端的通信。即一对一

(2)广播:用于一个主机对整个局域网上所有主机通信。即一对所有

(3)组播(多播):对一组特定的主机进行通信,而不是整个局域网上的所有主机。即一对一组

将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。

组播优点:

  • 具有同种业务的主机加入同一数据流,共享同一通道,节省了带宽和服务器的优点,具有广播的优点而又没有广播所需要的带宽。
  • 服务器的总带宽不受客户端带宽的限制。由于组播协议由接收者的需求来确定是否进行数据流的转发,所以服务器端的带宽是常量,与客户端的数量无关。
  • 与单播一样,多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行。

2、IP地址的不同

广播:广播使用广播IP地址 255.255.255.255

多播:

在多播系统中,有一个源点一组终点。这是一对多的关系。在这种类型的通信中,源地址是一个单播地址,而目的地址则是一个组地址。

多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类。

  • 局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
  • 预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
  • 管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。

3、组播(多播)详解

多播路由器:多播路由器上有识别多播数据报的软件,能够运行多播协议,它也可以转发普通的单播IP数据报。

4、多播数据报和一般IP数据报的区别就是它使用D类IP地址作为目的地址,并且首部中的协议字段值是2,表明使用国际管理协议IGMP。多播地址只能用于目的地址而不能用于源地址。

5、多播分类

网络数据多播需要两种协议:网际组管理协议IGMP和多播路由选择协议

(1)在局域网上进行硬件多播

IGMP:

IGMP协议让连接在本地局域网上的多播路由器知道了本局域网上是否有主机参加或退出了某个多播组。

多播路由选择协议:连接在局域网上的多播路由器还必须和互联网上的其他多播路由器协同工作,以便把多播数据报用最小的代价传送给所有的组成员。

多播数据报可以由没有加入多组播的主机发出,也可以通过没有组成员接入的网络。

二、UdpClient类

UdpClient类用于在同步阻塞模式下发送和接收无连接UDP数据报,因为UDP是无连接传输协议,所以不需要在发送和接收数据 前建立远程主机连接,但可以使用下面两种方式建立默认远程主机

方式一:使用远程主机名和端口号作为参数创建UdpClient类的实例

方式二:创建UdpClient类的实例,然后调用Connect方法

1、广播方式

服务端发送数据:

UDP广播就是向255.255.255.255发送数据,接收端只需绑定UDP广播的端口号即可。

发送端,发送的地址,255.255.255.255:Port,即,IPAddress.Broadcast:Port

接收端,接收的地址,IPAddress.Any:Port

127.0.0.1是环回地址,它是主机用于向自身发送通信的一个特殊地址。

255.255.255.255是受限的广播(它不被路由发送,但会被送到相同物理网络段上的所有主机),容易出现问题,建议改成直接广播地址,类似“192.168.1.255”(网络广播会被路由,并会发送到专门网络上的每台主机)。

(1)服务端编写:

服务端建立发送步骤:

第一步:创建UdpClient对象

第二步:建立默认远程主机

第三步:发送数据

using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Threading;

namespace Server
{
    class Program
    {
        //创建UdpClient对象
        static UdpClient udp = new UdpClient();
        static void Main(string[] args)
        {
            //调用UdpClient对象的Connect方法建立默认远程主机
           // udp.Connect("127.0.0.1", 888);//127.0.0.1是回环地址,用于测试本机内连接
            udp.Connect("192.168.1.255", 888);//192.168.1.255用来发到其他的主机上
            while (true)
            {
                Thread thread = new Thread(() =>
                {
                    while (true)
                    {
                        try
                        {                            
                            //定义一个字节数组,用来存放发送到远程主机的信息
                            Byte[] sendBytes = Encoding.Default.GetBytes("(" + DateTime.Now.ToLongTimeString() + ")节目预报:八点有大型晚会,请收听");
                            Console.WriteLine("(" + DateTime.Now.ToLongTimeString() + ")节目预报:八点有大型晚会,请收听");
                            //调用UdpClient对象的Send方法将UDP数据报发送到远程主机
                            udp.Send(sendBytes, sendBytes.Length);
                            Thread.Sleep(2000);//线程休眠2秒
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                });
                thread.Start();//启动线程
            }
        }
    }
}

(2)客户端编程:

客户端建立步骤:

第一步:使用端口号实例化UDP连接对象

第二步:创建IPEndPoint对象,用来显示响应主机的标识

第三步:接收数据

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace Client
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//在其他线程中可以调用主窗体控件
        }
        bool flag = true;//定义一个bool变量,标识是否接收数据
        Thread thread;//创建线程对象
        UdpClient udp;
        private void button1_Click(object sender, EventArgs e)
        {
           udp = new UdpClient(888);//使用端口号实例化UDP连接对象
            flag = true;//标识接收数据
            //创建IPEndPoint对象,用来显示响应主机的标识
            IPEndPoint ipendpoint = new IPEndPoint(IPAddress.Any, 888);//IPAddress.Any提供一个 IP 地址,指示服务器必须侦听所有网络接口上的客户端活动。 
            thread = new Thread(() =>//新开线程,执行接收数据操作
            {
                while(flag)//如果标识为true
                {
                    try
                    {
                        if (udp.Available <= 0) continue;//判断是否有网络数据
                        if (udp.Client == null) return;//判断连接是否为空
                        //调用UdpClient对象的Receive方法获得从远程主机返回的UDP数据报
                        byte[] bytes = udp.Receive(ref ipendpoint);
                        //将获得的UDP数据报转换为字符串形式
                        string str = Encoding.Default.GetString(bytes);
                        textBox2.Text = "正在接收的信息:\n" + str;//显示正在接收的数据
                        textBox1.Text += "\n" + str;//显示接收的所有数据
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);//错误提示
                    }
                    Thread.Sleep(2000);//线程休眠2秒
                }
            });
            thread.Start();//启动线程
        }

        private void button2_Click(object sender, EventArgs e)
        {
            flag = false;//不接收数据
            if (thread.ThreadState == ThreadState.Running)//判断线程是否运行
                thread.Abort();//终止线程
            //该错误是由于没有找到udp对象造成,请看第27行代码,第27行代码中创建了一个udp对象
            //但该对象是一个局部对象,因此,在button2的Click事件中无法访问到
            //要改正该程序,可以将udp对象声明为全局对象,即在Form1类的内部创建该对象
            udp.Close();//关闭连接
        }
    }
}

(3)IP地址设置

两台计算机之间通过网线连接,需要设置一下IP地址。

计算机A:

计算机B:

将上面的IP地址修改为:192.168.1.4

两台电脑,一台运行服务端,一台运行客户端即可通信。

如果只有一台电脑,服务端将IP地址修改为 127.0.0.1,同一台电脑同时运行服务端和客户端即可。

2、组播方式

比较重要的组播地址有:

224.0.0.1 - 网段中所有支持组播的主机

224.0.0.2 - 网段中所有支持组播的路由器

224.0.0.4 - 网段中所有的DVMRP路由器

224.0.0.5 - 所有的OSPF路由器

224.0.0.6 - 所有的OSPF指派路由器

224.0.0.9 - 所有RIPv2路由器

224.0.0.13 -所有PIM路由器

两台电脑直连,通过组播方式传送数据,利用抓包软件,只能找到ARP的数据包。后来测试发送,要想两台电脑直连实现UDP的组播,必须只留下一个网络适配器,其他的都禁用才可以。

地址解析协议ARP:

(1)作用:已经知道了一个主机或路由器的IP地址,找到其相应的硬件地址。

ARP是解决同一个局域网上的主机或路由器的IP地址和硬件地址的映射问题。

(2)由于IP协议使用了ARP协议,ARP协议划归为网络层,也可以划分为数据链路层。

每一台主机都设有一个ARP高速缓存,里面有本局域网上的各个主机和路由器的IP地址到硬件地址的映射表,并且这个映射表还经常的动态更新。

下面是根据广播的程序改变而成,两个程序在同一台电脑上运行可以通信。

服务端编写:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Server
{
    class Program
    {
        //创建UdpClient对象
        static UdpClient udp = new UdpClient(5566);//要通过其进行通信的本地端口号。  5566是源端口
        static void Main(string[] args)
        {
            udp.JoinMulticastGroup(IPAddress.Parse("224.0.0.0"));//将 UdpClient 添加到多播组;IPAddress.Parse将IP地址字符串转换为IPAddress 实例
            IPEndPoint multicast = new IPEndPoint(IPAddress.Parse("224.0.0.0"), 7788); //将网络终结点表示为 IP 地址和端口号  7788是目的端口
            while (true)
            {
                Thread thread = new Thread(() =>
                {
                    while (true)
                    {
                        try
                        {             
                            //定义一个字节数组,用来存放发送到远程主机的信息
                            Byte[] sendBytes = Encoding.Default.GetBytes("(" + DateTime.Now.ToLongTimeString() + ")节目预报:八点有大型晚会,请收听");
                            Console.WriteLine("(" + DateTime.Now.ToLongTimeString() + ")节目预报:八点有大型晚会,请收听");
                            //调用UdpClient对象的Send方法将UDP数据报发送到远程主机
                            udp.Send(sendBytes, sendBytes.Length, multicast);//将UDP数据报发送到位于指定远程终结点的主机
                            Thread.Sleep(2000);//线程休眠2秒
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }
                });
                thread.Start();//启动线程
            }
        }
    }
}

客户端编写:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace Client
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//在其他线程中可以调用主窗体控件
        }
        bool flag = true;//定义一个bool变量,标识是否接收数据
        Thread thread;//创建线程对象
        UdpClient udp;
        private void button1_Click(object sender, EventArgs e)
        {
            udp = new UdpClient(7788);//使用端口号实例化UDP连接对象  7788是源端口
            udp.JoinMulticastGroup(IPAddress.Parse("224.0.0.0"));
            IPEndPoint ipendpoint = new IPEndPoint(IPAddress.Parse("224.0.0.0"), 5566);  //5566是目的端口
            flag = true;//标识接收数据
            //创建IPEndPoint对象,用来显示响应主机的标识
            
            thread = new Thread(() =>//新开线程,执行接收数据操作
            {
                while(flag)//如果标识为true
                {
                    try
                    {
                        if (udp.Available <= 0) continue;//判断是否有网络数据
                        if (udp.Client == null) return;//判断连接是否为空
                        //调用UdpClient对象的Receive方法获得从远程主机返回的UDP数据报
                        byte[] bytes = udp.Receive(ref ipendpoint);
                        //将获得的UDP数据报转换为字符串形式
                        string str = Encoding.Default.GetString(bytes);
                        textBox2.Text = "正在接收的信息:\n" + str;//显示正在接收的数据
                        textBox1.Text += "\n" + str;//显示接收的所有数据
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);//错误提示
                    }
                    Thread.Sleep(2000);//线程休眠2秒
                }
            });
            thread.Start();//启动线程
        }

        private void button2_Click(object sender, EventArgs e)
        {
            flag = false;//不接收数据
            if (thread.ThreadState == ThreadState.Running)//判断线程是否运行
                thread.Abort();//终止线程
            //该错误是由于没有找到udp对象造成,请看第27行代码,第27行代码中创建了一个udp对象
            //但该对象是一个局部对象,因此,在button2的Click事件中无法访问到
            //要改正该程序,可以将udp对象声明为全局对象,即在Form1类的内部创建该对象
            udp.Close();//关闭连接
        }
    }
}

3、单播方式

(1)数据发送

 //UDP点对点发送
        UdpClient clientUnicast;
        IPEndPoint endpointUnicast;
        
        private void button1_Click(object sender, EventArgs e)
        {
            clientUnicast = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.31.200"), 4113));//定义本地IP地址和端口号
            endpointUnicast = new IPEndPoint(IPAddress.Parse("192.168.31.201"), 4112);//定义目标地址和端口号

            Thread sendUnicastUDPThread = new Thread(sendUnicastUDP);
            sendUnicastUDPThread.Start();
        }

        private void sendUnicastUDP()
        {
            while (true)
            {
                try
                {
                    sendBytes[1] = (byte)udpFrameCnt;//发送帧序号,先发高8位后发低8位
                    sendBytes[0] = (byte)(udpFrameCnt >> 8);
                    clientUnicast.Send(sendBytes, sendBytes.Length, endpointUnicast);//将UDP数据报发送到位于指定远程终结点的主机
                    udpFrameCnt++;
                    for (int i = 0; i < 100; i++)
                    {
                        for (int j = 0; j < SendSpeed; j++)
                        {

                        }
                    }
                }
                catch
                {

                }
            }
        }

(2)数据接收

  //UDP单播接收
        UdpClient recUnicastUDPclient;
        IPEndPoint recUnicastUDPendpoint;
        private void button2_Click(object sender, EventArgs e)
        {
            recUnicastUDPclient = new UdpClient(new IPEndPoint(IPAddress.Parse("192.168.31.201"), 4112));
            recUnicastUDPendpoint = new IPEndPoint(IPAddress.Parse("192.168.31.200"), 4113);
            //启动数据接收线程
            Thread acceptData = new Thread(new ThreadStart(AcceptUnicastUDPProccess));//建立接收数据线程
            acceptData.Start();
        }

        //采用双缓冲的处理机制 
        private List<byte> bufferA = new List<byte>(797000);   //缓冲器
        private List<byte> bufferB = new List<byte>(797000);   //缓冲器
        bool switchflag = false;//乒乓操作缓存标志位
        bool ByteAFull = false;
        bool ByteBFull = false;
        private void AcceptUnicastUDPProccess()
        {
            while (true)
            {
                if (recUnicastUDPclient.Available <= 0) continue;//判断是否有网络数据
                if (recUnicastUDPclient.Client == null) return;//判断连接是否为空
                if (switchflag)
                {
                    byte[] bytes = recUnicastUDPclient.Receive(ref recUnicastUDPendpoint);
                    bufferB.AddRange(bytes);
                    if (bufferB.Count >= 797000)
                    {
                        switchflag = false;
                        ByteBFull = true;
                    }
                }
                else
                {
                    byte[] bytes = recUnicastUDPclient.Receive(ref recUnicastUDPendpoint);
                    bufferA.AddRange(bytes);
                    if (bufferA.Count >= 797000)
                    {
                        switchflag = true;
                        ByteAFull = true;
                    }
                }
            }
        }



版权声明:本文为kenjianqi1647原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。