前几天在做一个文件点对点传输的项目,涉及到NAT穿透,从上个星期到今天一直在调试和测试,最后到今天,看来已经战败!以下是两篇是我作为技术依据的文章:http://blog.csdn.net/ssihc0/archive/2008/10/10/3053395.aspx
http://hi.baidu.com/wangzhe1945/blog/item/3e72fffe47fc2f365d60080c.html
http://hi.baidu.com/wangzhe1945/blog/item/5ccd3fa4e3ee67f09152ee38.html总的技术原理归纳如下:
首先还是 AB分别和服务器S分别建立连接,S记录AB的互联网实际终端。然后S分别向AB发送对方的实际终端。接着,从A和B向S连接时使用的端口,AB都异步调用connect函数连接对方的实际终端(就是S告诉的终端),同时,AB双方都在同一个本地端口监听到来的连接(也可以先监听,再connect更好)。由于双方都向对方发送了connect请求(假设各自的SYN封包已经穿过了自己的NAT),因此在对方connect请求到达本地的监听端口时,路由器会认为这个请求是刚刚那个connect会话的一部分,是已经被许可的,本地监听端口就会用SYN-ACK响应,同意连接。这样,TCP穿透NAT的点对点连接就成功了。 自己写的代码穿透失败。下载了别人p2p TCP 穿透的代码 http://download.csdn.net/source/700961 ,发现如果2个机器在同一个局域网(同一内网)内是可以传输文件,但是不同的陆游器(不同内网)下,穿透不成功。
我不是高手,p2p TCP 穿透 战败。

