[fix]修正UdpServer在接收广播时连续启动接收的错误,在StarAgent中,此时可能收到广播包,SocketFlags是Broadcast,需要清空,否则报错“参考的对象类型不支持尝试的操作”; 无需设置SocketOptionName.PacketInformation,在ReceiveMessageFromAsync时会自动设置,并且支持ipv6;
石头 编写于 2024-10-10 00:36:00 石头 提交于 2024-10-10 00:45:43
X
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Xml.Serialization;

namespace NewLife.Net;

/// <summary>协议类型</summary>
public enum NetType : Byte
{
    /// <summary>未知协议</summary>
    Unknown = 0,

    /// <summary>传输控制协议</summary>
    Tcp = 6,

    /// <summary>用户数据报协议</summary>
    Udp = 17,

    /// <summary>Http协议</summary>
    Http = 80,

    /// <summary>Https协议</summary>
    Https = 43,

    /// <summary>WebSocket协议</summary>
    WebSocket = 81
}

/// <summary>网络资源标识,指定协议、地址、端口、地址族(IPv4/IPv6)</summary>
/// <remarks>
/// 仅序列化<see cref="Type"/>和<see cref="EndPoint"/>,其它均是配角!
/// 有可能<see cref="Host"/>代表主机域名,而<see cref="Address"/>指定主机IP地址。
/// </remarks>
public class NetUri
{
    #region 属性
    /// <summary>协议类型</summary>
    public NetType Type { get; set; }

    /// <summary>主机或域名</summary>
    /// <remarks>可能对应多个IP地址</remarks>
    public String? Host { get; set; }

    /// <summary>地址</summary>
    /// <remarks>
    /// 域名多地址时的第一个。
    /// 设置地址后,反向覆盖Host。
    /// </remarks>
    [XmlIgnore, IgnoreDataMember]
    public IPAddress Address { get { return EndPoint.Address; } set { EndPoint.Address = value; } }

    /// <summary>端口</summary>
    public Int32 Port { get { return EndPoint.Port; } set { EndPoint.Port = value; } }

    [NonSerialized]
    private IPEndPoint? _EndPoint;
    /// <summary>终结点</summary>
    /// <remarks>
    /// 域名多地址时的第一个。
    /// 设置地址后,反向覆盖Host。
    /// </remarks>
    [XmlIgnore, IgnoreDataMember]
    public IPEndPoint EndPoint
    {
        get
        {
            var ep = _EndPoint;
            ep ??= _EndPoint = new IPEndPoint(IPAddress.Any, 0);
            if ((ep.Address == null || ep.Address.IsAny()) && !Host.IsNullOrEmpty()) ep.Address = ParseAddress(Host)?.FirstOrDefault() ?? IPAddress.Any;

            return ep;
        }
        set
        {
            var ep = _EndPoint = value;
            Host = ep?.Address?.ToString();
        }
    }
    #endregion

    #region 扩展属性
    /// <summary>是否Tcp协议</summary>
    [XmlIgnore, IgnoreDataMember]
    public Boolean IsTcp => Type == NetType.Tcp;

    /// <summary>是否Udp协议</summary>
    [XmlIgnore, IgnoreDataMember]
    public Boolean IsUdp => Type == NetType.Udp;
    #endregion

    #region 构造
    /// <summary>实例化</summary>
    public NetUri() { }

    /// <summary>实例化</summary>
    /// <param name="uri"></param>
    public NetUri(String uri) => Parse(uri);

    /// <summary>实例化</summary>
    /// <param name="protocol"></param>
    /// <param name="endpoint"></param>
    public NetUri(NetType protocol, IPEndPoint endpoint)
    {
        Type = protocol;
        _EndPoint = endpoint;
    }

    /// <summary>实例化</summary>
    /// <param name="protocol"></param>
    /// <param name="address"></param>
    /// <param name="port"></param>
    public NetUri(NetType protocol, IPAddress address, Int32 port)
    {
        Type = protocol;
        Address = address;
        Port = port;
    }

    /// <summary>实例化</summary>
    /// <param name="protocol"></param>
    /// <param name="host"></param>
    /// <param name="port"></param>
    public NetUri(NetType protocol, String host, Int32 port)
    {
        Type = protocol;
        Host = host;
        Port = port;
    }
    #endregion

    #region 方法
    static readonly String Sep = "://";

