什么是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.打不通的通过服务器做中转。