你的位置:pcMing工作室 >> 资讯 >> 编程开发 >> C++编程 >> 详细内容 在线投稿

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

排行榜 收藏 打印 发给朋友 举报 来源: 互联网   发布者:未知
热度2431票  浏览983次 【共0条评论】【我要评论 时间:2010年4月08日 20:18
pcMing工作室hu0Z8kZ\

F5X/[;F:QL$\O q [X0这里假设客户端A先启动,当客户端B启动后客户端A将收到服务器S的新客户端登录的通知,并得到客户端B的公网IP和端口,客户端A启动线程连接S的【协助打洞】端口(本地端口号可以用GetSocketName()函数取得,假设为M),请求S协助TCP打洞,然后启动线程侦听该本地端口(前面假设的M)上的连接请求,然后等待服务器的回应。

z pD I-o~0
//
// 客户端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\n", 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;
}
pcMing工作室{{2xR^W}

服务器S收到客户端A的协助打洞请求后通知客户端B,要求客户端B向客户端A打洞,即让客户端B尝试与客户端A的公网IP和端口进行connect。

oEx}:A.JN0
//
// 执行者:客户端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\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;
	}

	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\n", 
		g_WelcomePkt.szClientIP, g_WelcomePkt.nClientPort,
			hwFormatMessage(GetLastError()) );
		return FALSE;
	}
	
	return TRUE;
}

B:uA"y5gi)p0

顶:123 踩:146
对本文中的事件或人物打分:
当前平均分:-0.09 (774次打分)
对本篇资讯内容的质量打分:
当前平均分:-0.19 (688次打分)
【已经有700人表态】
108票
感动
79票
路过
88票
高兴
75票
难过
85票
搞笑
85票
愤怒
94票
无聊
86票
同情
上一篇 下一篇
发表评论
换一张

网友评论仅供网友表达个人看法,并不表明本网同意其观点或证实其描述。

查看全部回复【已有0位网友发表了看法】

网络资源