Какой самый хороший способ разобрать это в C ++? - PullRequest
8 голосов
/ 02 июня 2010

В моей программе у меня есть список «адресов серверов» в следующем формате:

host[:port]

Скобки здесь указывают, что port является необязательным.

  • host может быть именем хоста, адресом IPv4 или IPv6 (возможно, в «скобках»).
  • port, если присутствует, может быть числовым номером порта или служебной строкой (например,: "http" или "ssh").

Если присутствует port и host является IPv6-адресом, host должен быть в скобкахнотация (Пример: [::1])

Вот несколько допустимых примеров:

localhost
localhost:11211
127.0.0.1:http
[::1]:11211
::1
[::1]

И недопустимый пример:

::1:80 // Invalid: Is this the IPv6 address ::1:80 and a default port, or the IPv6 address ::1 and the port 80 ?
::1:http // This is not ambigous, but for simplicity sake, let's consider this is forbidden as well.

Моя цель состоит в том, чтобы отделить такиезаписи в двух частях (очевидно, host и port).Мне все равно, являются ли host или port недействительными, если они не содержат : в скобках (290.234.34.34.5 подходит для host, оно будет отклонено вследующий процесс);Я просто хочу разделить две части, или если нет port части, чтобы знать это как-то.

Я пытался что-то сделать с std::stringstream, но все, что я придумываюкажется хакерским и не очень элегантным.

Как бы вы это сделали в C++?

Я не возражаю против ответов в C, но C++ предпочтительнее.Любое boost решение также приветствуется.

Спасибо.

Ответы [ 5 ]

9 голосов
/ 02 июня 2010

Вы смотрели на boost :: spirit ? Это может быть излишним для вашей задачи, хотя.

5 голосов
/ 02 июня 2010

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

Использование:

const std::string ip_address_str = "127.0.0.1:3282";
IpAddress ip_address = IpAddress::Parse(ip_address_str);
std::cout<<"Input String: "<<ip_address_str<<std::endl;
std::cout<<"Address Type: "<<IpAddress::TypeToString(ip_address.getType())<<std::endl;
if (ip_address.getType() != IpAddress::Unknown)
{
    std::cout<<"Host Address: "<<ip_address.getHostAddress()<<std::endl;
    if (ip_address.getPortNumber() != 0)
    {
        std::cout<<"Port Number: "<<ip_address.getPortNumber()<<std::endl;
    }
}

Заголовочный файл класса, IpAddress.h

#pragma once
#ifndef __IpAddress_H__
#define __IpAddress_H__


#include <string>

class IpAddress
{
public:
    enum Type
    {
        Unknown,
        IpV4,
        IpV6
    };
    ~IpAddress(void);

    /**
     * \brief   Gets the host address part of the IP address.
     * \author  Abi
     * \date    02/06/2010
     * \return  The host address part of the IP address.
    **/
    const std::string& getHostAddress() const;

    /**
     * \brief   Gets the port number part of the address if any.
     * \author  Abi
     * \date    02/06/2010
     * \return  The port number.
    **/
    unsigned short getPortNumber() const;

    /**
     * \brief   Gets the type of the IP address.
     * \author  Abi
     * \date    02/06/2010
     * \return  The type.
    **/
    IpAddress::Type getType() const;

    /**
     * \fn  static IpAddress Parse(const std::string& ip_address_str)
     *
     * \brief   Parses a given string to an IP address.
     * \author  Abi
     * \date    02/06/2010
     * \param   ip_address_str  The ip address string to be parsed.
     * \return  Returns the parsed IP address. If the IP address is
     *          invalid then the IpAddress instance returned will have its
     *          type set to IpAddress::Unknown
    **/
    static IpAddress Parse(const std::string& ip_address_str);

    /**
     * \brief   Converts the given type to string.
     * \author  Abi
     * \date    02/06/2010
     * \param   address_type    Type of the address to be converted to string.
     * \return  String form of the given address type.
    **/
    static std::string TypeToString(IpAddress::Type address_type);
private:
    IpAddress(void);

    Type m_type;
    std::string m_hostAddress;
    unsigned short m_portNumber;
};

#endif // __IpAddress_H__

Исходный файл для класса, IpAddress.cpp

#include "IpAddress.h"
#include <boost/xpressive/xpressive.hpp>

namespace bxp = boost::xpressive;

static const std::string RegExIpV4_IpFormatHost = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]+(\\:[0-9]{1,5})?$";
static const std::string RegExIpV4_StringHost = "^[A-Za-z0-9]+(\\:[0-9]+)?$";

IpAddress::IpAddress(void)
:m_type(Unknown)
,m_portNumber(0)
{
}

IpAddress::~IpAddress(void)
{
}

IpAddress IpAddress::Parse( const std::string& ip_address_str )
{
    IpAddress ipaddress;
    bxp::sregex ip_regex = bxp::sregex::compile(RegExIpV4_IpFormatHost);
    bxp::sregex str_regex = bxp::sregex::compile(RegExIpV4_StringHost);
    bxp::smatch match;
    if (bxp::regex_match(ip_address_str, match, ip_regex) || bxp::regex_match(ip_address_str, match, str_regex))
    {
        ipaddress.m_type = IpV4;
        // Anything before the last ':' (if any) is the host address
        std::string::size_type colon_index = ip_address_str.find_last_of(':');
        if (std::string::npos == colon_index)
        {
            ipaddress.m_portNumber = 0;
            ipaddress.m_hostAddress = ip_address_str;
        }else{
            ipaddress.m_hostAddress = ip_address_str.substr(0, colon_index);
            ipaddress.m_portNumber = atoi(ip_address_str.substr(colon_index+1).c_str());
        }
    }
    return ipaddress;
}

