P2P通讯的C++实现-UDP Hole Punching方法

41 篇文章 0 订阅

Title: P2P通讯的C++实现-UDP Hole Punching方法

整理时间: 2016-03-24 by kagula

 

UDP Hole Punching方法,需要服务端。

假设有AB两个客户端和S一个服务器

Step 1 : AB发送UDP请求给SS知道了AB在公网的IP和端口。

Step 2: AS中取B在公网的IP和端口。

             BS中取A在公网的IP和端口。

Step 3: A通过B在公网的IP和端口向B发送UDP请求。

              B通过A在公网的IP和端口向A发送UDP请求。

Step Final:

              AB之间可以通过UDP直接互相发送信息。


目前看来这种方法最大的缺点, 还是需要服务器。


整理后的服务端文件

#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using boost::asio::ip::udp;

class udp_server
{
public:
	udp_server(boost::asio::io_service &io_service) :
		_sock(io_service, udp::endpoint(udp::v4(), 2333))
	{
		session_receive();
	}
	void session_receive();
	void handle_receive(const boost::system::error_code &ec, std::size_t len);
	void session_send(udp::endpoint &ep, std::string write_message);
	void handle_send(const boost::system::error_code &ec, std::size_t len);
private:
	udp::socket _sock;
	boost::array<char, 256> _recv_buffer;
	std::string _write_message;
	std::list<udp::endpoint> _endpoint_list;
	udp::endpoint _remote_endpoint;//current endpoint
};

void udp_server::session_receive()
{
	_sock.async_receive_from(
		boost::asio::buffer(_recv_buffer),
		_remote_endpoint,
		boost::bind(&udp_server::handle_receive,
		this,
		boost::asio::placeholders::error,
		boost::asio::placeholders::bytes_transferred));
}

void udp_server::handle_receive(const boost::system::error_code &ec, std::size_t len)
{
	std::string receive_message(_recv_buffer.data(), len);
	if (strcmp(receive_message.c_str(), "login") == 0)
	{
		int isLogged = 0;
		for (std::list<udp::endpoint>::iterator iter = _endpoint_list.begin(); iter != _endpoint_list.end(); ++iter)
		{
			if (*iter == _remote_endpoint)
			{
				session_send(_remote_endpoint, "You have already logged in.\n");
				isLogged = 1;
				break;
			}
			if (!isLogged)
			{
				std::cout << "User login.\nAddress : " << _remote_endpoint.address().to_string() << std::endl;
				std::cout << "Port : " << _remote_endpoint.port() << std::endl;
				_endpoint_list.push_back(_remote_endpoint);
				session_send(_remote_endpoint, "login success.\n");
			}
		}
	}
	else if (strcmp(receive_message.c_str(), "logout") == 0)
	{
		int isLogged = 0;
		for (std::list<udp::endpoint>::iterator iter = _endpoint_list.begin(); iter != _endpoint_list.end(); ++iter)
		{
			if (*iter == _remote_endpoint)
			{
				isLogged = 1;
				_endpoint_list.erase(iter);
				std::cout << "User logout.\nAddress : " << _remote_endpoint.address().to_string() << std::endl;
				std::cout << "Port : " << _remote_endpoint.port() << std::endl;
				session_send(_remote_endpoint, "Logout success.\n");
				break;
			}
		}
		if (!isLogged)
			session_send(_remote_endpoint, "Logout failed, you have not logged in.\n");
	}
	else if (strcmp(receive_message.c_str(), "list") == 0)
	{
		std::ostringstream message;
		int i = 0;
		for (std::list<udp::endpoint>::iterator iter = _endpoint_list.begin(); iter != _endpoint_list.end(); ++iter)
		{
			if (*iter == _remote_endpoint)
				message << "[" << i << "]" << iter->address().to_string() << ":" << iter->port() << " (yourself)" << std::endl;
			else
				message << "[" << i << "]" << iter->address().to_string() << ":" << iter->port() << std::endl;
			i++;
		}
		session_send(_remote_endpoint, message.str());
	}
	else if (strncmp(receive_message.c_str(), "punch", 5) == 0)
	{
		int punched_client = atoi(receive_message.c_str() + 6);
		std::list<udp::endpoint>::iterator iter = _endpoint_list.begin();
		for (int i = 0; i < punched_client && iter != _endpoint_list.end(); ++i, ++iter);
		std::ostringstream message;
		if (iter == _endpoint_list.end())
			message << "Punch failed, no such client.";
		else
		{
			std::ostringstream peer_message;
			//udp::endpoint peer_endpoint(iter->address(), iter->port);
			peer_message << "PUNCH_REQUEST " << _remote_endpoint.address().to_string() << ":" << _remote_endpoint.port() << std::endl;
			session_send(*iter, peer_message.str());
			message << "PUNCH_SUCCESS " << iter->address().to_string() << ":" << iter->port() << std::endl;
		}
		session_send(_remote_endpoint, message.str());
	}
	else if (strcmp(receive_message.c_str(), "help") == 0)
	{
		session_send(_remote_endpoint, "Command:\
									   \n\thelp : Show this information.\
									   \n\tlogin : Login p2p server to make you punchable.\
									   \n\tlogout : Logout p2p server so that other client(s) won't find you.\
									   \n\tlist: List all client(s) that have login.\
									   \n\tpunch <client_number>: send punch request to remote client and start a p2p session\n");
	}
	else
	{
		session_send(_remote_endpoint, "Unknown command, please type 'help' to see more options.\n");
	}
	session_receive();
}

