.Net UDP通讯示例

代码下载地址.NetSockUdp代码-C#文档类资源-CSDN下载.NetSockUdp代码更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/g313105910/85047398

一、NAT网络原理

最近要做UDP通讯,网上一查资料还真不多,比如服务器如何保存客户端的地址,因为UDP不是长连接,内网穿透可能会有问题,就是数据发到了服务器上,服务器发不回去了的这种可能,这就需要具体来讲解一下现在的网络结构。

目前主流的网络IP地址还是IPV4,过度到IPV6是个非常漫长的过程,所以目前“节约”IP地址最常见的方式:NAT,NAT大家肯定不陌生,在家里、公司上网,一般都是通过路由器的,这么做的好处有如下几点:

1、节约IP地址:只需要给路由器分配公网IP,路由器内部的设备用内网网段的地址,不同路由器内网网段的地址能复用。 比如一个家用路由器,一般最多能支持几十个来个设备同时连接路由器,都通过路由器上,网运营商只需要给路由器分配一个公网IP即可。

2、内网的设备并不直接暴露在公网,只能让路由器对外直接通信,在一定程度上保障了内网设备收不到外界的各种扫描、探测类的数据包,保障内网安全。  路由器收到外界这些主动连接的数据包会直接丢弃(除非网关设置了反向代理,比如服务器前端的网关一般会转发80端口的数据包),这也是客户端之间通信需要“穿透,打洞”的根本原因!  

3、对于部分网关而言:只要内网发送数据的IP不变,那么自己对外映射转发的端口就不变,比如内部192.168.0.10对外发数据,网关的公网地址是222.111.111.111,此时随机选一个端口比如333,那么对外数据包的源信息就是222.111.111.111:333; 后续网关凡是收到目的是222.111.111.111:333的数据包,不管这个包来自哪(因为是UDP协议,源和目的没有建立连接),都认为是响应192.168.0.10这个节点的,一律转发到该节点,这就是所谓的完整的锥形NAT,这种方式NAT并不验证源IP和端口就直接转发到内网主机(包括内网主机并未访问过的IP和端口),不安全。 下面介绍的UDP打洞必须依赖这个特性!  

  UDP打洞实现NAT穿越是一种在处于使用了NAT的私有网络中的Internet主机之间建立双向UDP连接的方法。由于NAT的行为是非标准化的,因此它并不能应用于所有类型的NAT。
  其基本思想是这样的:让位于NAT后的两台主机都与处于公共地址空间的服务器相连,然后,一旦NAT设备建立好UDP状态信息就转为直接通信,这项技术需要一个圆锥型NAT设备才能够正常工作。对称型NAT不能使用这项技术。

UDP打洞的过程大体上如下:
主机A和主机B都是通过NAT设备访问互联网,主机S位于互联网上。
1. A和B都与S之间通过UDP进行心跳连接
2. A通知S,要与B通信
3. S把B的公网IP、port告诉A,同时把A的公网IP、port告诉B
4. A向B的公网IP、port发送数据(这个数据包应该会被丢弃,但是打开了B回来的窗户)
5. B向A的公网IP、port发送数据(这个数据包就会被A接受,之后A和B就建立起了连接)

二、网络类型(四种)

1、Full cone NAT(全锥形NAT)

所有从同一个内网的(IP,端口)发送出来的请求都会被映射到同一个外网(IP,端口),且任何一个外网主机都可以通过访问映射后的公网地址,实现访问位于内网的主机设备功能。

外网主机可以主动连接内网主机。

该类型NAT只与源IP和源端口相关,只要(源IP,源端口)相同则可以通过映射后的(公网IP,端口)访问任意网站,因此称之为全锥形NAT. 有点类似于静态NAT 

2、Restricted Cone NAT(地址受限锥形NAT)

所有从同一个内网的(IP,端口)发送出来的请求都会被映射到通过一个外网(IP,端口),但与全锥形不同点在于:生成的映射表项与目的IP有关,只有符合要求的目的IP(要访问的公网服务器IP)才可以通讯。此NAT还有个特点:不能主动连接内网中的主机地址,连接必须由内网地址发起。

限制比全锥形NAT多了:IP地址限制。

此类型NAT除了与源IP和源端口相关外,还与目的IP有关,只有内网主机主动连接的公网IP才可以与内网中的主机通讯。

3、Port Restricted Cone NAT(端口受限锥形NAT)

所有从同一个内网的(IP,端口)发送出来的请求都会被映射到通过一个外网(IP,端口),但是在地址受限锥形NAT基础上增加了端口的限制。

地址受限锥形NAT时,只有内网主机主动连接的公网主机才可与之进行通讯,而不用担心端口号是否与请求的端口相同。

但是端口受限锥形NAT除了IP限制外,增加了端口限制。意思是说:除了之前主动连接了主机的(IP,port1,)可以通讯,其他的(IP,port2)等都不可以与之通讯。此NAT映射与报文的三元组绑定。

4、Symetric NAT(对称NAT)

