p2p打洞源码,p2p内网穿透源码,NAT内网穿透源码,NAT穿透源码

一、p2p是什么?

p2p是点对点的缩写(peer-to-peer networking),其可以定义为:端对端的资源共享,每一端即可是服务端,也可以是客户端。既可以是资源的提供者,也可以是资源的共享者。传统C/S模型需要实现端和端的资源共享, 需要将资源上传到服务器,然后另外一端再从转服务器下载,如下图所示:

而P2P则不需要将资源上传到服务器,它是端对端传输,每一个端点既可以是服务器,也可以是客户端。采用p2p之后,端点之间的实时性最高,无需服务器中转,不占服务器宽带,通信更加安全。在视频直播,在线教育,视频监控,安防行业用的比较多。p2p架构如下图所示:

p2p模式支持同一个内网的端点之间直接互传数据,如上图的客户端B和客户端C,这种方式数据传输是最短最快的。

同时也支持不同内网的端点之间直接互传数据,如上图的客户端A与 客户端B、客户端A客户端C之间,这时数据首先会到达NAT1,然后再到NAT2,最后到达客户端B或客户端C。

二、NAT是什么

NAT全称是网络地址转换,它是一种把内部私有网络地址(IP地址)转换成公网网络IP地址的技术。比如我们电脑里面网卡地址是192.168.1.10,但是我们再百度搜索“我的IP”却显示210.12.214.53,这就是NAT的功能。NAT主要是部署在路由器或者交换机上。

三、为什么需要NAT

NAT是Network Address Translation的缩写,也就是网络地址转换的意思。现在大多数设备的网络采用的是ipv4网络,ipv4中ip的定义为x.x.x.x,其中每一位为0-255,所以全球的ip总数256^4,这个数量是不足够全球使用的,为了保证每个人都能有ip使用,NAT技术诞生了。

使用NAT将有助于减缓可用的IP地址空间的枯竭。比如你有一个路由器(家用的那种就可以)这个路由器本身连接了公网(被分配到了一个公网的IP地址)。路由器后面有接了N多个设备,每个设备都分配到了一个私有的地址(内网地址),这些地址可以通过这个路由器和外网交互。使用NAT还有能避免来自网络外部的攻击,隐藏并保护网络内部的计算机。

四、NAT有哪些类型

NAT有4个类型,它们分别是:NAT1、NAT2、NAT3、NAT4。从 NAT1 至 NAT4 限制越来越多。

1. 完全圆锥形NAT(Full Cone NAT)

完全圆锥型NAT把一个来自内部IP地址和端口的所有请求,始终映射到相同的外网IP地址和端口;同时,任意外部主机向该映射的外网IP地址和端口发送报文,都可以实现和内网主机进行通信,就像一个向外开口的圆锥形一样,故得名。

这种模式很宽松,限制小,只要内网主机的IP地址和端口与公网IP地址和端口建立映射关系,所有互联网上的主机都可以访问该NAT之后的内网主机。

2. 地址限制式圆锥形NAT(Address Restricted Cone NAT)

地址限制式圆锥形NAT同样把一个来自内部IP地址和端口的所有请求,始终映射到相同的外网IP地址和端口;与完全圆锥型NAT不同的是,当内网主机向某公网主机发送过报文后,只有该公网主机才能向内网主机发送报文,故得名。相比NAT1,NAT2 增加了地址限制,也就是IP受限,而端口不受限。

如下图所示,地址限制圆锥型NAT(Address Restricted Cone NAT)会将客户机地址{X:y}转换成公网地址{A:b}并绑定,只有来自主机{P}的包才能和主机{X:y}通信。

3. 端口限制式圆锥形NAT(Port Restricted Cone NAT)

端口限制式圆锥形NAT更加严格,在上述条件下,只有该公网主机该端口才能向内网主机发送报文,故得名。相比NAT2,NAT3 又增加了端口限制,也就是说IP、端口都受限。