    /// <summary>分析</summary>
    /// <param name="uri"></param>
    public NetUri Parse(String uri)
    {
        if (uri.IsNullOrWhiteSpace()) return this;

        // 分析协议
        var protocol = "";
        var array = uri.Split(Sep);
        if (array.Length >= 2)
        {
            protocol = array[0]?.Trim() + "";
            Type = ParseType(protocol);
            uri = array[1]?.Trim() + "";
        }

        if (uri.IsNullOrWhiteSpace()) return this;

        Host = null;
        _EndPoint = null;

        // 特殊协议端口
        switch (protocol.ToLower())
        {
            case "http":
            case "ws":
                Port = 80;
                break;
            case "https":
            case "wss":
                Port = 443;
                break;
        }

        // 这个可能是一个Uri,去掉尾部
        var p = uri.IndexOf('/');
        if (p < 0) p = uri.IndexOf('\\');
        if (p < 0) p = uri.IndexOf('?');
        if (p >= 0) uri = uri[..p]?.Trim() + "";

        // 分析端口,冒号前一个不能是冒号
        p = uri.LastIndexOf(':');
        if (p >= 0 && (p < 1 || uri[p - 1] != ':'))
        {
            var pt = uri[(p + 1)..];
            if (Int32.TryParse(pt, out var port))
            {
                Port = port;
                uri = uri[..p]?.Trim() + "";
            }
        }

        if (IPAddress.TryParse(uri, out var address))
            Address = address;
        else
            Host = uri;

        return this;
    }

    private static NetType ParseType(String? value)
    {
        if (value.IsNullOrEmpty()) return NetType.Unknown;

        try
        {
            if (value.EqualIgnoreCase("Http", "Https")) return NetType.Http;
            if (value.EqualIgnoreCase("ws", "wss")) return NetType.WebSocket;

            return (NetType)(Int32)Enum.Parse(typeof(ProtocolType), value, true);
        }
        catch { return NetType.Unknown; }
    }

    /// <summary>获取该域名下所有IP地址</summary>
    /// <returns></returns>
    public IPAddress[] GetAddresses() => ParseAddress(Host) ?? [Address];

    /// <summary>获取该域名下所有IP节点(含端口)</summary>
    /// <returns></returns>
    public IPEndPoint[] GetEndPoints() => GetAddresses().Select(e => new IPEndPoint(e, Port)).ToArray();

    /// <summary>克隆</summary>
    /// <returns></returns>
    public NetUri Clone() => new() { Type = Type, Host = Host, Port = Port, Address = Address };
    #endregion

    #region 辅助
    /// <summary>分析地址</summary>
    /// <param name="hostname">主机地址</param>
    /// <returns></returns>
    public static IPAddress[]? ParseAddress(String? hostname)
    {
        if (hostname.IsNullOrEmpty()) return null;
        if (hostname == "*") return null;

        try
        {
            if (IPAddress.TryParse(hostname, out var addr)) return [addr];

            var hostAddresses = DnsResolver.Instance.Resolve(hostname);
            if (hostAddresses == null || hostAddresses.Length <= 0) return null;

            return hostAddresses.Where(d => d.AddressFamily is AddressFamily.InterNetwork or AddressFamily.InterNetworkV6).ToArray();
        }
        catch (SocketException ex)
        {
            throw new XException($"Failed to resolve the address of host {hostname}!{ex.Message}", ex);
        }
    }

    /// <summary>已重载。</summary>
    /// <returns></returns>
    public override String ToString()
    {
        var protocol = Type.ToString().ToLower();
        switch (Type)
        {
            case NetType.Unknown:
                protocol = "";
                break;
            case NetType.WebSocket:
                protocol = Port == 443 ? "wss" : "ws";
                break;
        }
        var host = Host;
        if (host.IsNullOrEmpty())
        {
            if (Address.AddressFamily == AddressFamily.InterNetworkV6 && Port > 0)
                host = $"[{Address}]";
            else
                host = Address + "";
        }

        if (Port > 0)
            return $"{protocol}://{host}:{Port}";
        else
            return $"{protocol}://{host}";
    }
    #endregion

    #region 重载运算符
    /// <summary>重载类型转换,字符串直接转为NetUri对象</summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static implicit operator NetUri(String value) => new(value);
    #endregion
}