v9.8.2018.0605   由DataReader直接映射实体列表,以支持netstandard的MySql和SQLite,且提升性能
大石头 编写于 2018-06-05 00:45:23
X
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using NewLife.Net.Sockets;
using NewLife.Reflection;

namespace NewLife.Net.Stun
{
    /// <summary>Stun服务端。Simple Traversal of UDP over NATs,NAT 的UDP简单穿越。RFC 3489</summary>
    /// <remarks>
    /// <a target="_blank" href="http://baike.baidu.com/view/884586.htm">STUN</a>
    /// </remarks>
    public class StunServer : NetServer
    {
        #region 属性
        private IDictionary<Int32, IPEndPoint> _Public;
        /// <summary>我的公网地址。因为当前服务器可能在内网中,需要调用StunClient拿公网地址</summary>
        public IDictionary<Int32, IPEndPoint> Public { get { return _Public; } private set { _Public = value; } }

        private IPEndPoint _Partner;
        /// <summary>伙伴地址。需要改变地址时,向该伙伴地址发送信息</summary>
        public IPEndPoint Partner { get { return _Partner; } set { _Partner = value; } }

        private Int32 _Port2;
        /// <summary>第二端口</summary>
        public Int32 Port2 { get { return _Port2; } set { _Port2 = value; } }
        #endregion

        #region 构造
        /// <summary>实例化</summary>
        public StunServer()
        {
            Name = GetType().Name.TrimEnd("Server");

            Port = 3478;
            Port2 = Port + 1;

            //// 同时两个端口
            //AddServer(IPAddress.Any, 3478, ProtocolType.Udp, AddressFamily.InterNetwork);
            //AddServer(IPAddress.Any, 3479, ProtocolType.Udp, AddressFamily.InterNetwork);
        }

        /// <summary>确保建立服务器</summary>
        public override void EnsureCreateServer()
        {
            if (Servers.Count <= 0)
            {
                // 同时两个端口
                AddServer(IPAddress.Any, Port, NetType.Unknown, AddressFamily.InterNetwork);
                AddServer(IPAddress.Any, Port2, NetType.Unknown, AddressFamily.InterNetwork);

                var dic = new Dictionary<Int32, IPEndPoint>();
                IPEndPoint ep = null;
                var pub = NetHelper.MyIP();
                StunResult rs = null;
                WriteLog("获取公网地址……");

                foreach (var item in Servers)
                {
                    if (item.Local.IsTcp) continue;

                    // 查询公网地址和网络类型,如果是受限网络,或者对称网络,则采用本地端口,因此此时只能依赖端口映射,将来这里可以考虑操作UPnP
                    if (rs == null)
                    {
                        item.Start();
                        rs = new StunClient(item).Query();
                        if (rs != null)
                        {
                            if (rs != null && rs.Type == StunNetType.Blocked && rs.Public != null) rs.Type = StunNetType.Symmetric;
                            WriteLog("网络类型:{0} {1}", rs.Type, rs.Type.GetDescription());
                            ep = rs.Public;
                            if (ep != null) pub = ep.Address;
                        }
                    }
                    else
                        ep = new StunClient(item).GetPublic();
                    if (rs != null && rs.Type > StunNetType.AddressRestrictedCone) ep = new IPEndPoint(pub, item.Port);
                    WriteLog("{0}的公网地址:{1}", item.Local, ep);
                    dic.Add(item.Port, ep);
                }
                // Tcp没办法获取公网地址,只能通过Udp获取到的公网地址加上端口形成,所以如果要使用Tcp,服务器必须拥有独立公网地址
                foreach (var item in Servers)
                {
                    if (item.Local.IsTcp)
                    {
                        ep = new IPEndPoint(pub, item.Port);
                        WriteLog("{0}的公网地址:{1}", item.Local, ep);
                        dic.Add(item.Port + 100000, ep);
                    }
                }
                //var ep = StunClient.GetPublic(Port, 2000);
                //WriteLog("端口{0}的公网地址:{1}", Port, ep);
                //dic.Add(Port, ep);
                //ep = StunClient.GetPublic(Port2, 2000);
                //WriteLog("端口{0}的公网地址:{1}", Port2, ep);
                //dic.Add(Port2, ep);
                WriteLog("成功获取公网地址!");
                Public = dic;
            }
        }
        #endregion