端口限制圆锥型NAT(Port Restricted Cone NAT)会将客户机地址{X:y}转换成公网地址{A:b}并绑定,只有来自主机{P,q}的包才能和主机{X:y}通信。如下图所示:

4. 对称式NAT(Symmetric NAT)

对称式NAT把内网IP和端口到相同目的地址和端口的所有请求,都映射到同一个公网地址和端口;同一个内网主机,用相同的内网IP和端口向另外一个目的地址发送报文,则会用不同的映射(比如映射到不同的端口)。如下图所示:

对称型NAT(Symmetric NAT)会将客户机地址{X:y}转换成公网地址{A:b}并绑定为{X:y}|{A:b}<->{P:q}。对称型NAT只接受来自{P:q}的incoming packet,将它转给{X:y} ,每次客户机请求一个不同的公网地址和端口,NAT会新分配一个端口号{C,d} 。

和端口限制式NAT不同的是,端口限制式NAT是所有请求映射到相同的公网IP地址和端口,而对称式NAT是为不同的请求建立不同的映射。

它具有端口受限锥型的受限特性,内部地址每一次请求一个特定的外部地址,都可能会绑定到一个新的端口号。也就是请求不同的外部地址映射的端口号是可能不同的。

五.如何穿透NAT

目前主流的几种解决方式有ALG、STUN、TURN、ICE,我们分别来介绍它们的工作原理及工作流程

1、ALG

ALG是指能识别特定应用层协议(如SIP、H.323或MGCP协议)的防火墙。它不是简单地查看分组首部信息来解决数据分组是否可以通过,而是更深层地分析负载内容的数据,也就是应用层的数据。SIP和H.323协议都在负载中放了重要的控制信息。通过分析哪一个端口需要打开。防火墙动态的打开那些被应用的端口,而所有别的端口依然安全地保持关闭状态。ALG是支持VOIP应用最简单的一种方式,但该方案的缺点非常明显:每增加一种新的应用都将需要对 NAT/Firewall进行升级。在安全要求上还需要作一些折衷,因为ALG 不能识别加密后的报文内容,所以必须保证报文采用明文传送,这使得报文在公网中传送时有很大的安全隐患。SIP响应消息用于对请求消息进行响应,指示呼叫或注册的成功或失败状态。在请求与响应报文中需要进行ALG处理的地址字段类型主要有:Via、Record_Route、Contact、SDP。

ALG处理流程为如下三个步骤:

首先,ALG根据会话标识的协议类型对报文进行解码,若解码发现报文为不需要做ALG或解码发现为错误字段时退出,解码发现需进行字段转换时进一步处理;其次,ALG查找接口上的NAT配置,根据NAT配置转换报文中的IP地址、端口、call-id等信息并建立关联表,关联表记录了载荷地址的转换关系;最后,ALG调整报文载荷中的长度字段,如sip message header的content-length字段标识message body的长度,ALG对message body中的地址转换后,message body长度可能变化,content-length字段值需要置为变化后的值。

2、STUN

STUN的全称是Simple Traversal of UDP Through NAT,即UDP对NAT的简单穿越方式。是一种网络协议它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT 路由器之后的主机之间建立UDP通信。该协议由RFC 3489定义。 应用程序(即STUN CLIENT)向NAT外的STUN SERVER通过UDP发送请求STUN 消息询问自身的转换后地址,

STUN SERVER收到请求消息,产生响应消息,响应消息中携带请求消息的源端口,即STUN CLIENT在NAT上对应的外部端口。响应消息通过NAT发送给STUN CLIENT,

STUN CLIENT通过响应消息体中的内容得知其在NAT上对应的外部地址,并且将其填入以后呼叫协议的UDP负载中,告知对端,同时还可以在终端注册时直接注册这个转换后的公有IP地址,这样就解决SIP穿越NAT的通信建立问题以及作为被叫时的问题。

本端的接收地址和端口号为NAT外的地址和端口号。由于通过STUN协议已在NAT上预先建立媒体流的NAT映射表项,故媒体流可顺利穿越NAT。

