[英]What is the nicest way to parse this in 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
解決方案也是受歡迎的。
謝謝你。
你看過boost::spirit嗎? 不過,這對你的任務來說可能有點矯枉過正。
這是一個簡單的類,它使用 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 中,我相信 make 是一個加分項。
順便說一下,簡而言之,只是為了分解正則表達式的格式......
^ = 字符串開始
$ = 字符串結尾
[] = 一組可以出現的字母或數字
[0-9] = 0 到 9 之間的任何一位數
[0-9]+ = 0 到 9 之間的一位或多位數字
這 '。' 對正則表達式有特殊意義,但由於我們的格式在 ip 地址格式中有 1 個點,我們需要指定我們想要一個 '.' 數字之間使用'\\.'。 但是由於 C++ 需要為 '\\' 設置一個轉義序列,所以我們必須使用“\\\\”。
? = 可選組件
所以,簡而言之, “^[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
我參加聚會遲到了,但我正在谷歌上搜索如何做到這一點。 Spirit 和 C++ 已經成長了很多,所以讓我補充一下 2021 年的情況:
#include <fmt/ranges.h>
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
auto parse_server_address(std::string_view address_spec,
std::string_view default_service = "https")
{
using namespace boost::spirit::x3;
auto service = ':' >> +~char_(":") >> eoi;
auto host = '[' >> *~char_(']') >> ']' // e.g. for IPV6
| raw[*("::" | (char_ - service))];
std::tuple<std::string, std::string> result;
parse(begin(address_spec), end(address_spec),
expect[host >> (service | attr(default_service))], result);
return result;
}
int main() {
for (auto input : {
"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.
})
{
// auto [host, svc] = parse_server_address(input);
fmt::print("'{}' -> {}\n", input, parse_server_address(input));
}
}
印刷
'localhost' -> ("localhost", "https")
'localhost:11211' -> ("localhost", "11211")
'127.0.0.1:http' -> ("127.0.0.1", "http")
'[::1]:11211' -> ("::1", "11211")
'::1' -> ("::1", "https")
'[::1]' -> ("::1", "https")
'::1:80' -> ("::1", "80")
'::1:http' -> ("::1", "http")
驗證/解析地址。 解析是 100% 不變的,只是使用 Asio 來解析結果,同時驗證它們:
#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
using boost::asio::ip::tcp;
using boost::asio::system_executor;
using boost::system::error_code;
int main() {
tcp::resolver r(system_executor{});
error_code ec;
for (auto input : {
"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.
"stackexchange.com",
"unknown-host.xyz",
})
{
auto [host, svc] = parse_server_address(input);
for (auto&& endpoint : r.resolve({host, svc}, ec)) {
std::cout << input << " -> " << endpoint.endpoint() << "\n";
}
if (ec.failed()) {
std::cout << input << " -> unresolved: " << ec.message() << "\n";
}
}
}
打印(有限網絡Live On Wandbox和 Coliru http://coliru.stacked-crooked.com/a/497d8091b40c9f2d )
localhost -> 127.0.0.1:443
localhost:11211 -> 127.0.0.1:11211
127.0.0.1:http -> 127.0.0.1:80
[::1]:11211 -> [::1]:11211
::1 -> [::1]:443
[::1] -> [::1]:443
::1:80 -> [::1]:80
::1:http -> [::1]:80
stackexchange.com -> 151.101.129.69:443
stackexchange.com -> 151.101.1.69:443
stackexchange.com -> 151.101.65.69:443
stackexchange.com -> 151.101.193.69:443
unknown-host.xyz -> unresolved: Host not found (authoritative)
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());
}
}
如前所述,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 表達幾乎可以清楚!
#pragma once
#ifndef ENDPOINT_HPP
#define ENDPOINT_HPP
#include <string>
using std::string;
struct Endpoint {
string
Host,
Port;
enum : char {
V4,
V6
} Type = V4;
__inline Endpoint(const string& text) {
bind(text);
}
private:
void __fastcall bind(const string& text) {
if (text.empty())
return;
auto host { text };
string::size_type bias = 0;
constexpr auto NONE = string::npos;
while (true) {
bias = host.find_first_of(" \n\r\t", bias);
if (bias == NONE)
break;
host.erase(bias, 1);
}
if (host.empty())
return;
auto port { host };
bias = host.find(']');
if (bias != NONE) {
host.erase(bias);
const auto skip = text.find('[');
if (skip == NONE)
return;
host.erase(0, skip + 1);
Type = V6;
++bias;
}
else {
bias = host.find(':');
if (bias == NONE)
port.clear();
else {
const auto next = bias + 1;
if (host.length() == next)
return;
if (host[next] == ':') {
port.clear();
Type = V6;
}
else if (! bias)
host.clear();
else
host.erase(bias);
}
}
if (! port.empty())
Port = port.erase(0, bias + 1);
if (! host.empty())
Host = host;
}
};
#endif // ENDPOINT_HPP
如果您通過字符串或 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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.