这两天一直在研究这个UDP打洞。看了许多博客。NAT协议都看了。。。。最后理解了UDP打洞的原理。昨天的努力UDP打洞终于成功。下面讲一下UDP打洞过程。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1.为什么要有UDP打洞?
可以从图中看出,ClientA访问Server时。先是要通过NAT,经过这一层的转换后,他的内网IP会被换成NAT的公网IP,并且会为ClientA的此次访问事件分配一个临时的端口。
这能解决IP地址日益匮乏的问题。因为NAT的存在,可以是多台Client共用一个公网IP地址。但是同时造成了一个问题。外网访问内网是很麻烦的。因为NA这个时候并没有为ClientA的每个端口提供映射。而是要用时,临时分配一个。那个如果外网的Server想主动发起通信是不可以的。同样的,另一个内网的ClientB想访问ClientA就更难了。
2.UDP打洞
如果ClientA发出连接Server的请求,那么NAT上就会有一个ClientA对应的通信端口的洞。这个时候外网的Server就可以通过这个洞来访问了。因为ClientA请求了Server,代表信任Server。那么Server通过这个洞就能和ClientA进行通信了。
但是要注意一个问题。如果ClientB知道了这个洞大多数情况下,也是不能发送消息给A的。会被NAT抛弃。因为不是可信来源。(要使得ClientB能发送信息给A的前提是。ClientA给ClientB发送一个消息。就像ClientA对B说,我相信你),那么这个时候Clinet'B再给ClientA发送信息,A就能接受到了。
3.打洞过程
(1)ClientA请求Server。
(2)ClientB请求Server。
(3)Server把ClientA的IP和端口信息发给ClientB。
(4)Server把ClientB的IP和端口信息发给ClientA。
(5)ClientA利用信息给ClientB发消息。(A信任B)
(6)ClinetB利用信息给ClientA发消息。(B信任A)
(7)连接已经建立。两者可以直接通信了。
//--------------------------------------------------------------
但是这里一定要注意,能用UDP打洞成功是有要求的,不能是两个NAT设备都是对称类型的。如果一个是对称类型,一个是cone类型的NAT,这种情况下还可以使用预测法。网上可以找到对应的论文,大家可以去看看。我这里的成功是基于两端都是cone NAT类型设备的。
//--------------------------------------------------------------
下面是服务端和客户端代码。C++
----------------------------------------------------------编译环境,vs2017 console MFC支持-------------------------------------------------
SOCKET sockClient = 0;
SOCKADDR_IN addrSrv = { 0 };
//Save the other's info.
SOCKADDR_IN addrClientOther = { 0 };
BOOL InitSocketToUDP(SOCKET & _sock)
{
_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sock == INVALID_SOCKET)
{
int err = WSAGetLastError();
cout << "socket with error!,error code is " << err << endl;
return FALSE;
}
//bind loacal port
int iErr = 0;
WORD wLocalPort = 0;
SOCKADDR_IN addrLocal = { 0 };
addrLocal.sin_family = AF_INET;
cout << "Please enter the port that you want to recv UDP msg : ";
cin >> wLocalPort;
addrLocal.sin_port = htons(wLocalPort);//wLocalPort
cout << "Port : " << wLocalPort << endl;
addrLocal.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
iErr = bind(_sock, (SOCKADDR *)&addrLocal, sizeof(SOCKADDR));
if (iErr == SOCKET_ERROR)
{
iErr = WSAGetLastError();
cout << "bind() with error. error code is : " << iErr << endl;
closesocket(_sock);
return FALSE;
}
return TRUE;
}
BOOL SendHoleCmdGetHole(SOCKET & _sock, SOCKADDR_IN & _addr, const char * _szAddr, USHORT _uPort, SOCKADDR_IN & _addrOther)
{
SOCKADDR_IN addrOther = { 0 };
int iErr = 0;
_addr.sin_addr.S_un.S_addr = inet_addr(_szAddr);
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_uPort);
//Send Hole Cmd to server.
iErr = sendto(_sock,
"Hole",
strlen("Hole") + 1,
0,
(SOCKADDR *)&_addr,
sizeof(SOCKADDR));
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "sendto() with error, error code is " << iErr << endl;
return FALSE;
}
//Wait for other Client, Recv his's info from Srv.
int iAddrLen = sizeof(SOCKADDR);
iErr = recvfrom(_sock,
(char *)&addrOther,
sizeof(SOCKADDR_IN),
0,
(SOCKADDR *)&_addr,
&iAddrLen);
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "recvfrom() with error, error code is " << iErr << endl;
return FALSE;
}
//No error and return
_addrOther = addrOther;
//Out the other's info
cout << "Other's IP : " << inet_ntoa(_addrOther.sin_addr) << " ";
cout << "Port : " << ntohs(_addrOther.sin_port) << endl;
return TRUE;
}
BOOL Hole(SOCKET & _sock, SOCKADDR_IN & _addrOther)
{
int iErr = 0;
//Send Hole Msg
iErr = sendto(_sock,
"I Believe you!",
strlen("I Believe you!") + 1,
0,
(SOCKADDR *)&_addrOther,
sizeof(SOCKADDR));
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "sendto() with error, error code is " << iErr << endl;
return FALSE;
}
return TRUE;
}
DWORD WINAPI RecvProc(
_In_ LPVOID lpParameter
)
{
SOCKET * pSock = (SOCKET *)lpParameter;
char RecvBuf[512] = { 0 };
SOCKADDR_IN addrSender = { 0 };
int iAddrLen = sizeof(SOCKADDR);
int iErr = 0;
cout << "In sockClient is : " << *pSock << endl;
while (1)
{
iErr = recvfrom(*pSock,
RecvBuf,
512,
0,
(SOCKADDR*)&addrSender,
&iAddrLen);
if (iErr == SOCKET_ERROR)
{
iErr = WSAGetLastError();
cout << "RecvProc() recvfrom with error, error code is " << iErr << endl;
}
else
{
cout << inet_ntoa(addrSender.sin_addr) << " : " << RecvBuf << endl;
}
}
return 0;
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
//Init Socket envirement.
if (AfxSocketInit() == 0)
return FALSE;
//InitSocket.
if (InitSocketToUDP(sockClient) == 0)
return FALSE;
/*Get the Srv's IP and port*/
char strSrvIP[] = "255.255.255.255";
USHORT uPort = 0;
cout << "Please enter Hole Server's IP address : " << endl;
cin >> strSrvIP;
cout << "Please enter Port : " << endl;
cin >> uPort;
cout << "Sending Hole cmd to srv....." << endl;
//Send Hole Request.
if (SendHoleCmdGetHole(sockClient,
addrSrv,
strSrvIP,
uPort,
addrClientOther//recv
) == FALSE)
{
cout << "Get Hole Info from srv fails!" << endl;
return FALSE;
}
//Run the recv proc
cout << "Real socketClient is : " << sockClient << endl;
HANDLE hThead = CreateThread(NULL,
0,
RecvProc,
(LPVOID)&sockClient,
0,
NULL);
CloseHandle(hThead);
//Hole
if (Hole(sockClient, addrClientOther) == FALSE)
{
cout << "Hole fails!" << endl;
}
//Send msg
cout << "Hole successful you can send msg to him !" << endl;
char SendBuf[512] = { 0 };
int iErr = 0;
while (1)
{
cin >> SendBuf;
iErr = sendto(sockClient,
SendBuf,
512,
0,
(SOCKADDR*)&addrClientOther,
sizeof(SOCKADDR));
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "sendto() with error, error code is " << iErr << endl;
return FALSE;
}
}
//close socket
closesocket(sockClient);
// TODO: 在此处为应用程序的行为编写代码。
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
system("pause");
return nRetCode;
}服务端代码
SOCKET sockSrv = 0;
SOCKADDR_IN addrSrv = { 0 };
BOOL InitSocketToUDP(SOCKET & _sock)
{
_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (_sock == INVALID_SOCKET)
{
int err = WSAGetLastError();
cout << "socket with error!,error code is " << err << endl;
return FALSE;
}
return TRUE;
}
BOOL Bind(SOCKET & _sock, USHORT _port, SOCKADDR_IN & _addr)
{
_addr.sin_addr.S_un.S_addr = htonl(ADDR_ANY);
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
int err = bind(_sock, (SOCKADDR *)&_addr, sizeof(SOCKADDR));
if (err == SOCKET_ERROR)
{
err = WSAGetLastError();
cout << "bind with error!, error code is " << err << endl;
return FALSE;
}
return TRUE;
}
void HoleSrv(SOCKET & _sock, SOCKADDR_IN & _addr)
{
SOCKADDR_IN addrClient = { 0 };
int iAddrClientLen = sizeof(SOCKADDR);
char RecvBuf[512] = { 0 };
int iErr = 0;
for (;1;)
{
//Get the first Client's cmd.
iErr = recvfrom(_sock,
RecvBuf,
512,
0,
(SOCKADDR *)&addrClient,
&iAddrClientLen);
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "recvfrom with error(), error code is " << iErr << endl;
return;
}
/* out to sreen the Client's IP and Cmd. */
cout << inet_ntoa(addrClient.sin_addr) << " : " << RecvBuf << " Port : " << ntohs(addrClient.sin_port) << endl;
//if the cmd is Hole,then will send he's addr to Other one.
if (strcmp(RecvBuf, "Hole") == 0)
{
SOCKADDR_IN AddrClientTemp = { 0 };
int iAddrClientTempLen = sizeof(SOCKADDR);
iErr = recvfrom(_sock,
RecvBuf,
512,
0,
(SOCKADDR *)&AddrClientTemp,
&iAddrClientTempLen);
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "recvfrom() with error, error code is " << iErr << endl;
return;
}
/* out to sreen the Client's IP and Cmd. */
cout << inet_ntoa(AddrClientTemp.sin_addr) << " : " << RecvBuf << " Port : " << ntohs(AddrClientTemp.sin_port) << endl;
//if the cmd is Hole,then will send the address to each other.
if (strcmp(RecvBuf, "Hole") == 0)
{
//swap send SOCKADDR
/* Firster's info to laster */
iErr = sendto(_sock,
(char *)&addrClient,
sizeof(SOCKADDR_IN),
0,
(SOCKADDR *)&AddrClientTemp,
sizeof(SOCKADDR));
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "sendto() with error, error code is " << iErr << endl;
return;
}
/* Laster's info to firster */
iErr = sendto(_sock,
(char *)&AddrClientTemp,
sizeof(SOCKADDR_IN),
0,
(SOCKADDR *)&addrClient,
sizeof(SOCKADDR));
if (iErr == SOCKET_ERROR)
{
//Get error code.
iErr = WSAGetLastError();
cout << "sendto() with error, error code is " << iErr << endl;
return;
}
//Tip
cout << inet_ntoa(addrClient.sin_addr) << " and " << inet_ntoa(AddrClientTemp.sin_addr) << "Hole successful!" << endl;
}
}
}
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
//Init Socket envirement.
if (AfxSocketInit() == 0)
return FALSE;
//InitSocket.
if (InitSocketToUDP(sockSrv) == FALSE)
return FALSE;
//Bind sockSrv to port.
if (Bind(sockSrv, 5174, addrSrv) == FALSE)//5174
return FALSE;
//Begin Hole Srv.
HoleSrv(sockSrv, addrSrv);
// TODO: 在此处为应用程序的行为编写代码。
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
system("pause");
return nRetCode;
}本文转自https://blog.csdn.net/u011580175/article/details/71001796
- 文章2314
- 用户1336
- 访客11813325
让我们在简单宁静的时刻中寻找快乐。