2010-06-02 3 views
8

В моей программе, у меня есть список «адрес сервера» в следующем формате:Каков наилучший способ разобрать это на C++?

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 также приветствуется.

спасибо.

+0

ли Вы, выбрать этот формат? –

+0

Я не уверен, что понимаю, что вы подразумеваете под «выбрать». Я сам разработал формат (если это то, что вы просите), но я считаю, что он очень распространен. Я мог бы использовать другой разделитель, но я не думаю, что это было бы элегантно. Пример: «Подключение к localhost $ http» кажется менее интуитивным, чем «Подключение к localhost: http». – ereOn

+0

Если вы не хотите менять разделитель (localhost-http кажется мне правдоподобным), тогда вы можете заставить все хосты заключить в скобки, иначе regex сделает это – Patrick

ответ

9

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

+1

Не знал, что он существует. Благодарю. Однако, как вы только что сказали, для моей задачи, похоже, слишком много. Я никого больше не придумаю что-то более прямолинейное, я, конечно же, буду глубоко вникать в это. – ereOn

+0

Поскольку сообщество, похоже, любит это решение, может кто-нибудь, пожалуйста, дайте мне несколько рекомендаций, чтобы начать с 'boost :: spirit' в моем конкретном случае? – ereOn

+1

Принесите полотенце! –

-3

Если вы получаете порт и хост через строку или на C++ массив символов; вы можете получить длину строки. Сделайте цикл for до конца строки и пройдите до тех пор, пока вы не найдете один двоеточие самостоятельно и разделите строку на две части в этом месте.

for (int i=0; i<string.length; i++) { 
    if (string[i] == ':') { 
      if (string[i+1] != ':') { 
       if (i > 0) { 
        if (string[i-1] != ':') { 
         splitpoint = i; 
} } } } } 

Просто предложение его любопытное глубоко, и я уверен, что есть более эффективный способ, но надеюсь, что это помогает, Gale

+2

Вы знаете, что вы можете комбинировать условные обозначения с '&&'? 'if (string [i] == ':' && string [i + 1]! = ':' && i> 0 && string [i-1]! = ':')' –

+0

Спасибо за ваш ответ. Но это еще более хладнокровно, что я подошел;) И я не уверен, что это обрабатывает специальные случаи «IPv6». – ereOn

+1

@ Майкл - Да, я знаю, что вы можете, однако, попытаться выполнить сравнение строки [i-1] одновременно с проверкой, если i> 0, тогда вы будете бросать ошибки, потому что вы не можете получить доступ к строке [ -1], и я просто бросил его вместе = P @ereOn - Нет проблем, просто подумал, что я дам первое, что появилось у меня в голове. – geshafer

0

Как уже упоминалось, 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

Спасибо! Вероятно, не оптимальный, но вполне читаемый. Однако, что произойдет, если я поставлю следующую строку: '" [:: 1: 22 "'? – ereOn

+0

':: 1: 22' будет считаться хостом: здесь вообще нет обработки ошибок, вы можете убедиться, что в первом случае есть закрывающая скобка' assert (pos! = Std :: string :: npos) 'или что бы вы ни пожелали :) –

+0

ли std :: string имеет функцию count()? Это дает мне ошибки в VC2008. error C2039: 'count': не является членом 'std :: basic_string <_Elem, _Traits, _Ax>' –

5

Вот простой класс, который использует 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 - это просто решение на основе шаблонов и, следовательно, не требует, чтобы файлы .lib были скомпилированы в ваш exe, что, по моему мнению, является плюсом.

Кстати просто сломать формат регулярных выражений в двух словах ...
^ = начало строки
$ = конец строки
[] = группа букв или цифр, которые могут появиться
[0-9] = любой одной цифры от 0 до 9
[0-9] + = одна или более цифр от 0 до 9-го
e '.' имеет особое значение для регулярного выражения, но поскольку наш формат имеет 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 вы можете использовать this site

Edit:. Я не заметил, что вы необязательно имели 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

+7

Когда у людей возникает проблема, они говорят: я знаю, я буду использовать регулярное выражение. Теперь у них есть 2 проблемы. –

+0

LOL. Это не слишком сложно понять. Я узнал менее чем за 2 часа. Это не похоже на то, что он не знает формат и что у него еще нет решения. Если я прав, у него уже есть решение, использующее std :: stringstream, и он хочет элегантное решение. Я добавлю сообщение регулярного выражения в сообщении. –

+0

Раздутый. Регулярные выражения всегда работали отлично для меня. Уход за всеми моими ленивыми коллегами. – Jay

3
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

В части IPV6 условие '&&' on 'splitEnd' кажется сомнительным. Вы вызываете неопределенное поведение ... и с тех пор, как искали '' 'Я не понимаю, как итератор мог указать на': 'для начала. –

+0

@ Matthieu M .: Вы правы, это должно было быть! =. –

+0

Я до сих пор не понимаю, как это может быть: «Не означает ли вы (* (splitEnd ++) == ':')? (хотя снова возникнет риск неопределенного поведения). –

Смежные вопросы