100 lines
3.6 KiB
C++
100 lines
3.6 KiB
C++
// posix_net_helpers.h
|
||
// Linux/macOS 客户端共用的网络/Shell 工具:execCmd / httpGet / getPublicIP /
|
||
// jsonExtract / getGeoLocation。Windows 端已有等价实现,不应包含此头。
|
||
//
|
||
// 全部 inline,header-only,避免新增 .cpp / 改 CMakeLists。
|
||
//
|
||
// 设计说明:
|
||
// - httpGet 优先 curl,备选 wget(Linux 默认自带;macOS 默认无 wget,缺失时
|
||
// wget 命令失败、execCmd 返空——无副作用,等价于"只用 curl")
|
||
// - getPublicIP 轮询多个公网 IP 查询源,按顺序尝试直到成功
|
||
// - jsonExtract 仅做最简单的 "key":"value" 提取,不依赖 jsoncpp
|
||
// - getGeoLocation 通过 ipinfo.io 反查地理位置,与 Windows IPConverter 同源
|
||
#pragma once
|
||
|
||
#include <cstdio>
|
||
#include <memory>
|
||
#include <string>
|
||
|
||
#include "common/logger.h"
|
||
|
||
namespace PosixNet {
|
||
|
||
// 执行 shell 命令,捕获其 stdout 输出(trim 末尾空白后返回)
|
||
inline std::string execCmd(const std::string& cmd)
|
||
{
|
||
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
|
||
if (!pipe) return "";
|
||
char buf[4096];
|
||
std::string result;
|
||
while (fgets(buf, sizeof(buf), pipe.get())) {
|
||
result += buf;
|
||
}
|
||
while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' '))
|
||
result.pop_back();
|
||
return result;
|
||
}
|
||
|
||
// HTTP GET 请求:优先 curl,备选 wget
|
||
inline std::string httpGet(const std::string& url, int timeoutSec = 5)
|
||
{
|
||
std::string t = std::to_string(timeoutSec);
|
||
std::string r = execCmd("curl -s --max-time " + t + " \"" + url + "\" 2>/dev/null");
|
||
if (!r.empty()) return r;
|
||
r = execCmd("wget -qO- --timeout=" + t + " \"" + url + "\" 2>/dev/null");
|
||
return r;
|
||
}
|
||
|
||
// 获取公网 IP(轮询多个查询源,与 Windows 端 IPConverter 一致)
|
||
inline std::string getPublicIP()
|
||
{
|
||
static const char* urls[] = {
|
||
"https://checkip.amazonaws.com",
|
||
"https://api.ipify.org",
|
||
"https://ipinfo.io/ip",
|
||
"https://icanhazip.com",
|
||
"https://ifconfig.me/ip",
|
||
};
|
||
for (auto& url : urls) {
|
||
std::string ip = httpGet(url, 3);
|
||
if (!ip.empty() && ip.find('.') != std::string::npos && ip.size() <= 45) {
|
||
Mprintf("getPublicIP: %s (from %s)\n", ip.c_str(), url);
|
||
return ip;
|
||
}
|
||
}
|
||
Mprintf("getPublicIP: all sources failed\n");
|
||
return "";
|
||
}
|
||
|
||
// 从 JSON 字符串中提取指定 key 的 string 值(简易解析,不依赖 jsoncpp)
|
||
// 仅支持 "key": "value" 或 "key":"value" 格式
|
||
inline std::string jsonExtract(const std::string& json, const std::string& key)
|
||
{
|
||
std::string needle = "\"" + key + "\"";
|
||
size_t pos = json.find(needle);
|
||
if (pos == std::string::npos) return "";
|
||
pos = json.find(':', pos + needle.size());
|
||
if (pos == std::string::npos) return "";
|
||
pos = json.find('"', pos + 1);
|
||
if (pos == std::string::npos) return "";
|
||
size_t end = json.find('"', pos + 1);
|
||
if (end == std::string::npos) return "";
|
||
return json.substr(pos + 1, end - pos - 1);
|
||
}
|
||
|
||
// 获取 IP 地理位置(ipinfo.io,与 Windows IPConverter 同源)
|
||
inline std::string getGeoLocation(const std::string& ip)
|
||
{
|
||
if (ip.empty()) return "";
|
||
std::string json = httpGet("https://ipinfo.io/" + ip + "/json", 5);
|
||
if (json.empty()) return "";
|
||
std::string country = jsonExtract(json, "country");
|
||
std::string city = jsonExtract(json, "city");
|
||
if (city.empty() && country.empty()) return "";
|
||
if (city.empty()) return country;
|
||
if (country.empty()) return city;
|
||
return city + ", " + country;
|
||
}
|
||
|
||
} // namespace PosixNet
|