所有从同一个内网(IP,端口)发送到同一个目的IP和端口的请求都会被映射到同一个IP和端口。换句话说(SIP,Sport, DIP, Dport)只要有一个发生变化都会使用不同的映射条目,即此NAT映射与报文四元组绑定。

前三种nat有一个共同点:只要内网中的(IP,端口)相同的请求就会被NAT映射到同一个外网(IP,port)。 

NAT类型说明
全锥形NAT任何公网主机都可与之通讯。双方都可以主动发起
地址受限锥形NAT只有内网主动连接的公网主机可与之通讯,必须内网主机发起。且此公网主机可通过任意端口与内网主机通讯。
端口受限锥形NAT只有内网主动连接的公网主机的连接可与之通讯,必须内网主机发起。且此公网只能通过固定的端口与之进行通讯。

 最后一种对称NAT: 一个连接一条映射(网络上的连接通过四元组表示:[SIP,DIP,SPORT,DPORT] )

NAT类型说明
对称NAT根据四元组创建NAT映射,四元组中的任何一项发生变化均导致NAT映射的更换。此形状双方一对一映射,因此被称之为对称NAT

三、.Net Sock Udp代码

服务器会转发所有客户端发送的消息,执行效果

服务端代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace UDPServer
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] endPoints = new string[1000];
            int endPointsCount = 0;
            int recv;
            byte[] data = new byte[1024];

            //构建TCP 服务器

            //得到本机IP,设置TCP端口号         
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8889);
            Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            //绑定网络地址
            newsock.Bind(ipep);

            Console.WriteLine("This is a Server, host name is {0}", Dns.GetHostName());

            //等待客户机连接
            Console.WriteLine("Waiting for a client");


            //客户机连接成功后,发送欢迎信息
            string welcome = "Welcome ! ";

            //字符串与字节数组相互转换
            data = Encoding.ASCII.GetBytes(welcome);

            //发送信息
            //newsock.SendTo(data, data.Length, SocketFlags.None, Remote);
            while (true)
            {
                data = new byte[1024];
                //得到客户机IP
                IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
                EndPoint remote = (EndPoint)sender;
                recv = newsock.ReceiveFrom(data, ref remote);
                bool isSeek = false;
                string ipport = remote.ToString();
                Console.WriteLine(ipport);
                for (int i = 0;i< endPointsCount; i++)
                {
                    if (!string.IsNullOrEmpty(endPoints[i]) && (endPoints[i].Equals(ipport)))
                    {
                        isSeek = true;
                    }
                }
                if(isSeek == false)
                {
                    if (endPointsCount < 999)
                    {
                        endPoints[endPointsCount++] = ipport;
                        //newsock.SendTo(data, data.Length, SocketFlags.None, remote);
                        Console.WriteLine("添加到列表中");
                    }
                    else
                    {
                        Console.WriteLine("设备太多了");
                    }
                }
                else
                {
                    Console.WriteLine("已经在列表中");
                }
                Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
                for (int i = 0; i < endPointsCount; i++)
                {
                    System.Net.IPAddress IPadr = System.Net.IPAddress.Parse(endPoints[i].Split(':')[0]);
                    System.Net.IPEndPoint EndPoint = new System.Net.IPEndPoint(IPadr, int.Parse(endPoints[i].Split(':')[1]));
                    newsock.SendTo(data, recv, SocketFlags.None, EndPoint);
                }
            }
        }

    }
}

客户端代码

using System.Net;
using System.Net.Sockets;
using System.Text;

namespace UDPClient
{
    class Program
    {
        public static Socket _server = null;
        public static bool _isClose = false;
        static void Main(string[] args)
        {
            try
            {
                byte[] data = new byte[1024];
                string input, stringData;

                //构建TCP 服务器

                Console.WriteLine("This is a Client, host name is {0}", Dns.GetHostName());

                //设置服务IP,设置TCP端口号
                IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8889);

                //定义网络类型,数据连接类型和网络协议UDP
                Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                _server = server;
                string welcome = "Hello! ";
                data = Encoding.ASCII.GetBytes(welcome);
                server.SendTo(data, data.Length, SocketFlags.None, ipep);

                data = new byte[1024];
                //对于不存在的IP地址,加入此行代码后,可以在指定时间内解除阻塞模式限制
                //server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 100);

                //创建一个线程接收远程主机发来的信息  
                Thread myThread = new Thread(ReceiveData);
                myThread.IsBackground = true;
                myThread.Start();
                while (true)
                {
                    input = Console.ReadLine();
                    if (input == "exit")
                    {
                        _isClose = true;
                        break;
                    }
                    server.SendTo(Encoding.ASCII.GetBytes(input), ipep);
                }
                Console.WriteLine("Stopping Client.");
                server.Close();
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        public static void ReceiveData()
        {
            try
            {
                IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
                EndPoint Remote = (EndPoint)sender;
                byte[] data = new byte[1024];
                while (true)
                {
                    try
                    {
                        if (_isClose == true)
                        {
                            break;
                        }
                        int recv = _server.ReceiveFrom(data, ref Remote);
                        Console.WriteLine("Message received from {0}: ", Remote.ToString());
                        Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                    }
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
}
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花开花落的个人博客

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值