前几天在做一个文件点对点传输的项目,涉及到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 穿透 战败。
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 穿透 战败。
PS:
我的目标是 ----> ^_^
A B开启的端口是多少是S服务知道的
要想联接,问S对方端口就行了。
http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
跨 LAN 不能连接说明打洞失败。
TCP 打洞可能要求 raw socket ,权限可能有问题(在 XP SP2/SP3 下据说有权限问题,禁止了 raw socket,只有驱动可以使用)
坚持就是胜利,LZ
这里是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具有一定防火墙功能,可以有效的阻止外网请求
还希望多多指导.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;
}
// 执行者:客户端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; }
//
// 执行者:客户端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;
}
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;
}
同意,昨天看了一份资料:有几种类型, Cone NAPT方式的 陆游器是可以穿透的,
还有一种是Symmetric NAPT ,据说是如何不能穿透的
这个我不同意,网络上那么多的TCP 穿透资料,我不相信都是盖的
赞一个, 关于这方面,理论上可以通过端口复用来解决。在A机上同一个端口启动2个socket,一个socket负责向外连接B,另一个socket 负责监听, B机上也在同端口启动2个socket,一个向A连接,一个负责监听。这样,据说能欺骗 nat 的防火墙。
问题不是出在这里, 私有地址是不行的。 你自己真正动手写的时候,会发现NAT-防火墙问题所在的。
用这个地址,http://download.csdn.net/source/700961
http://nutss.gforge.cis.cornell.edu/stunt.php
留名,学习
除非是特定的环境,不然TCP打洞有点"碰运气"的成份在内.因为要不断尝试某些可能的端口,以达到连上.UDP打洞能成功,那是因为UDP协议不需要"连接",只需要消息就能传播,但TCP却不一样,还要三次握手,你的程序能知道在本机绑定的TCP端口是什么,但经过路由后,服务器根本不知道要路由是用哪个端口进行映射.如果路由开了UPNP功能的话,那么可以进行UPNP映射,但在一些二级交换机的网络中,这招一样不灵.我看过一些象music box等软件进行的TCP打洞,其它就是使用UPNP映射,如果路由没打开UPNP功能,它只能使用UDP打洞.