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


0 0
原创粉丝点击