std::string IpAddress::TypeToString( Type address_type )
{
    std::string result = "Unknown";
    switch(address_type)
    {
    case IpV4:
        result = "IP Address Version 4";
        break;
    case IpV6:
        result = "IP Address Version 6";
        break;
    }
    return result;
}

const std::string& IpAddress::getHostAddress() const
{
    return m_hostAddress;
}

unsigned short IpAddress::getPortNumber() const
{
    return m_portNumber;
}

IpAddress::Type IpAddress::getType() const
{
    return m_type;
}

Я только установил правила для IPv4, потому что я не знаю правильный формат для IPv6. Но я уверен, что это не сложно реализовать. Boost Xpressive - это просто решение на основе шаблонов, поэтому для его компиляции в ваш exe-файл не требуется никаких файлов .lib, что, на мой взгляд, является плюсом.

Кстати, просто в двух словах разбить формат регулярного выражения ...
^ = начало строки
$ = конец строки
[] = группа букв или цифр, которые могут появляться
[0-9] = любая цифра от 0 до 9
[0-9] + = одна или несколько цифр от 0 до 9
"." имеет специальное значение для регулярных выражений, но поскольку наш формат имеет 1 точку в формате IP-адреса, нам нужно указать, что мы хотим, чтобы '.' между цифрами с помощью '\.'. Но так как C ++ нуждается в escape-последовательности для '\', нам придется использовать "\\."
? = дополнительный компонент

Итак, вкратце, "^ [0-9] + $" представляет регулярное выражение, которое верно для целого числа.
"^ [0-9] + \. $" означает целое число, оканчивающееся на '.'
"^ [0-9] + \. [0-9]? $" - это либо целое число, оканчивающееся на '.' или десятичная дробь.
Для целого или действительного числа регулярное выражение будет "^ [0-9] + (\. [0-9] *)? $" .
RegEx целое число от 2 до 3 чисел: "^ [0-9] {2,3} $" .

Теперь разбить формат IP-адреса:

"^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]+(\\:[0-9]{1,5})?$"

Это синонимы: "^ [0-9] {1,3} \. [0-9] {1,3} \. [0-9] {1,3} \. [0-9] ] + (\: [0-9] {1,5})? $ ", Что означает:

[start of string][1-3 digits].[1-3 digits].[1-3 digits].[1-3 digits]<:[1-5 digits]>[end of string]
Where, [] are mandatory and <> are optional

Второй RegEx проще, чем этот. Это просто комбинация буквенно-цифрового значения, за которым следует необязательное двоеточие и номер порта.

Кстати, если вы хотите протестировать RegEx, вы можете использовать этот сайт .

Редактировать : Я не заметил, что у вас есть http вместо номера порта. Для этого вы можете изменить выражение на:

"^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]+(\\:([0-9]{1,5}|http|ftp|smtp))?$"

Это принимает форматы как:
127.0.0.1
127.0.0.1:3282
127.0.0.1:http
217.0.0.1:ftp
18.123.2.1:smtp

3 голосов
/ 02 июня 2010
std::string host, port;
std::string example("[::1]:22");

if (example[0] == '[')
{
    std::string::iterator splitEnd =
        std::find(example.begin() + 1, example.end(), ']');
    host.assign(example.begin(), splitEnd);
    if (splitEnd != example.end()) splitEnd++;
    if (splitEnd != example.end() && *splitEnd == ':')
        port.assign(splitEnd, example.end());
}
else
{
    std::string::iterator splitPoint =
        std::find(example.rbegin(), example.rend(), ':').base();
    if (splitPoint == example.begin())
        host = example;
    else
    {
        host.assign(example.begin(), splitPoint);
        port.assign(splitPoint, example.end());
    }
}
0 голосов
/ 02 июня 2010

Как уже упоминалось, Boost.Spirit.Qi может справиться с этим.

Как уже упоминалось, это излишне (на самом деле).

const std::string line = /**/;

if (line.empty()) return;

std::string host, port;

if (line[0] == '[')           // IP V6 detected
{
  const size_t pos = line.find(']');
  if (pos == std::string::npos) return;  // Error handling ?
  host = line.substr(1, pos-1);
  port = line.substr(pos+2);
}
else if (std::count(line.begin(), line.end(), ':') > 1) // IP V6 without port
{
  host = line;
}
else                          // IP V4
{
  const size_t pos = line.find(':');
  host = line.substr(0, pos);
  if (pos != std::string::npos)
    port = line.substr(pos+1);
}

Я действительно не думаю, что это требует библиотеки разбора, она может не получить читабельности из-за перегруженного использования :.

Теперь мое решение, конечно, не безупречно, можно, например, задуматься о его эффективности ... но я действительно думаю, что этого достаточно, и, по крайней мере, вы не потеряете следующего сопровождающего, потому что из опыта выражения ци могут быть почти ясно!

0 голосов
/ 02 июня 2010

Если вы получаете порт и хост через строку или в C ++ массив символов;Вы могли бы получить длину строки.Делайте цикл for до конца строки и продолжайте до тех пор, пока не найдете одиночное двоеточие и разбейте строку на две части в этом месте.Я уверен, что есть более эффективный способ, но надеюсь, что это поможет, Гейл

...