TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞
当有新的客户端连接到服务器时,服务器负责将该客户端的信息(IP地址、端口号)发送给其他客户端。
// // 执行者:客户端A // 有新客户端B登录了,我(客户端A)连接服务器端口 SRV_TCP_HOLE_PORT ,申请与客户端B建立直接的TCP连接 // BOOL Handle_NewUserLogin ( CSocket &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, &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 = &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, &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 ( &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 && *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; }