3、TURN

TURN的全称为Traversal Using RelayNAT,即通过Relay方式穿越NAT,TURN应用模型通过分配TURNServer的地址和端口作为客户端对外的接受地址和端口,即私网用户发出的报文都要经过TURNServer进行Relay转发。这种方式又称SPAN(Simple Protocol for Augmenting NATs)方式. TURN方式解决NAT问题的思路与STUN相似,也是基于私网接入用户通过某种机制预先得到其私有地址对应在公网的地址(STUN方式得到的地址为出口NAT上的地址,TURN方式得到地址为TURNServer上的地址),然后在报文负载中所描述的地址信息直接填写该公网地址的方式,实际应用原理也是一样的。这种方式除了具有STUN方式的优点外,还解决了STUN应用无法穿透对称NAT(Symmetric NAT)以及类似的Firewall设备的缺陷,即无论企业网/驻地网出口为哪种类型的NAT/FW,都可以实现NAT的穿透,同时TURN支持基于TCP的应用,如H323协议。此外TURN Server控制分配地址和端口,能分配RTP/RTCP地址对(RTCP端口号为RTP端口号加1)作为私网终端用户的接受地址,避免了STUN方式中出口NAT对RTP/RTCP地址端口号的任意分配,使得客户端无法收到对端发来的RTCP报文(对端发RTCP报文时,目的端口号缺省按RTP端口号加 1发送)。

4、ICE

交互式连通建立方式ICE(Interactive Connectivity Establishment)并非一种新的协议,它不需要对STUN、TURN或RSIP进行扩展就可适用于各种NAT。ICE是通过综合运用上面某几种协议,使之在最适合的情况下工作,以弥补单独使用其中任何一种所带来的固有缺陷。ICE跟STUN和TURN不一样,ICE不是一种协议,而是一个framework,它整合了STUN和TURN。使用ICE方式穿透NAT,必须映射ICE定义的参数到SIP消息格式中,同时对其SDP属性进行简单扩展—在SDP的Media块中定义一个新的属性“alt”来支持ICE。它包含一个候选IP地址和端口,SDP的接受端可以用该地址来替换m和c中的地址。Media块中可能会有多个alt属性,这时每个alt应该包括不重复的IP地址和端口。 对于SIP来说,ICE只需要定义一些SDP(Session Description Protocol)附加属性即可,对于别的多媒体信令协议也需要制定一些相应的机制来实现。其思想是:建立媒体流信道时,发出很多种选择,有本地端口,STUN端口,TURN端口,并给出这些端口的优先级,由被叫方自主选择端口,根据一定的算法和联通性测试,选出最好的端口来通信。

六.p2p打洞实测

下面通过两个程序来测试p2p打洞并传输实时视频流,一个作为播放端的p2pclient,另一个是提供rtsp视频服务的p2pdevice,p2pclient和p2pdevice分别运行于不同的内网机器上。打洞成功后,p2pclient直接从p2pdevice获取rtsp视频流,并由vlc播放。

rtsp协议本身支持用udp或tcp传输实际的视频流,由于我们的p2p底层是由udp实现的,因此为了简单,我们这里配置vlc播放器使用tcp模式: 运行VLC media player后 ,选择工具—偏好设置—输入/编解码器。在最下面的live555流传输中选择 RTP over RTSP (TCP), 如下图:

下面先看程序p2pclient的代码,以下示例代码在windows下用vc2017或以上编译并执行。

/*
本P2PSDK主要用于实时音视频传输
支持linux、安卓、IOS和windows平台
支持c语言,c++,c#,java,unity等环境下使用
rtmp或rtsp等由tcp或udp开发的应用均能接入,配置简单

接口的详细说明参见另外的博客说明:
https://blog.csdn.net/xiehuanbin/article/details/128917721

有问题请联系qq: 390090739
*/
#include "stdafx.h"
#include "p2papi.h"

