IPAddress.Parse() используя порт на IPv4

Я пытаюсь разобрать строку, содержащую IP-адрес и порт, используя IPAddress.Parse. Это хорошо работает с адресами IPv6, но не с адресами IPv4. Может ли кто-нибудь объяснить, почему это происходит?

Код, который я использую:

IPAddress.Parse("[::1]:5"); //Valid
IPAddress.Parse("127.0.0.1:5"); //null

7 ответов

Uri url;
IPAddress ip;
if (Uri.TryCreate(String.Format("http://{0}", "127.0.0.1:5"), UriKind.Absolute, out url) &&
   IPAddress.TryParse(url.Host, out ip))
{
    IPEndPoint endPoint = new IPEndPoint(ip, url.Port);
}

Это происходит потому, что порт не является частью IP-адреса. Он принадлежит TCP/UDP, и вам придется сначала его удалить. Класс Uri может быть полезен для этого.

IP-адрес - это не IP+ порт. Вы хотите IPEndPoint.

Пример с http://www.java2s.com/Code/CSharp/Network/ParseHostString.htm

public static void ParseHostString(string hostString, ref string hostName, ref int port)
{
   hostName = hostString;
   if (hostString.Contains(":"))
   {
      string[] hostParts = hostString.Split(':');

      if (hostParts.Length == 2)
      {
         hostName = hostParts[0];
         int.TryParse(hostParts[1], out port);
      }
   }
}

Изменить: Хорошо, я признаю, что это было не самое элегантное решение. Попробуйте этот, который я написал (только для вас):

// You need to include some usings:
using System.Text.RegularExpressions;
using System.Net;

// Then this code (static is not required):
private static Regex hostPortMatch = new Regex(@"^(?<ip>(?:\[[\da-fA-F:]+\])|(?:\d{1,3}\.){3}\d{1,3})(?::(?<port>\d+))?$", System.Text.RegularExpressions.RegexOptions.Compiled);
public static IPEndPoint ParseHostPort(string hostPort)
{
   Match match = hostPortMatch.Match(hostPort);
   if (!match.Success)
      return null;

   return new IPEndPoint(IPAddress.Parse(match.Groups["ip"].Value), int.Parse(match.Groups["port"].Value));
}

Обратите внимание, что этот принимает только IP-адрес, а не имя хоста. Если вы хотите поддерживать имя хоста, вам придется разрешить его в IP или не использовать IPAddress/IPEndPoint.

IPAddress.Parse предназначен для получения строки, которая содержит IP-адрес в четырехточечной нотации для IPv4 и в шестнадцатеричной системе счисления для IPv6. Таким образом, ваш первый пример работает для IPv6, а второй - не работает, потому что он не поддерживает порт для IPv4. Ссылка http://msdn.microsoft.com/en-us/library/system.net.ipaddress.parse.aspx

Как указал Тедд Хансен, вы пытаетесь проанализировать не IP-адрес, а конечную точку IP (IP-адрес + порт). А начиная с .NET Core 3.0 вы можете использовать IPEndPoint.TryParse для анализа строки как IPEndPoint:

      if (IPEndPoint.TryParse("127.0.0.1:5", out IPEndPoint endpoint))
{
    // parsed successfully, you can use the "endpoint" variable
}
else
{
    // failed to parse
}

Если вы работаете со старыми версиями .net, вы можете использовать реализацию IPEndPoint.Parse из открытого исходного кода: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Primitives/src/System/ . Сеть/IPEndPoint.cs

Чтобы добавить мои два цента... Поскольку Microsoft сама реализовала TryParseв NET Core 3.0 я решил отказаться от использования собственного парсера IP+Port и любезно позаимствовал их код с некоторыми адаптациями:

      public static class IPEndPointParserExtension
{
    public static bool TryParseAsIPEndPoint(this string s, out IPEndPoint result) {
#if NETCOREAPP3_0_OR_GREATER
        return IPEndPoint.TryParse(s, out result);
#else
        int addressLength = s.Length;  // If there's no port then send the entire string to the address parser
        int lastColonPos = s.LastIndexOf(':');
        // Look to see if this is an IPv6 address with a port.
        if (lastColonPos > 0) {
            if (s[lastColonPos - 1] == ']') 
                addressLength = lastColonPos;
            // Look to see if this is IPv4 with a port (IPv6 will have another colon)
            else if (s.Substring(0, lastColonPos).LastIndexOf(':') == -1) 
                addressLength = lastColonPos;
        }
        if (IPAddress.TryParse(s.Substring(0, addressLength), out IPAddress address)) {
            long port = 0;
            if (addressLength == s.Length ||
                (long.TryParse(s.Substring(addressLength + 1), out port) 
                    && port <= IPEndPoint.MaxPort)) {
                result = new IPEndPoint(address, (int)port);
                return true;
            }
        }
        result = null;
        return false;
#endif
    }
    public static IPEndPoint AsIPEndPoint(this string s) => 
        s.TryParseAsIPEndPoint(out var endpoint) 
            ? endpoint 
            : throw new FormatException($"'{s}' is not a valid IP Endpoint");
   
}

Мои изменения заключались в основном в обмене Span<char>за stringи сделать его методом расширения класса Stringсам. Я также условно компилировал для использования реализации Microsoft, если она доступна (NET Core 3.0 или выше).

Следующие тесты nUnit показывают, как использовать код:

      [Test]
public void CanParseIpv4WithPort() {
    var sIp = "192.168.0.233:8080";
    if (sIp.TryParseAsIPEndPoint(out var endpoint)) {
        var expected = new IPEndPoint(new IPAddress(new byte[] { 192, 168, 0, 233 }), 8080);
        Assert.AreEqual(expected, endpoint);
    } else 
        Assert.Fail($"Failed to parse {sIp}");
}
[Test]
public void CanParseIpv6WithPort() {
    var sIp = "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443";
    if (sIp.TryParseAsIPEndPoint(out var endpoint)) {
        var expected = new IPEndPoint(IPAddress.Parse("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 443);
        Assert.AreEqual(expected, endpoint);
    } else
        Assert.Fail($"Failed to parse {sIp}");
}

Вы также можете использовать AsIpEndPointкоторый вызовет исключение, если ему не удастся проанализировать IP-адрес и порт (порт не является обязательным):

      var ep = "127.0.0.1:9000".AsIPEndPoint();
Другие вопросы по тегам