什么叫远场P2P穿越
- 远场P2P穿越(NAT穿越)是一种网络技术,用于实现不同NAT(网络地址转换)网络中的设备之间的直接通信,主要解决的是不同局域网下的设备如何建立直接连接的问题
相关概念
- NAT:将私有IP转换为公网IP的技术
- 穿透:突破NAT限制建立连接的过程
常见的穿透技术
- STUN(simple Traversaf of UDP through NAT)
struct stun_header {
unit16_t type;
unit16 length;
unit32_t cookie;
uint8_t transaction_id[12];
}
- TURN( Traversal Using Relays around NAT)
const char* TURN_SERVER = "turn:stun.example.com:3478";
const char* USERNAME = "user";
const char* PASSWORD = "pass";
- ICE (Interactive Connectivity Establishment)
enum class ICECandidateType {
Host,
ServerReflexive,
PeerReflexive,
Relay
};
实现远场P2P穿透的基本步骤
class P2PConnection {
public:
P2PConnection() {
initSTUN();
initTURN();
gatherCandidates();
}
private:
void initSTUN() {
const char* STUN_SERVER = "stun:stun.l.google.com:19302";
}
- 穿透流程:
- 设备A和B都连接到了STUN服务器
- 获取各自的公网IP和端口
- 通过信令服务器交换链接信息
- 尝试直接连接
- 如果直接连接失败,使用TURN中继
常见的应用场景
示例代码
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <thread>
#include <nice/agent.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <glib.h>
class P2PFileTransfer {
private:
NiceAgent* agent;
GMainLoop* loop;
guint stream_id;
SSL_CTX* ssl_ctx;
SSL* ssl;
std::string remote_credentials;
bool is_sender;
static const int BUFFER_SIZE = 8192;
public:
P2PFileTransfer(bool sender) : is_sender(sender) {
g_networking_init();
loop = g_main_loop_new(NULL, FALSE);
agent = nice_agent_new(g_main_loop_get_context(loop),
NICE_COMPATIBILITY_RFC5245);
g_object_set(G_OBJECT(agent),
"stun-server", "stun.l.google.com",
"stun-server-port", 19302,
NULL);
initializeSSL();
}
~P2PFileTransfer() {
g_object_unref(agent);
g_main_loop_unref(loop);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
}
private:
void initializeSSL() {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_method());
if (!ssl_ctx) {
throw std::runtime_error("SSL context creation failed");
}
if (SSL_CTX_use_certificate_file(ssl_ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
throw std::runtime_error("Certificate loading failed");
}
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, "key.pem", SSL_FILETYPE_PEM) <= 0) {
throw std::runtime_error("Private key loading failed");
}
}
static void candidateGatheringDone(NiceAgent* agent, guint stream_id, gpointer data) {
P2PFileTransfer* p2p = static_cast<P2PFileTransfer*>(data);
gchar* local_sdp = nice_agent_generate_local_sdp(agent);
std::cout << "Local SDP:\n" << local_sdp << std::endl;
g_free(local_sdp);
}
static void componentStateChanged(NiceAgent* agent, guint stream_id,
guint component_id, guint state,
gpointer data) {
if (state == NICE_COMPONENT_STATE_READY) {
std::cout << "ICE connection established!" << std::endl;
P2PFileTransfer* p2p = static_cast<P2PFileTransfer*>(data);
p2p->startTransfer();
}
}
public:
void setupConnection() {
stream_id = nice_agent_add_stream(agent, 1);
g_signal_connect(G_OBJECT(agent), "candidate-gathering-done",
G_CALLBACK(candidateGatheringDone), this);
g_signal_connect(G_OBJECT(agent), "component-state-changed",
G_CALLBACK(componentStateChanged), this);
nice_agent_gather_candidates(agent, stream_id);
std::thread loop_thread([this]() {
g_main_loop_run(this->loop);
});
loop_thread.detach();
}
void sendFile(const std::string& filename) {
if (!is_sender) {
throw std::runtime_error("This instance is not configured as sender");
}
std::ifstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file: " + filename);
}
file.seekg(0, std::ios::end);
size_t filesize = file.tellg();
file.seekg(0, std::ios::beg);
std::string file_info = filename + ":" + std::to_string(filesize);
SSL_write(ssl, file_info.c_str(), file_info.length());
std::vector<char> buffer(BUFFER_SIZE);
while (!file.eof()) {
file.read(buffer.data(), BUFFER_SIZE);
std::streamsize bytes_read = file.gcount();
if (bytes_read > 0) {
SSL_write(ssl, buffer.data(), bytes_read);
}
}
file.close();
}
void receiveFile() {
if (is_sender) {
throw std::runtime_error("This instance is not configured as receiver");
}
char info_buffer[1024];
int bytes = SSL_read(ssl, info_buffer, sizeof(info_buffer));
std::string file_info(info_buffer, bytes);
size_t pos = file_info.find(':');
std::string filename = file_info.substr(0, pos);
size_t filesize = std::stoull(file_info.substr(pos + 1));
std::ofstream file(filename, std::ios::binary);
std::vector<char> buffer(BUFFER_SIZE);
size_t total_received = 0;
while (total_received < filesize) {
int bytes = SSL_read(ssl, buffer.data(), BUFFER_SIZE);
if (bytes > 0) {
file.write(buffer.data(), bytes);
total_received += bytes;
}
}
file.close();
}
private:
void startTransfer() {
ssl = SSL_new(ssl_ctx);
if (is_sender) {
SSL_set_connect_state(ssl);
} else {
SSL_set_accept_state(ssl);
}
if (is_sender) {
sendFile("example.txt");
} else {
receiveFile();
}
}
};
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cout << "Usage: " << argv[0] << " [send|receive]" << std::endl;
return 1;
}
try {
bool is_sender = std::string(argv[1]) == "send";
P2PFileTransfer p2p(is_sender);
p2p.setupConnection();
std::cout << "Enter remote SDP: " << std::endl;
std::string remote_sdp;
std::getline(std::cin, remote_sdp);
std::string input;
std::getline(std::cin, input);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}