int g_nlocalport=0;
int DEVICE_ID   = 1000;
int g_nP2PSetUp = 0;

int main(int argc, char* argv[])
{
    int frame_index = 0;
    int64_t start_time = 0;
    if (argc >= 2)
        DEVICE_ID = atoi(argv[1]);

    printf("my deviceid = %d\n", DEVICE_ID);

    p2p_engine_init("43.142.138.68",20000,"d:/p2p.log");//"192.168.3.166"
    
    int sessionID = p2p_get_free_session();
    CP2PHoleSink sink;
    p2p_set_sink(sessionID,&sink);
    //p2p_set_log_level(2);
    g_nlocalport = 49999;
    p2p_start_proxy(sessionID, g_nlocalport,1);

    int64_t p2puid = DEVICE_ID | 0x1000000000000000;
    p2p_connect_server(sessionID, p2puid, "");
    printf("local server port=%d\n", g_nlocalport);

    while (1)
    {
        SleepEx(10,TRUE);
    }

    p2p_stop_proxy(sessionID);
    p2p_engine_destroy();

    return 0;
}

CP2PHoleSink是一个回调类,打洞状态发生变化时将回调此接口。

class CP2PHoleSink : public IP2PSessionSink
{
public:
    CP2PHoleSink()
    {
    }
    ~CP2PHoleSink()
    {
    }

    virtual void onServerConnected(int session, int state)
    {
        p2p_login_server(session, DEVICE_ID);
    }

    virtual void  onLoginServer(int session, int state)
    {
        //13是要连接的设备ID
        if (g_nP2PSetUp==0)
            p2p_connect_peer(session,13);
    }

    // < 0 p2p打洞失败,后续将连接中转服务器    
    // 0 p2p打洞失败,服务器中转建立失败或中转连接断开    
    // 1 p2p打洞成功,使用外网IP,后续可以创建通道或者直接收发udp数据
    // 2 p2p打洞成功,使用内网IP,后续可以创建通道或者直接收发udp数据
    // 3 p2p打洞失败,服务器中转已经建立
    virtual void onHoleState(int session, int64_t uid, int state, int reserved)
    {        
        g_nP2PSetUp = state;
        if (state < 0)
            printf("打洞失败,正在连接中转服务器...\n");
        else if (state == 0)
            printf("与中转服务器的连接断开...\n");
        else if (state == 1 || state == 2)
            printf("打洞成功.\n");
        else if (state == 3)
            printf("打洞失败,服务器中转连接已经建立.\n");
        else
            printf("打洞错误,state=%d.\n", state);

        if (state > 0)
        {
    /*
            //通道100:
            //默认为 P2P_OPTION_UDP_MODEL 模式,不需要调用p2p_create_channel,就可以直接发送数据
            p2p_send(session, uid, 100, "test data", 9);

            //通道101:
            //通过调用p2p_create_channel,改为 P2P_OPTION_TCP_MODEL 模式
            p2p_create_channel(session, uid, 101);
            p2p_send(session, uid, 101, "testtcp", 7);

    */
        }
    }

    virtual void onRecvServerData(int session, int64_t cmd, int subcmd, const unsigned char * pDataBuffer, int wDataSize)
    {
    }

    virtual void onRecvP2PData(int session, int64_t uid, int channel, const unsigned char * pDataBuffer, int wDataSize)
    {
    }

    virtual void onChannelState(int session, int64_t uid, int channel, int state)
    {
    }

    virtual void onProxyStarted(int session, int port)
    {
    }
};

接着再看程序p2pdevice的代码,以下示例代码在windows下用vc2017或以上编译并执行。

/*
本P2PSDK主要用于实时音视频传输
支持linux、安卓、IOS和windows平台
支持c语言,c++,c#,java,unity等环境下使用
rtmp或rtsp等由tcp或udp开发的应用均能接入,配置简单

接口的详细说明参见另外的博客说明:
https://blog.csdn.net/xiehuanbin/article/details/128917721

有问题请联系qq: 390090739
*/
#include "stdafx.h"
#include "p2papi.h"
#include "livemedia.h"