        #region 方法
        /// <summary>接收到数据时</summary>
        /// <param name="session"></param>
        /// <param name="stream"></param>
        protected override void OnReceive(INetSession session, Stream stream)
        {
            if (stream.Length > 0)
            {
                var remote = session.Remote;
                //if (remote == null && session != null) remote = session.RemoteEndPoint;

                var request = StunMessage.Read(stream);
                WriteLog("{0} {1} {2}{3}", request.Type, remote, request.ChangeIP ? " ChangeIP" : "", request.ChangePort ? " ChangePort" : "");

                // 如果是兄弟服务器发过来的,修正响应地址
                switch (request.Type)
                {
                    case StunMessageType.BindingRequest:
                        //case StunMessageType.BindingResponse:
                        request.Type = StunMessageType.BindingRequest;
                        if (request.ResponseAddress != null) remote.EndPoint = request.ResponseAddress;
                        break;
                    case StunMessageType.SharedSecretRequest:
                        //case StunMessageType.SharedSecretResponse:
                        request.Type = StunMessageType.SharedSecretRequest;
                        if (request.ResponseAddress != null) remote.EndPoint = request.ResponseAddress;
                        break;
                    default:
                        break;
                }

                // 是否需要发给伙伴
                if (request.ChangeIP)
                {
                    //if (Partner != null && !Partner.Equals(session.Host.LocalEndPoint.GetRelativeEndPoint(Partner.Address)))
                    //{
                    //    // 发给伙伴
                    //    request.ChangeIP = false;
                    //    // 记住对方的地址
                    //    request.ResponseAddress = remote.EndPoint;
                    //    //session.Send(request.GetStream(), Partner);
                    //    var us = session.Host as UdpServer;
                    //    if (us != null)
                    //    {
                    //        //us.CreateSession(Partner).Send(request.GetStream());
                    //        us.Send(request.GetStream(), Partner);
                    //    }
                    //    return;
                    //}
                    // 如果没有伙伴地址,采用不同端口代替
                    request.ChangePort = true;
                }

                // 开始分流处理
                switch (request.Type)
                {
                    case StunMessageType.BindingRequest:
                        //case StunMessageType.BindingResponse:
                        OnBind(request, session.Session);
                        break;
                    case StunMessageType.SharedSecretRequest:
                        break;
                    default:
                        break;
                }
            }
        }
        #endregion

        #region 绑定
        /// <summary>绑定</summary>
        /// <param name="request"></param>
        /// <param name="session"></param>
        /// <returns></returns>
        protected void OnBind(StunMessage request, ISocketSession session)
        {
            var rs = new StunMessage();
            rs.Type = StunMessageType.BindingResponse;
            rs.TransactionID = request.TransactionID.ReadBytes();
            rs.MappedAddress = session.Remote.EndPoint;
            //rs.SourceAddress = session.GetRelativeEndPoint(remote.Address);
            if (Public != null)
            {
                if (session.Local.IsTcp)
                    rs.SourceAddress = Public[session.Port + 100000];
                else
                    rs.SourceAddress = Public[session.Port];
            }

            // 找另一个
            ISocketSession session2 = null;
            var anotherPort = 0;
            for (var i = 0; i < Servers.Count; i++)
            {
                var server = Servers[i];
                if (server.Local.Type == session.Local.Type && server.Local.Port != session.Local.Port)
                {
                    anotherPort = server.Port;
                    if (server.Local.IsTcp)
                    {
                        break;
                    }
                    else
                    {
                        session2 = (server as UdpServer).CreateSession(session.Remote.EndPoint);
                        if (session2 != null) break;
                    }
                }
            }
            //rs.ChangedAddress = Partner ?? session2.GetRelativeEndPoint(remote.Address);
            if (Public != null)
            {
                if (session.Local.IsTcp)
                    rs.ChangedAddress = Partner ?? Public[anotherPort + 100000];
                else
                    rs.ChangedAddress = Partner ?? Public[anotherPort];
            }

            var name = Name;
            if (name == GetType().Name) name = GetType().FullName;
            rs.ServerName = String.Format("{0} v{1}", name, AssemblyX.Create(Assembly.GetExecutingAssembly()).CompileVersion);

            // 换成另一个
            if (request.ChangePort) session = session2;

            session.Send(rs.ToArray());
        }
        #endregion

        #region 辅助
        static Boolean IsResponse(StunMessageType type)
        {
            return ((UInt16)type & 0x0100) != 0;
        }
        #endregion
    }
}