В моей программе у меня есть список "адреса сервера" в следующем формате:
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
недопустимы, пока они не содержат non-bracket-enclosed :
(290.234.34.34.5
хорошо для host
, это будет отклонено в следующем процессе); я просто хочу разделить эти две части, или если существует нет port
часть, для знания этого так или иначе.
Я пытался сделать что-то с std::stringstream
но все, к чему я подхожу, кажется hacky и не действительно изящное.
Как Вы выполнили бы в этом C++
?
Я не возражаю против ответов в C
но C++
предпочтен.Любой boost
решение приветствуется также.
Спасибо.
Вы смотрели на boost::spirit? Хотя для вашей задачи это может оказаться излишеством.
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());
}
}
Если вы получаете порт и хост через строку или в 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;
} } } } }
Просто предложение, оно довольно глубокое, и я уверен, что есть более эффективный способ, но надеюсь, что это поможет, Гейл
Как уже говорилось, 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);
}
Я действительно не думаю, что это требует библиотеки синтаксического анализа, это может не улучшить читабельность из-за перегруженного использования :
.
Теперь мое решение, конечно, не безупречно, можно, например, задаться вопросом о его эффективности... но я действительно считаю его достаточным, и, по крайней мере, вы не потеряете следующего сопровождающего, потому что, судя по опыту, выражения Qi могут быть совершенно однозначными!
Вот простой класс, который использует 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, что я считаю плюсом.
Кстати, чтобы в двух словах рассказать о формате regex...
^ = начало строки
$ = конец строки
[] = группа букв или цифр, которые могут появляться
[0-9] = любая однозначная цифра от 0 до 9
[0-9]+ = одна или несколько цифр от 0 до 9
'.' имеет специальное значение для regex, но поскольку наш формат имеет 1 точку в формате ip-адреса, нам нужно указать, что мы хотим '.' между цифрами, используя '\.'. Но поскольку в C++ для '\' нужна управляющая последовательность, нам придется использовать "\\\."
( = необязательный компонент
Итак, вкратце, "^[0-9]+$" представляет собой regex, что верно для целого числа.
"^[0-9]+\.$" означает целое число, которое заканчивается на '.'
"^[0-9]+\.[0-9]?$" - это либо целое число, которое заканчивается на '.', либо десятичная дробь.
Для целого или действительного числа, regex будет "^[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, вы можете использовать этот сайт.
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