简单的p2p-demo,udp打洞
来源:互联网 发布:域名转出流程 编辑:程序博客网 时间:2024/05/15 11:56
什么是p2p:
peer-to-peer,简单来说,就是两个用户可以直接进行网络通信。
为什么我们需要p2p:
1.大多数的网络状态都是用户A和用户B互相通信,需要一个中间服务器来做消息的中转。如果可以用户对用户直接通信,那么可以减轻服务器压力。
2.一定程度上消除了网络延迟。
3.很多现实场景需要P2P,例如网络电话。
对网络有一定了解的话就会知道,一般用户是没有自己的公网ip的,用户和服务器通信,都是用户主动向服务器发送消息(反向连接)并打开一个可以让服务器进来的通道。服务器才知道用户的ip,并且可以向用户发送消息。
如果A通过服务器知道了B的Ip,A和B之间也不一定是能够通信的。这里就涉及如下的内容:
NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。
这种方法需要在专用网连接到因特网的路由器上安装NAT软件。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址。这样,所有使用本地地址的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址,才能和因特网连接。
另外,这种通过使用少量的公有IP 地址代表较多的私有IP 地址的方式,将有助于减缓可用的IP地址空间的枯竭。在RFC 1632中有对NAT的说明。
而NAT又有3种:
静态转换是指将内部网络的私有IP地址转换为公有IP地址,IP地址对是一对一的,是一成不变的,某个私有IP地址只转换为某个公有IP地址。借助于静态转换,可以实现外部网络对内部网络中某些特定设备(如服务器)的访问。
动态转换是指将内部网络的私有IP地址转换为公用IP地址时,IP地址是不确定的,是随机的,所有被授权访问上Internet的私有IP地址可随机转换为任何指定的合法IP地址。也就是说,只要指定哪些内部地址可以进行转换,以及用哪些合法地址作为外部地址时,就可以进行动态转换。动态转换可以使用多个合法外部地址集。当ISP提供的合法IP地址略少于网络内部的计算机数量时。可以采用动态转换的方式。
端口多路复用(Port address Translation,PAT)是指改变外出数据包的源端口并进行端口转换,即端口地址转换(PAT,Port Address Translation).采用端口多路复用方式。内部网络的所有主机均可共享一个合法外部IP地址实现对Internet的访问,从而可以最大限度地节约IP地址资源。同时,又可隐藏网络内部的所有主机,有效避免来自internet的攻击。因此,目前网络中应用最多的就是端口多路复用方式。
引用自(http://baike.baidu.com/link?url=b3s1JVUyBy_UucgNiCXLcMcVUHWJjbstQBjOJEoeaqWvKN_taY-TsyVpOm-asMKwAJAINdKi1HQ0EUTSWadK6_)
也就是说,其中一种NAT的ip映射方式是动态的。(这种NAT很难打通)
对于其他几种可以打通的NAT。我们可以搭建一个中间服务器,用于打洞。这样可以实现用户和用户之间的直接通信。流程如下:
1.A连接服务器,服务器得到A的ip。
2.B连接服务器,服务器得到B的ip。
3.A告诉服务器A试图对B发送消息,服务器告诉B,让B访问A的ip。这时打开了一个B向A的通道。
4.服务器告诉A打洞完成,A向B发送消息。
5.B告诉服务器B试图对A发送消息,服务器告诉A,让A访问B的ip。这时打开了一个A向B的通道。
6.服务器告诉B打洞完成,B向A发送消息。
这样就实现了A和B的互通消息。
如下一个简单的DEMO,直接上代码:
Server:
//// async_udp_echo_server.cpp// ~~~~~~~~~~~~~~~~~~~~~~~~~//// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)//// Distributed under the Boost Software License, Version 1.0. (See accompanying// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)//#include <cstdlib>#include <iostream>#include <sstream>#include <boost/asio.hpp>#include <boost/thread/thread.hpp>using boost::asio::ip::udp;class Server{private: const std::string LOGIN = "login"; const std::string LOGIN_SUCESS = "login_sucess"; const std::string LOGIN_ERROR = "login_error"; const std::string LOGOUT = "logout"; const std::string LOGOUT_ERROR = "logout_error"; const std::string GETURL_P2S_REQUEST = "getUrlsReq"; const std::string GETURL_S2P_RESPONSE = "getUrlsRep"; const std::string P2PMSG = "p2pMsg"; const std::string P2SMSG = "p2sMsg"; const std::string S2PMSG = "s2pMgs"; const std::string TRANSLATE_P2S_REQUEST = "p2sTranslate"; const std::string TRANSLATE_S2P_REQUEST = "s2pTranslate"; const std::string ACK_P2P = "ack";public: Server(boost::asio::io_service& io_service, short port); void UserLogin(const std::string& userName,const udp::endpoint& netPoint); void UserLogout(const std::string& userName); void PaserCommand(const std::string& cmd,const udp::endpoint& netPoint); void UserGetUrl(const udp::endpoint& netPoint); void UserTransfer(const std::string& userName,const udp::endpoint& netPoint); ~Server(); void do_receive(); void do_send(std::size_t length);private: struct User{ std::string userName; udp::endpoint netPoint; User(const User &user){ this->userName = user.userName; this->netPoint = user.netPoint; } User(std::string userName,udp::endpoint netPoint):userName(userName),netPoint(netPoint) { } }; std::vector<User> clientList; udp::socket server; udp::endpoint remotePoint; enum { max_length = 1024 }; char data_[max_length]; int msgLength;};
//// async_udp_echo_server.cpp// ~~~~~~~~~~~~~~~~~~~~~~~~~//// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)//// Distributed under the Boost Software License, Version 1.0. (See accompanying// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)//#include "server.h"#include <cstdlib>#include <iostream>#include <sstream>#include <boost/asio.hpp>#include <stdarg.h>#include <boost/algorithm/string.hpp>#include <boost/tokenizer.hpp>#include <boost/format.hpp>#include <stdlib.h>#include <sstream>using boost::asio::ip::udp;using namespace std;string merage(std::string string_array[],unsigned int len){ if(string_array == NULL) return ""; std::string msg = ""; for(unsigned int i = 0;i < len;++i){ if(i == len-1) { msg += string_array[i]; } else { msg += string_array[i]; msg += " "; } } return msg;}void Server::do_send(std::size_t length){ server.async_send_to( boost::asio::buffer(data_, length), remotePoint, [this](boost::system::error_code,std::size_t) { do_receive(); } );}Server::Server(boost::asio::io_service& io_service,short port):server(io_service,udp::endpoint(udp::v4(),port)),msgLength(-1){ do_receive();}void Server::do_receive(){ server.async_receive_from( boost::asio::buffer(data_,max_length),remotePoint, [this](boost::system::error_code ec,std::size_t bytes_recvd){ data_[bytes_recvd] = '\0'; cout<<"do_recieve: "<<data_<<endl; PaserCommand(string(data_),remotePoint); if(!ec && bytes_recvd > 0) { do_send(msgLength); } else { do_receive(); } });}Server::~Server(){}void Server::UserLogin(const std::string& userName,const udp::endpoint& netPoint){ clientList.push_back(User(userName,netPoint));}void Server::UserTransfer(const std::string& userName,const udp::endpoint& netPoint){ cout<<"userName: "<<userName<<endl; int index = -1; for(unsigned int i = 0;i < clientList.size();++i) { if(clientList[i].userName.compare(userName) == 0) { index = i; break; } } if(index != -1){ stringstream stream; stream<<remotePoint.port(); string string_port; stream>>string_port; // join({}, " ") std::string string_array[] = { TRANSLATE_S2P_REQUEST, remotePoint.address().to_string(), string_port }; string msg = merage(string_array,3); memcpy(data_,msg.c_str(),msg.size()); data_[msg.size()] = '\0'; msgLength = msg.size()+1; cout<<string_array[1]<<endl; cout<<string_array[2]<<":"<<string_port<<endl; cout<<"to"<<clientList[index].netPoint<<endl; remotePoint = clientList[index].netPoint; } else { cout<<"no this user"<<endl; }}void Server::UserLogout(const std::string& userName){ for(auto it = clientList.begin();it != clientList.end();it++) { if((*it).userName.compare(userName) == 0) { clientList.erase(it); } }}void Server::UserGetUrl(const udp::endpoint& netPoint){ std::string msg = GETURL_S2P_RESPONSE; for(unsigned int i = 0;i < clientList.size();++i) { stringstream stream; stream<<clientList[i].netPoint.port(); string string_port; stream>>string_port; string string_array[] = { msg, clientList[i].userName, clientList[i].netPoint.address().to_string(), string_port }; msg = merage(string_array,4); memcpy(data_,msg.c_str(),msg.size()); data_[msg.size()] = '\0'; msgLength = msg.size()+1; }}void Server::PaserCommand(const std::string &cmd,const udp::endpoint& netPoint){ cout<<endl; cout<<"cmd: "<<cmd<<endl; std::vector<std::string> vec_string; boost::split(vec_string,cmd,boost::is_any_of(" ")); if(vec_string.size() > 0) { if(vec_string[0].compare(LOGIN) == 0) { UserLogin(vec_string[1],netPoint); } else if(vec_string[0].compare(LOGOUT) == 0) { UserLogout(vec_string[1]); } else if(vec_string[0].compare(GETURL_P2S_REQUEST) == 0) { UserGetUrl(netPoint); } else if(vec_string[0].compare(TRANSLATE_P2S_REQUEST) == 0) { UserTransfer(vec_string[2],netPoint); } else { cout<<"error command"<<endl; } }}
//// async_udp_echo_server.cpp// ~~~~~~~~~~~~~~~~~~~~~~~~~//// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)//// Distributed under the Boost Software License, Version 1.0. (See accompanying// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)//#include <cstdlib>#include <iostream>#include <sstream>#include <boost/asio.hpp>#include "server.h"using boost::asio::ip::udp;int main(int argc, char* argv[]){ try { boost::asio::io_service io_service; Server server(io_service, 2280); io_service.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0;}
Client: (C#)
using System;using System.Collections.Generic;using System.Text;using System.Net;using System.Net.Sockets;using System.Threading;namespace P2P.P2PClient{ public class Client : IDisposable { private const int MAXRETRY = 10; private UdpClient client; private IPEndPoint hostPoint; private IPEndPoint remotePoint; private P2P.WellKnown.UserCollection userList; private string myName; private bool ReceivedACK; private Thread listenThread; public Client(string serverIP) { ReceivedACK = false; remotePoint = new IPEndPoint(IPAddress.Any, 0); hostPoint = new IPEndPoint(IPAddress.Parse(serverIP), P2P.WellKnown.P2PConsts.SRV_PORT); client = new UdpClient(); userList = new P2P.WellKnown.UserCollection(); listenThread = new Thread(new ThreadStart(Run)); } public void Start() { if (this.listenThread.ThreadState == ThreadState.Unstarted) { this.listenThread.Start(); } } public void ConnectToServer(string userName) { myName = userName; string msg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.LOGIN, userName); byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg); client.Send(byteArray, byteArray.Length, hostPoint); RequestGetUrls(); } private bool SendMessageTo(string toUserName, string message) { P2P.WellKnown.User toUser = userList.Find(toUserName); if (toUser == null) { return false; } for (int i = 0; i < MAXRETRY; i++) { string p2pmsg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.P2PMSG,message); byte[] p2pByteArray = System.Text.Encoding.Default.GetBytes(p2pmsg); client.Send(p2pByteArray,p2pByteArray.Length,toUser.NetPoint); // 等待接收线程将标记修改 for (int j = 0; j < 10; j++) { if (this.ReceivedACK) { this.ReceivedACK = false; return true; } else { Thread.Sleep(300); } } //UDP打洞 string msg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.TRANSLATE_P2S_REQUEST,myName,toUserName); byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg); client.Send(byteArray, byteArray.Length,hostPoint); // 等待对方先发送信息 Thread.Sleep(100); } return false; } private void DisplayUsers(P2P.WellKnown.UserCollection users) { foreach (P2P.WellKnown.User user in users) { Console.WriteLine("Username: {0}, IP:{1}, Port:{2}", user.UserName, user.NetPoint.Address.ToString(), user.NetPoint.Port); } } private void RecieveUserListMsg(string[] args) { if (args.Length < 4) return; userList.Clear(); for (int i = 1; i < args.Length; i+=3) { userList.Add(new P2P.WellKnown.User( args[i], new IPEndPoint(IPAddress.Parse(args[i+1]), int.Parse(args[i+2]))) ); } this.DisplayUsers(userList); } private void RecieveTransferMsg(IPEndPoint remotePoint) { string msg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.ACK_P2P); byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg); client.Send(byteArray,byteArray.Length,remotePoint); for (int i = 1; i < 11; i++) { Console.WriteLine(remotePoint.Port + i); client.Send(byteArray, byteArray.Length, new IPEndPoint(remotePoint.Address,remotePoint.Port + i)); } } private void RecieveP2PMsg() { //回复 string msg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.ACK_P2P); byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg); client.Send(byteArray, byteArray.Length,remotePoint); } private void RequestGetUrls() { string msg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.GETURL_P2S_REQUEST, myName); byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg); client.Send(byteArray, byteArray.Length, hostPoint); } private void PaserResponseCommand(String cmdstring) { cmdstring = cmdstring.Trim(); string[] args = cmdstring.Split(new char[] { ' ' }); Console.WriteLine(cmdstring); if (args.Length > 0) { if (string.Compare(args[0], P2P.WellKnown.P2PConsts.GETURL_S2P_RESPONSE, true) == 0) { RecieveUserListMsg(args); } else if (string.Compare(args[0], P2P.WellKnown.P2PConsts.TRANSLATE_S2P_REQUEST, true) == 0) { RecieveTransferMsg(new IPEndPoint(IPAddress.Parse(args[1]), int.Parse(args[2]))); } else if (string.Compare(args[0], P2P.WellKnown.P2PConsts.P2PMSG, true) == 0) { RecieveP2PMsg(); } else if (string.Compare(args[0], P2P.WellKnown.P2PConsts.ACK_P2P, true) == 0) { Console.WriteLine("Receive ACK"); this.ReceivedACK = true; } } } private void Run() { byte[] buffer; while (true) { Console.WriteLine("run!"); buffer = client.Receive(ref remotePoint); String str = System.Text.Encoding.Default.GetString(buffer); PaserResponseCommand(str); Thread.Sleep(100); } } public void PaserCommand(string cmdstring) { cmdstring = cmdstring.Trim(); string[] args = cmdstring.Split(new char[] { ' ' }); if (args.Length > 0) { if (string.Compare(args[0], P2P.WellKnown.P2PConsts.LOGOUT, true) == 0) { string msg = P2P.WellKnown.P2PConsts.MerageCmd(P2P.WellKnown.P2PConsts.LOGOUT, myName); byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg); client.Send(byteArray, byteArray.Length, hostPoint); Dispose(); System.Environment.Exit(0); } else if (string.Compare(args[0], P2P.WellKnown.P2PConsts.P2PMSG, true) == 0) { if (args.Length > 2) { string toUserName = args[1]; string message = ""; for (int i = 2; i < args.Length; i++) { if (args[i] == "") message += " "; else message += args[i]; } if (this.SendMessageTo(toUserName, message)) { Console.WriteLine("Send OK!"); } else Console.WriteLine("Send Failed!"); } } else if (string.Compare(args[0], P2P.WellKnown.P2PConsts.GETURL_P2S_REQUEST, true) == 0) { RequestGetUrls(); } else { Console.WriteLine("Unknown command {0}", cmdstring); } } } #region IDisposable 成员 public void Dispose() { try { this.listenThread.Abort(); this.client.Close(); } catch { } } #endregion }}
using System;using System.Collections.Generic;using System.Text;using System.Threading;namespace P2P.P2PClient{ class Program { static void Main(string[] args) { Client client = new Client("xxx.xxx.xxx.xxx"); client.ConnectToServer("xxx.xxx.xxx.xxx"); client.Start(); while (true) { string str = Console.ReadLine(); client.PaserCommand(str); } } }}
服务器的代码是对boost中得demo的简单修改。
1.如果要真正实现一个自己的TURN服务器的话。还需要考虑洞的生存时间,需要发一些包用来维护洞的存在。
2.打不通的通过服务器做中转。
- 简单的p2p-demo,udp打洞
- p2p的UDP打洞原理
- Python实现简单的udp打洞(P2P)
- P2P通信 原理-UDP打洞方式
- 嵌入式 初始p2p交互,UDP打洞
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)【转】
- P2P之UDP穿透NAT原理并有UDP打洞的源码
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching
- [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- P2P之UDP穿透NAT原理并有UDP打洞的源码
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- P2P之UDP穿透NAT原理并有UDP打洞的源码
- UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
- android界面开发:ViewPager的详解,并用于仿微博滑动实例和图片滚动实例
- CSS 基本选择器&优先级
- js面向对象的几中写法对比
- C语言学习日记06
- 文本溢出加...
- 简单的p2p-demo,udp打洞
- 为Nexus5编译AndroidL固件
- 【程序哥】分析 网易一元夺宝是否有作弊空间,真像媒体所说的吗?
- 单例模式
- 其他关于oracle的操作链接
- 对2440开发板烧写的理解
- Android ListView 嵌套RadioGroup 滑动时出现错乱
- HashAggregate, GroupAggregate, hash join
- C语言学习日记07