void udp_server::session_send(udp::endpoint &ep, std::string write_message)
{
	//std::getline(std::cin, _write_message);
	_sock.async_send_to(boost::asio::buffer(write_message),
		ep,
		boost::bind(&udp_server::handle_send,
		this,
		boost::asio::placeholders::error,
		boost::asio::placeholders::bytes_transferred));
}

void udp_server::handle_send(const boost::system::error_code &ec, std::size_t len)
{
	//session_send();
}

int main(int argc, char *argv[])
{
	boost::asio::io_service io_service;
	udp_server server(io_service);
	io_service.run();
	return 0;
}


整理后的客户端文件

#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <cstring>
#include <cstdlib>
boost::shared_mutex io_mutex;

namespace
{
	int p2p_connect = 0;
}

using boost::asio::ip::udp;
class udp_client
{
public:
	udp_client(boost::asio::io_service &io_service, const char *host, const char *port):_sock(io_service)
	{
		udp::resolver _resolver(io_service);
		udp::resolver::query _query(udp::v4(), host, port);
		_server_endpoint = *_resolver.resolve(_query);
		_sock.open(udp::v4());
		start_send(); //**Must send first**
	}
	
	void start_send();
	void session_send();
	void handle_send(const boost::system::error_code &ec, std::size_t len);
	void session_receive();
	void handle_recevie(const boost::system::error_code &ec, std::size_t len);
	void p2p_receive(udp::socket &sock, udp::endpoint &peer_endpoint);
	void p2p_send(udp::socket *sock, udp::endpoint *peer_endpoint);
private:
	udp::socket _sock;
	udp::endpoint _server_endpoint;
	boost::array<char, 512> _recv_buffer;
	std::string _write_message;
};

void udp_client::start_send()
{
	_sock.send_to(boost::asio::buffer("help"), _server_endpoint);
	session_receive();
}

void udp_client::session_send()
{
	std::getline(std::cin, _write_message);
	_sock.async_send_to(
		boost::asio::buffer(_write_message),
		_server_endpoint,
		boost::bind(&udp_client::handle_send,
		this,
		boost::asio::placeholders::error,
		boost::asio::placeholders::bytes_transferred));
}

void udp_client::handle_send(const boost::system::error_code &ec, std::size_t len)
{
	//if(p2p_connect)
	//    return;
	//else
	//    session_send();
}

void udp_client::session_receive()
{
	_sock.async_receive_from(
		boost::asio::buffer(_recv_buffer),
		_server_endpoint,
		boost::bind(&udp_client::handle_recevie,
		this,
		boost::asio::placeholders::error,
		boost::asio::placeholders::bytes_transferred));
}

void udp_client::handle_recevie(const boost::system::error_code &ec, std::size_t len)
{
	std::string receive_message(_recv_buffer.data(), len);
	if (strncmp(receive_message.c_str(), "PUNCH_SUCCESS", 13) == 0)
	{
		//punch finished
		//start a p2p session to remote peer
		std::cout << receive_message << std::endl;
		p2p_connect = 1;
		char str_endpoint[127];
		strcpy(str_endpoint, receive_message.c_str() + 14);
		char *peer_ip = strtok(str_endpoint, ":");
		char *peer_port = strtok(NULL, ":");
		udp::endpoint request_peer(boost::asio::ip::address::from_string(peer_ip),
			std::atoi(peer_port));
		_sock.send_to(boost::asio::buffer("Sender peer connection complete."), request_peer);
		boost::thread(boost::bind(&udp_client::p2p_send, this, &_sock, &request_peer));
		p2p_receive(_sock, request_peer);
	}
	else if (strncmp(receive_message.c_str(), "PUNCH_REQUEST", 13) == 0)
	{
		//send something to request remote peer
		//and start a p2p session
		std::cout << receive_message << std::endl;
		p2p_connect = 1;
		char str_endpoint[127];
		strcpy(str_endpoint, receive_message.c_str() + 14);
		char *peer_ip = strtok(str_endpoint, ":");
		char *peer_port = strtok(NULL, ":");
		udp::endpoint request_peer(boost::asio::ip::address::from_string(peer_ip),std::atoi(peer_port));
		
		std::cin.clear(std::cin.rdstate() & std::cin.eofbit);
		_sock.send_to(boost::asio::buffer("Receiver peer connection complete."), request_peer);
		boost::thread(boost::bind(&udp_client::p2p_send, this, &_sock, &request_peer));
		p2p_receive(_sock, request_peer);
	}
	else
	{
		std::cout << receive_message << std::endl;
	}
	session_receive();
	if (p2p_connect)
		return;
	else
		session_send();
}

void udp_client::p2p_receive(udp::socket &sock, udp::endpoint &peer_endpoint)
{
	for (;;)
	{
		boost::system::error_code error;
		//blocked until successfully received
		size_t len = sock.receive_from(boost::asio::buffer(_recv_buffer),
			peer_endpoint, 0, error);
		std::string receive_message(_recv_buffer.data(), len);
		std::cout << receive_message << std::endl;
	}
}

void udp_client::p2p_send(udp::socket *sock, udp::endpoint *peer_endpoint)
{
	while (std::getline(std::cin, _write_message))
	{
		sock->send_to(boost::asio::buffer(_write_message), *peer_endpoint);//blocked
	}
}

int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		std::cerr << "Usage: client <Server IP> <Server Port>" << std::endl;
		return 1;
	}
	
	boost::asio::io_service io_service;
	udp_client client(io_service, argv[1], argv[2]);
	
	//run in 2 threads
	//boost::thread(boost::bind(&boost::asio::io_service::run, &io_service));
	io_service.run();
	return 0;
}


参考

[1]《P2P通信原理与实现(C++)》

http://www.tuicool.com/articles/YNBVrei


  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值