解决方案 »

  1.   


    PS: 
    我的目标是 ----> ^_^
      

  2.   

    A B都开启一个服务端口侦听,才能内网对内网的PtoP
    A B开启的端口是多少是S服务知道的
    要想联接,问S对方端口就行了。
      

  3.   

    关键是陆游器 nat 穿透不了。
      

  4.   

    TCP 我也没搞定。琢磨很久了
      

  5.   

    tcp的好像比较难。udp实现起来比较简单,要有一台在公网的机器做服务器,它用来当有内网的机器存在时帮助建立连接。
      

  6.   

    S放的是A和B的NAT服务地址,S只知道A和B的公网地址,他不管A和B的私有地址,NAT负责把公网地址转为A和B的内部私有地址A和B 的私有地址是不能通过路由的。
      

  7.   

    RFC
    http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
      

  8.   

    tcp的比较难。udp实现起来比较简单
      

  9.   

    并非所的路由器都支持NAT穿透啊!
      

  10.   

    参考实现在同一 LAN 内可以通信很简单,只要在连接服务器时同时告知自己的内网 IP/Port 就可以了。服务器比较得知两个客户端的外网 IP 相同则应在同一 LAN 内,因此告知对方的内网 EP,自然可以直接连接。
    跨 LAN 不能连接说明打洞失败。
    TCP 打洞可能要求 raw socket ,权限可能有问题(在 XP SP2/SP3 下据说有权限问题,禁止了 raw socket,只有驱动可以使用)
      

  11.   

    UDP的容易实现,TCP的不知道。
      

  12.   

    最近正在学习这个tcp穿透NAT的东东。
    坚持就是胜利,LZ
      

  13.   

    为什么失败,说说我的理解吧,可能不对
    这里是nat地解释
    http://en.wikipedia.org/wiki/Network_address_translation
    里面有个drawbacks,就是nat的局限性   client1         client1 server       client2 server     client2
    [192.168.1.1] ---- [202.102.122.3] ----- [130.21.42.4]----[10.1.1.15]
    \ /
     \        /
                                      [68.102.31.7]
                                         server当client1(192.168.1.1)发起请求,server(68.102.31.7)知道client1的地址和端口,但这个是client映射到client1 server(202.102.122.3)的地址和端口
    同样client2发起请求,server知道的是client2 server的地址和端口当client1(192.168.1.1)向client2(10.1.1.15)发起p2p连接请求的话,其实是向client2 server(130.21.42.4)发起连接请求,nat的局限性就说过了外网一般不能发起连接请求(除非作特殊处理,比如端口映射,双向nat等)
    因为client server2并不知道外网的想连接内网的那台机器,这也是nat具有一定防火墙功能,可以有效的阻止外网请求
      

  14.   

    我这有一些资料,准备考完试,也就是3天后做一个P2P视频直播的小软件
    还希望多多指导.Email: wptad$tom.com
    给你一个TCP的例子,希望对你有所帮助.
    Clt如下:#include "stdafx.h" 
    #include "TcpHoleClt.h" #ifdef _DEBUG 
    #define new DEBUG_NEW 
    #undef THIS_FILE 
    static char THIS_FILE[] = __FILE__; 
    #endif HANDLE m_hEvtEndModule = NULL; HANDLE g_hThread_Main = NULL; 
    CSocket *g_pSock_Main = NULL; HANDLE g_hThread_MakeHole = NULL; 
    CSocket *g_pSock_MakeHole = NULL; HANDLE g_hThread_Listen = NULL; 
    CSocket *g_pSock_Listen = NULL; char *g_pServerAddess = "callgle.xicp.net"; // 服务器地址 // 我自己的客户端信息 
    t_WelcomePkt g_WelcomePkt; 
    UINT g_nHolePort = 0; HANDLE g_hEvt_MakeHoleFinished = NULL; // 打洞操作已经完成,可以让主动端(客户端A)来连接了 
    HANDLE g_hEvt_ListenFinished = NULL; // 侦听任务已启动 
    HANDLE g_hEvt_ConnectOK = NULL; // 连接建立了,这个事件来通知其他线程停止连接尝试 // 
    // 执行者:客户端A 
    // 服务器要求主动端(客户端A)直接连接被动端(客户端B)的外部IP和端口号 
    // 
    BOOL Handle_SrvReqDirectConnect ( t_SrvReqDirectConnectPkt *pSrvReqDirectConnectPkt ) 

    ASSERT ( pSrvReqDirectConnectPkt ); 
    printf ( "You can connect direct to ( IP:>s PORT:>d ID:>u )\n", pSrvReqDirectConnectPkt->szInvitedIP, 
    pSrvReqDirectConnectPkt->nInvitedPort, pSrvReqDirectConnectPkt->dwInvitedID ); // 直接与客户端B建立TCP连接,如果连接成功说明TCP打洞已经成功了。 
    CSocket Sock; 
    try 

    if ( !Sock.Socket () ) 

    printf ( "Create socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    UINT nOptValue = 1; 
    if ( !Sock.SetSockOpt ( SO_REUSEADDR, &amt;nOptValue , sizeof(UINT) ) ) 

    printf ( "SetSockOpt socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    if ( !Sock.Bind ( g_nHolePort ) ) 

    printf ( "Bind socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    for ( int ii=0; ii<100; ii++ ) 

    if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) == WAIT_OBJECT_0 ) 
    break; 
    DWORD dwArg = 1; 
    if ( !Sock.IOCtl ( FIONBIO, &amt;dwArg ) ) 

    printf ( "IOCtl failed : >s\n", hwFormatMessage(GetLastError()) ); 

    if ( !Sock.Connect ( pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort ) ) 

    printf ( "Connect to [>s:>d] failed : >s\n", pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort, hwFormatMessage(GetLastError()) ); 
    Sleep (100); 

    else break; 

    if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) != WAIT_OBJECT_0 ) 

    if ( HANDLE_IS_VALID ( g_hEvt_ConnectOK ) ) SetEvent ( g_hEvt_ConnectOK ); 
    printf ( "Connect to [>s:>d] successfully !!!\n", pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort ); // 接收测试数据 
    printf ( "Receiving data ...\n" ); 
    char szRecvBuffer[NET_BUFFER_SIZE] = {0}; 
    int nRecvBytes = 0; 
    for ( int i=0; i<1000; i++ ) 

    nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) ); 
    if ( nRecvBytes > 0 ) 

    printf ( "-->>> Received Data : >s\n", szRecvBuffer ); 
    memset ( szRecvBuffer, 0, sizeof(szRecvBuffer) ); 
    SLEEP_BREAK ( 1 ); 

    else 

    SLEEP_BREAK ( 300 ); 




    catch ( CException e ) 

    char szError[255] = {0}; 
    e.GetErrorMessage( szError, sizeof(szError) ); 
    printf ( "Exception occur, >s\n", szError ); 
    return FALSE; 
    } return TRUE; 
      

  15.   

    // 
    // 执行者:客户端A、客户端B 
    // 侦听线程函数。 
    // 打洞开始后,客户端还同时启动一个侦听,接收来自端口 g_nHolePort 的连接请求 
    // 
    DWORD WINAPI ThreadProc_Listen( 
    LPVOID lpParameter // thread data 


    ASSERT ( HANDLE_IS_VALID(g_hEvt_ListenFinished) &amt;&amt; HANDLE_IS_VALID(g_hEvt_MakeHoleFinished) ); 
    printf ( "Client.>u will listen at port >u\n", g_WelcomePkt.dwID, g_nHolePort ); BOOL bRet = FALSE; 
    CSocket Sock; 
    // 创建Socket,侦听来自端口 g_nHolePort 的连接请求 
    try 

    if ( !Sock.Socket () ) 

    printf ( "Create socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    UINT nOptValue = 1; 
    if ( !Sock.SetSockOpt ( SO_REUSEADDR, &amt;nOptValue , sizeof(UINT) ) ) 

    printf ( "SetSockOpt socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    if ( !Sock.Bind ( g_nHolePort ) ) 

    printf ( "Bind socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    if ( !Sock.Listen () ) 

    printf ( "Listen failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    printf ( "Start TCP server listen port : >u\n", g_nHolePort ); 
    g_pSock_Listen = &amt;Sock; 
    if ( HANDLE_IS_VALID(g_hEvt_ListenFinished) ) 
    SetEvent ( g_hEvt_ListenFinished ); CSocket sockAct; 
    if ( Sock.Accept ( sockAct ) ) 

    CString csSocketAddress; 
    UINT nPort = 0; 
    if ( !sockAct.GetPeerName ( csSocketAddress, nPort ) ) 

    printf ( "GetPeerName failed : >s\n", hwFormatMessage(GetLastError()) ); 

    else 

    if ( HANDLE_IS_VALID ( g_hEvt_ConnectOK ) ) SetEvent ( g_hEvt_ConnectOK ); 
    printf ( "Client.>u accept >s:>u\n", g_WelcomePkt.dwID, csSocketAddress, nPort ); 
    // 发送测试数据 
    printf ( "Sending data ...\n" ); 
    char szBuf[1024] = {0}; 
    for ( int i=0; i<10; i++ ) 

    int nLen = _snprintf ( szBuf, sizeof(szBuf), "Line.>04d - Test Data", i ); 
    if ( sockAct.Send ( szBuf, nLen ) != nLen ) 

    printf ( "Send data failed : >s\n", hwFormatMessage(GetLastError()) ); 
    break; 

    else 

    printf ( "Sent Data : >s -->>>\n", szBuf ); 
    SLEEP_BREAK ( 300 ); 





    catch ( CException e ) 

    char szError[255] = {0}; 
    e.GetErrorMessage( szError, sizeof(szError) ); 
    printf ( "Exception occur, >s\n", szError ); 
    goto finished; 
    } bRet = TRUE; finished: 
    printf ( "ThreadProc_Listen end\n" ); 
    return bRet; 
    } // 
    // 执行者:客户端A 
    // 有新客户端B登录了,我(客户端A)连接服务器端口 SRV_TCP_HOLE_PORT ,申请与客户端B建立直接的TCP连接 
    // 
    BOOL Handle_NewUserLogin ( CSocket &amt;MainSock, t_NewUserLoginPkt *pNewUserLoginPkt ) 

    printf ( "New user ( >s:>u:>u ) login server\n", pNewUserLoginPkt->szClientIP, 
    pNewUserLoginPkt->nClientPort, pNewUserLoginPkt->dwID ); BOOL bRet = FALSE; 
    DWORD dwThreadID = 0; 
    t_ReqConnClientPkt ReqConnClientPkt; 
    CSocket Sock; 
    CString csSocketAddress; 
    char szRecvBuffer[NET_BUFFER_SIZE] = {0}; 
    int nRecvBytes = 0; 
    // 创建打洞Socket,连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT 
    try 

    if ( !Sock.Socket () ) 

    printf ( "Create socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    UINT nOptValue = 1; 
    if ( !Sock.SetSockOpt ( SO_REUSEADDR, &amt;nOptValue , sizeof(UINT) ) ) 

    printf ( "SetSockOpt socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    if ( !Sock.Bind ( 0 ) ) 

    printf ( "Bind socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) ) 

    printf ( "Connect to [>s:>d] failed : >s\n", g_pServerAddess, SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) ); 
    goto finished; 


    catch ( CException e ) 

    char szError[255] = {0}; 
    e.GetErrorMessage( szError, sizeof(szError) ); 
    printf ( "Exception occur, >s\n", szError ); 
    goto finished; 

    g_pSock_MakeHole = &amt;Sock; 
    ASSERT ( g_nHolePort == 0 ); 
    VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) ); // 创建一个线程来侦听端口 g_nHolePort 的连接请求 
    dwThreadID = 0; 
    g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &amt;dwThreadID ); 
    if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE; 
    Sleep ( 3000 ); // 我(客户端A)向服务器协助打洞的端口号 SRV_TCP_HOLE_PORT 发送申请,希望与新登录的客户端B建立连接 
    // 服务器会将我的打洞用的外部IP和端口号告诉客户端B 
    ASSERT ( g_WelcomePkt.dwID > 0 ); 
    ReqConnClientPkt.dwInviterID = g_WelcomePkt.dwID; 
    ReqConnClientPkt.dwInvitedID = pNewUserLoginPkt->dwID; 
    if ( Sock.Send ( &amt;ReqConnClientPkt, sizeof(t_ReqConnClientPkt) ) != sizeof(t_ReqConnClientPkt) ) 
    goto finished; // 等待服务器回应,将客户端B的外部IP地址和端口号告诉我(客户端A) 
    nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) ); 
    if ( nRecvBytes > 0 ) 

    ASSERT ( nRecvBytes == sizeof(t_SrvReqDirectConnectPkt) ); 
    PACKET_TYPE *pePacketType = (PACKET_TYPE*)szRecvBuffer; 
    ASSERT ( pePacketType &amt;&amt; *pePacketType == PACKET_TYPE_TCP_DIRECT_CONNECT ); 
    Sleep ( 1000 ); 
    Handle_SrvReqDirectConnect ( (t_SrvReqDirectConnectPkt*)szRecvBuffer ); 
    printf ( "Handle_SrvReqDirectConnect end\n" ); 

    // 对方断开连接了 
    else 

    goto finished; 
    } bRet = TRUE; 
    finished: 
    g_pSock_MakeHole = NULL; 
    return bRet; } 
      

  16.   


    // 
    // 执行者:客户端B 
    // 打洞处理线程函数。 
    // 服务器要我(客户端B)向客户端A打洞,我(客户端B)将尝试与客户端A的外部IP和端口号connect 
    // 
    DWORD WINAPI ThreadProc_MakeHole( 
    LPVOID lpParameter // thread data 


    /* { //d 
    if ( HANDLE_IS_VALID(g_hEvt_MakeHoleFinished) ) 
    SetEvent ( g_hEvt_MakeHoleFinished ); 
    return 0; 
    } //d */ 
    ASSERT ( HANDLE_IS_VALID(g_hEvt_ListenFinished) &amt;&amt; HANDLE_IS_VALID(g_hEvt_MakeHoleFinished) ); 
    t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt = (t_SrvReqMakeHolePkt*)lpParameter; 
    ASSERT ( pSrvReqMakeHolePkt ); 
    t_SrvReqMakeHolePkt SrvReqMakeHolePkt; 
    memcpy ( &amt;SrvReqMakeHolePkt, pSrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt) ); 
    delete pSrvReqMakeHolePkt; pSrvReqMakeHolePkt = NULL; printf ( "Server request make hole to ( IP:>s PORT:>d ID:>u )\n", SrvReqMakeHolePkt.szClientHoleIP, 
    SrvReqMakeHolePkt.nClientHolePort, SrvReqMakeHolePkt.dwInviterID ); BOOL bRet = FALSE; 
    CSocket Sock; 
    // 创建Socket,本地端口绑定到 g_nHolePort,连接客户端A的外部IP和端口号(这个连接往往会失败) 
    try 

    if ( !Sock.Socket () ) 

    printf ( "Create socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    UINT nOptValue = 1; 
    if ( !Sock.SetSockOpt ( SO_REUSEADDR, &amt;nOptValue , sizeof(UINT) ) ) 

    printf ( "SetSockOpt socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    if ( !Sock.Bind ( g_nHolePort ) ) 

    printf ( "Bind socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    if ( HANDLE_IS_VALID(g_hEvt_MakeHoleFinished) ) 
    SetEvent ( g_hEvt_MakeHoleFinished ); DWORD dwArg = 1; 
    if ( !Sock.IOCtl ( FIONBIO, &amt;dwArg ) ) 

    printf ( "IOCtl failed : >s\n", hwFormatMessage(GetLastError()) ); 

    for ( int i=0; i<100; i++ ) 

    if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) == WAIT_OBJECT_0 ) 
    break; 
    if ( !Sock.Connect ( SrvReqMakeHolePkt.szClientHoleIP, SrvReqMakeHolePkt.nClientHolePort ) ) 

    printf ( "Connect to [>s:>d] failed : >s\n", SrvReqMakeHolePkt.szClientHoleIP, SrvReqMakeHolePkt.nClientHolePort, hwFormatMessage(GetLastError()) ); 
    Sleep ( 100 ); 

    else 

    if ( HANDLE_IS_VALID ( g_hEvt_ConnectOK ) ) SetEvent ( g_hEvt_ConnectOK ); 
    // 有些路由器(如TPLink R402)不用打洞就能直接连接进去 
    // 接收测试数据 
    printf ( "Connect success when make hole. Receiving data ...\n" ); 
    char szRecvBuffer[NET_BUFFER_SIZE] = {0}; 
    int nRecvBytes = 0; 
    for ( int i=0; i<1000; i++ ) 

    nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) ); 
    if ( nRecvBytes > 0 ) 

    printf ( "-->>> Received Data : >s\n", szRecvBuffer ); 
    memset ( szRecvBuffer, 0, sizeof(szRecvBuffer) ); 
    SLEEP_BREAK ( 1 ); 

    else 

    SLEEP_BREAK ( 300 ); 


    goto finished; 



    catch ( CException e ) 

    char szError[255] = {0}; 
    e.GetErrorMessage( szError, sizeof(szError) ); 
    printf ( "Exception occur, >s\n", szError ); 
    goto finished; 
    } bRet = TRUE; finished: 
    printf ( "ThreadProc_MakeHole end\n" ); 
    return bRet; 
    } // 
    // 执行者:客户端B 
    // 处理服务器要我(客户端B)向另外一个客户端(A)打洞,打洞操作在线程中进行。 
    // 先连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT ,通过服务器告诉客户端A我(客户端B)的外部IP地址和端口号,然后启动线程进行打洞, 
    // 客户端A在收到这些信息以后会发起对我(客户端B)的外部IP地址和端口号的连接(这个连接在客户端B打洞完成以后进行,所以 
    // 客户端B的NAT不会丢弃这个SYN包,从而连接能建立) 
    // 
    BOOL Handle_SrvReqMakeHole ( CSocket &amt;MainSock, t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt ) 

    ASSERT ( pSrvReqMakeHolePkt ); 
    // 创建Socket,连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT,连接建立以后发送一个断开连接的请求给服务器,然后连接断开 
    // 这里连接的目的是让服务器知道我(客户端B)的外部IP地址和端口号,以通知客户端A 
    CSocket Sock; 
    try 

    if ( !Sock.Create () ) 

    printf ( "Create socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    return FALSE; 

    if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) ) 

    printf ( "Connect to [>s:>d] failed : >s\n", g_pServerAddess, SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) ); 
    return FALSE; 


    catch ( CException e ) 

    char szError[255] = {0}; 
    e.GetErrorMessage( szError, sizeof(szError) ); 
    printf ( "Exception occur, >s\n", szError ); 
    return FALSE; 

      

  17.   

    CString csSocketAddress; 
    ASSERT ( g_nHolePort == 0 ); 
    VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) ); // 连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT,发送一个断开连接的请求,然后将连接断开,服务器在收到这个包的时候也会将 
    // 连接断开 
    t_ReqSrvDisconnectPkt ReqSrvDisconnectPkt; 
    ReqSrvDisconnectPkt.dwInviterID = pSrvReqMakeHolePkt->dwInvitedID; 
    ReqSrvDisconnectPkt.dwInviterHoleID = pSrvReqMakeHolePkt->dwInviterHoleID; 
    ReqSrvDisconnectPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID; 
    ASSERT ( ReqSrvDisconnectPkt.dwInvitedID == g_WelcomePkt.dwID ); 
    if ( Sock.Send ( &amt;ReqSrvDisconnectPkt, sizeof(t_ReqSrvDisconnectPkt) ) != sizeof(t_ReqSrvDisconnectPkt) ) 
    return FALSE; 
    Sleep ( 100 ); 
    Sock.Close (); // 创建一个线程来向客户端A的外部IP地址、端口号打洞 
    t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt_New = new t_SrvReqMakeHolePkt; 
    if ( !pSrvReqMakeHolePkt_New ) return FALSE; 
    memcpy ( pSrvReqMakeHolePkt_New, pSrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt) ); 
    DWORD dwThreadID = 0; 
    g_hThread_MakeHole = ::CreateThread ( NULL, 0, ::ThreadProc_MakeHole, LPVOID(pSrvReqMakeHolePkt_New), 0, &amt;dwThreadID ); 
    if (!HANDLE_IS_VALID(g_hThread_MakeHole) ) return FALSE; // 创建一个线程来侦听端口 g_nHolePort 的连接请求 
    dwThreadID = 0; 
    g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &amt;dwThreadID ); 
    if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE; // 等待打洞和侦听完成 
    HANDLE hEvtAry[] = { g_hEvt_ListenFinished, g_hEvt_MakeHoleFinished }; 
    if ( ::WaitForMultipleObjects ( LENGTH(hEvtAry), hEvtAry, TRUE, 30*1000 ) == WAIT_TIMEOUT ) 
    return FALSE; 
    t_HoleListenReadyPkt HoleListenReadyPkt; 
    HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID; 
    HoleListenReadyPkt.dwInviterHoleID = pSrvReqMakeHolePkt->dwInviterHoleID; 
    HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID; 
    if ( MainSock.Send ( &amt;HoleListenReadyPkt, sizeof(t_HoleListenReadyPkt) ) != sizeof(t_HoleListenReadyPkt) ) 

    printf ( "Send HoleListenReadyPkt to >s:>u failed : >s\n", g_WelcomePkt.szClientIP, g_WelcomePkt.nClientPort, 
    hwFormatMessage(GetLastError()) ); 
    return FALSE; 
    } return TRUE; 
    } // 
    // 执行者:客户端A、客户端B 
    // 处理从服务器主连接中收到的数据 
    // 
    BOOL HandleDataMainSocket(CSocket &amt;MainSock, char *data, int size) 

    if ( !data || size < 4 ) return FALSE; PACKET_TYPE *pePacketType = (PACKET_TYPE*)data; 
    ASSERT ( pePacketType ); 
    switch ( *pePacketType ) 

    // 收到服务器的欢迎信息,说明登录已经成功 
    case PACKET_TYPE_WELCOME: 

    ASSERT ( sizeof(t_WelcomePkt) == size ); 
    t_WelcomePkt *pWelcomePkt = (t_WelcomePkt*)data; 
    printf ( ">s:>u:>u >>> >s\n", pWelcomePkt->szClientIP, pWelcomePkt->nClientPort, 
    pWelcomePkt->dwID, pWelcomePkt->szWelcomeInfo ); 
    memcpy ( &amt;g_WelcomePkt, pWelcomePkt, sizeof(t_WelcomePkt) ); 
    ASSERT ( g_WelcomePkt.dwID > 0 ); 
    break; 

    // 其他客户端(客户端B)登录到服务器了 
    case PACKET_TYPE_NEW_USER_LOGIN: 

    ASSERT ( size == sizeof(t_NewUserLoginPkt) ); 
    Handle_NewUserLogin ( MainSock, (t_NewUserLoginPkt*)data ); 
    break; 

    // 服务器要我(客户端B)向另外一个客户端(客户端A)打洞 
    case PACKET_TYPE_REQUEST_MAKE_HOLE: 

    ASSERT ( size == sizeof(t_SrvReqMakeHolePkt) ); 
    Handle_SrvReqMakeHole ( MainSock, (t_SrvReqMakeHolePkt*)data ); 
    break; 

    } return TRUE; 
    } // 
    // 执行者:客户端A、客户端B 
    // 主线程函数 
    // 
    DWORD WINAPI ThreadProc_MainTCPClient( 
    LPVOID lpParameter // thread data 


    BOOL bRet = FALSE; 
    UINT nPort = (UINT)lpParameter; 
    CSocket MainSock; 
    char szRecvBuffer[NET_BUFFER_SIZE] = {0}; 
    int nRecvBytes = 0; 
    // 创建主连接的Socket,用来和服务器主Socket建立常连接 
    try 

    if ( !MainSock.Socket () ) 

    printf ( "Create socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    UINT nOptValue = 1; 
    if ( !MainSock.SetSockOpt ( SO_REUSEADDR, &amt;nOptValue , sizeof(UINT) ) ) 

    printf ( "SetSockOpt socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    if ( !MainSock.Bind ( 0 ) ) 

    printf ( "Bind socket failed : >s\n", hwFormatMessage(GetLastError()) ); 
    goto finished; 

    if ( !MainSock.Connect ( g_pServerAddess, nPort ) ) 

    printf ( "Connect to [>s:>d] failed : >s\n", g_pServerAddess, nPort, hwFormatMessage(GetLastError()) ); 
    goto finished; 

    CString csSocketAddress; 
    UINT nMyPort = 0; 
    VERIFY ( MainSock.GetSockName ( csSocketAddress, nMyPort ) ); 
    printf ( "Connect to [>s:>d] success, My Info is [>s:>d]\n", g_pServerAddess, nPort, csSocketAddress, nMyPort ); 

    catch ( CException e ) 

    char szError[255] = {0}; 
    e.GetErrorMessage( szError, sizeof(szError) ); 
    printf ( "Exception occur, >s\n", szError ); 
    goto finished; 
    } g_pSock_Main = &amt;MainSock; // 循环接收网络数据 
    while ( TRUE ) 

    nRecvBytes = MainSock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) ); 
    if ( nRecvBytes > 0 ) 

    if ( !HandleDataMainSocket ( MainSock, szRecvBuffer, nRecvBytes ) ) 
    goto finished; 

    else if ( (nRecvBytes == 0 &amt;&amt; GetLastError() != NO_ERROR) || (SOCKET_ERROR == nRecvBytes &amt;&amt; GetLastError() == WSAEWOULDBLOCK) ) 

    SLEEP_BREAK ( 10 ); 

    // 对方断开连接了 
    else 

    goto finished; 

    SLEEP_BREAK ( 1 ); 

    bRet = TRUE; finished: 
    g_pSock_Main = NULL; 
    printf ( "ThreadProc_MainTCPClient end\n" ); 
    return bRet; 
    } BOOL StartMainTCPClient ( UINT nPort, HANDLE *phThread ) 

    ASSERT ( phThread ); 
    DWORD dwThreadID = 0; 
    *phThread = ::CreateThread ( NULL, 0, ::ThreadProc_MainTCPClient, LPVOID(nPort), 0, &amt;dwThreadID ); return HANDLE_IS_VALID(*phThread); 
    } // 
    // 运行程序 
    // 
    int Run () 

    if ( !AfxSocketInit() ) 
    return End( FALSE ); m_hEvtEndModule = ::CreateEvent ( NULL, TRUE, FALSE, NULL ); 
    g_hEvt_MakeHoleFinished = ::CreateEvent ( NULL, FALSE, FALSE, NULL ); 
    g_hEvt_ListenFinished = ::CreateEvent ( NULL, FALSE, FALSE, NULL ); 
    g_hEvt_ConnectOK = ::CreateEvent ( NULL, TRUE, FALSE, NULL ); if ( !HANDLE_IS_VALID(m_hEvtEndModule) || !HANDLE_IS_VALID(g_hEvt_MakeHoleFinished) || 
    !HANDLE_IS_VALID(g_hEvt_ListenFinished) || !HANDLE_IS_VALID(g_hEvt_ConnectOK) ) 
    return End( FALSE ); if ( !StartMainTCPClient ( SRV_TCP_MAIN_PORT, &amt;g_hThread_Main ) ) 
    return End( FALSE ); printf ( "Press any key to terminate program ...\n" ); 
    ::getchar (); 
    return End( TRUE ); 
    } // 
    // 结束程序 
    // 
    int End ( BOOL bSuccess ) 

    if ( HANDLE_IS_VALID(m_hEvtEndModule) ) 
    ::SetEvent ( m_hEvtEndModule ); if ( g_pSock_Main ) g_pSock_Main->CancelBlockingCall (); 
    if ( g_pSock_MakeHole ) g_pSock_MakeHole->CancelBlockingCall (); WSACleanup (); printf ( "End programe\n" ); 
    if ( bSuccess ) return 0; printf ( "Last error is : >s\n", hwFormatMessage(GetLastError()) ); 
    return 1; 
    } ///////////////////////////////////////////////////////////////////////////// 
    // The one and only application object CWinApp theApp; using namespace std; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) 

    int nRetCode = 0; if ( argc >= 2 ) 

    g_pServerAddess = argv[1]; 
    } // initialize MFC and print and error on failure 
    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) 

    // TODO: change error code to suit your needs 
    cerr << _T("Fatal Error: MFC initialization failed") << endl; 
    nRetCode = 1; 
    return nRetCode; 

    else 

    nRetCode = Run (); 
    } return nRetCode; 
      

  18.   

    TCP 穿透. 技术上就是不可能的.只有UDP才有可能.
      

  19.   

    UDP如果在不同的网段的话,那可以加入分组,就可以实现穿透.
      

  20.   


    同意,昨天看了一份资料:有几种类型, Cone NAPT方式的 陆游器是可以穿透的,
    还有一种是Symmetric NAPT ,据说是如何不能穿透的
      

  21.   


    这个我不同意,网络上那么多的TCP 穿透资料,我不相信都是盖的
      

  22.   


    赞一个, 关于这方面,理论上可以通过端口复用来解决。在A机上同一个端口启动2个socket,一个socket负责向外连接B,另一个socket 负责监听, B机上也在同端口启动2个socket,一个向A连接,一个负责监听。这样,据说能欺骗 nat 的防火墙。
      

  23.   

    wptad  上面贴的代码,也是用这个原理,我是看出来了。 找了一些国外的文章,上面让先测试自己的陆游器的NAT 方式。我感觉说的对, 如果NAT是不可穿透的类型,再怎么写代码也是白搭
      

  24.   


    问题不是出在这里, 私有地址是不行的。 你自己真正动手写的时候,会发现NAT-防火墙问题所在的。
      

  25.   

    楼主,演示如何用TCP协议穿透NAT实现文件传送 http://dl2.csdn.net/down4/20070724/24133943521.rar 这个不能下啊
      

  26.   


    用这个地址,http://download.csdn.net/source/700961
      

  27.   

    也偶尔做做TCP的 但还没研究这 
      

  28.   

    楼主可以参考一个库,库名叫STUNT(Simple Traversal of UDP Through NATs and TCP too)
    http://nutss.gforge.cis.cornell.edu/stunt.php
      

  29.   

    只知道有透传,不知道有这么麻烦;看我同学做的挺简单,难道是udp?
    留名,学习
      

  30.   


    除非是特定的环境,不然TCP打洞有点"碰运气"的成份在内.因为要不断尝试某些可能的端口,以达到连上.UDP打洞能成功,那是因为UDP协议不需要"连接",只需要消息就能传播,但TCP却不一样,还要三次握手,你的程序能知道在本机绑定的TCP端口是什么,但经过路由后,服务器根本不知道要路由是用哪个端口进行映射.如果路由开了UPNP功能的话,那么可以进行UPNP映射,但在一些二级交换机的网络中,这招一样不灵.我看过一些象music box等软件进行的TCP打洞,其它就是使用UPNP映射,如果路由没打开UPNP功能,它只能使用UDP打洞.