WEB开发网
开发学院软件开发VC TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞 阅读

TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞

 2010-07-25 20:46:50 来源:WEB开发网   
核心提示:这里假设客户端A先启动,当客户端B启动后客户端A将收到服务器S的新客户端登录的通知,TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞(4),并得到客户端B的公网IP和端口,客户端A启动线程连接S的【协助打洞】端口(本地端口号可以用GetSocketName()函数取得,以通知客户端A CSocket Sock;

这里假设客户端A先启动,当客户端B启动后客户端A将收到服务器S的新客户端登录的通知,并得到客户端B的公网IP和端口,客户端A启动线程连接S的【协助打洞】端口(本地端口号可以用GetSocketName()函数取得,假设为M),请求S协助TCP打洞,然后启动线程侦听该本地端口(前面假设的M)上的连接请求,然后等待服务器的回应。//
// 客户端A请求我(服务器)协助连接客户端B,这个包应该在打洞Socket中收到
//
BOOL CSockClient::Handle_ReqConnClientPkt(t_ReqConnClientPkt *pReqConnClientPkt)
{
   ASSERT ( !m_bMainConn );
   CSockClient *pSockClient_B = FindSocketClient ( pReqConnClientPkt->dwInvitedID );
   if ( !pSockClient_B ) return FALSE;
   printf ( "%s:%u:%u invite %s:%u:%u connection
", m_csPeerAddress, m_nPeerPort, m_dwID,
     pSockClient_B->m_csPeerAddress, pSockClient_B->m_nPeerPort, pSockClient_B->m_dwID );
   // 客户端A想要和客户端B建立直接的TCP连接,服务器负责将A的外部IP和端口号告诉给B
   t_SrvReqMakeHolePkt SrvReqMakeHolePkt;
   SrvReqMakeHolePkt.dwInviterID = pReqConnClientPkt->dwInviterID;
   SrvReqMakeHolePkt.dwInviterHoleID = m_dwID;
   SrvReqMakeHolePkt.dwInvitedID = pReqConnClientPkt->dwInvitedID;
   STRNCPY_CS ( SrvReqMakeHolePkt.szClientHoleIP, m_csPeerAddress );
   SrvReqMakeHolePkt.nClientHolePort = m_nPeerPort;
   if ( pSockClient_B->SendChunk ( &SrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt), 0 ) != sizeof(t_SrvReqMakeHolePkt) )
     return FALSE;
   // 等待客户端B打洞完成,完成以后通知客户端A直接连接客户端外部IP和端口号
   if ( !HANDLE_IS_VALID(m_hEvtWaitClientBHole) )
     return FALSE;
   if ( WaitForSingleObject ( m_hEvtWaitClientBHole, 6000*1000 ) == WAIT_OBJECT_0 )
   {
     if ( SendChunk ( &m_SrvReqDirectConnectPkt, sizeof(t_SrvReqDirectConnectPkt), 0 )
         == sizeof(t_SrvReqDirectConnectPkt) )
       return TRUE;
   }
   return FALSE;
}
服务器S收到客户端A的协助打洞请求后通知客户端B,要求客户端B向客户端A打洞,即让客户端B尝试与客户端A的公网IP和端口进行connect。//
// 执行者:客户端B
// 处理服务器要我(客户端B)向另外一个客户端(A)打洞,打洞操作在线程中进行。
// 先连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT ,通过服务器告诉客户端A我(客户端B)的外部IP地址和端口号,然后启动线程进行打洞,
// 客户端A在收到这些信息以后会发起对我(客户端B)的外部IP地址和端口号的连接(这个连接在客户端B打洞完成以后进行,所以
// 客户端B的NAT不会丢弃这个SYN包,从而连接能建立)
//
BOOL Handle_SrvReqMakeHole ( CSocket &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
", hwFormatMessage(GetLastError()) );
       return FALSE;
     }
     if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )
     {
       printf ( "Connect to [%s:%d] failed : %s
", 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
", szError );
     return FALSE;
   }
   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 ( &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, &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, &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 ( &HoleListenReadyPkt, sizeof(t_HoleListenReadyPkt) ) != sizeof(t_HoleListenReadyPkt) )
   {
     printf ( "Send HoleListenReadyPkt to %s:%u failed : %s
",
     g_WelcomePkt.szClientIP, g_WelcomePkt.nClientPort,
       hwFormatMessage(GetLastError()) );
     return FALSE;
   }
  
   return TRUE;
}

上一页  1 2 3 4 5 6  下一页

Tags:TCP 实现 PP

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接