int DEVICE_ID   = 13;
int g_nP2PSetUp = 0;

int main(int argc, char* argv[])
{
    int frame_index = 0;
    int64_t start_time = 0;
    if (argc >= 2)
        DEVICE_ID = atoi(argv[1]);

    p2p_engine_init("43.142.138.68",20000,"d:/p2pserver.log");//"192.168.3.166"

    DWORD last_update_time = GetTickCount();
    DWORD last_framenum_update = last_update_time;
    DWORD nFrameNum = 0;
    int initVal = 0;

    printf("my deviceid = %d\n", DEVICE_ID );
    int sessionID = p2p_get_free_session();

    CP2PHoleSink sink;
    p2p_set_sink(sessionID,&sink);
    
    p2p_set_log_level(3);
    //设备授权ID和密钥:4294967390,49uozeazd9
    //此授权ID与p2p_login_server中的设备ID不同
    //8589934684,lg27kun886
    p2p_connect_server(sessionID, 4294967390, "49uozeazd9");
    
    int serviceport = start_rtsp_server(sessionID,0);
    p2p_set_device_port(sessionID, serviceport);

    while (initVal == 0)
    {
        rtsp_server_update();
        SleepEx(10,TRUE);
    }
    
    p2p_stop_proxy(sessionID);
    stop_rtsp_server();
    p2p_engine_destroy();
    return 0;
}

CP2PHoleSink和p2pclient里面 定义一样,是一个回调类,打洞状态发生变化时将回调此接口。


class CP2PHoleSink : public IP2PSessionSink
{
public:
    CP2PHoleSink()    {    }
    ~CP2PHoleSink()    {    }

    virtual void onServerConnected(int session, int state)
    {
        p2p_login_server(session, DEVICE_ID);
    }

    virtual void  onLoginServer(int session, int state)
    {
    }

    virtual void onProxyStarted(int session, int port)
    {
    }

    // < 0 p2p打洞失败,后续将连接中转服务器    
    // 0 p2p打洞失败,服务器中转建立失败或中转连接断开    
    // 1 p2p打洞成功,使用外网IP,后续可以创建通道或者直接收发udp数据
    // 2 p2p打洞成功,使用内网IP,后续可以创建通道或者直接收发udp数据
    // 3 p2p打洞失败,服务器中转已经建立
    virtual void onHoleState(int session, int64_t uid, int state, int reserved)
    {        
        g_nP2PSetUp=state;    
        if (state < 0)
            printf("打洞失败,正在连接中转服务器...\n");
        else if (state == 0)
            printf("与中转服务器的连接断开...\n");
        else if (state == 1 || state == 2)
            printf("打洞成功.\n");
        else if (state == 3)
            printf("打洞失败,服务器中转连接已经建立.\n");
        else
            printf("打洞错误,state=%d.\n", state );
    }

    virtual void onRecvServerData(int session, int64_t cmd, int subcmd, const unsigned char * pDataBuffer, int wDataSize)
    {
    }

    virtual void onRecvP2PData(int session, int64_t uid, int channel, const unsigned char * pDataBuffer, int wDataSize)
    {
    }

    virtual void onChannelState(int session, int64_t uid, int channel, int state)
    {

    }
};

p2pdevice编译后运行截图:

p2pclient编译后运行截图:

打洞成功后,就可以使用vcl media player来播放rtsp视频了, 如有任何凝问,请加QQ群:384170753交流。

测试时必须先运行p2pdevice程序,再运行p2pclient程序,然后运行vcl media player,打开vcl后,选择 媒体—打开网络串流—网络,输入地址rtsp://127.0.0.1:49999/video/test.264,然后点“播放”按扭, 既可播放p2pdevice提供的视频流。

点击下面的链接,下载完整的示例代码,可以自己编译,测试p2p打洞效果。

完整测试代码下载

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值