C++实现UDP打洞

Home / C++ MrLee 2018-8-10 3049

这两天一直在研究这个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

本文链接:https://www.it72.com/12401.htm

推荐阅读
最新回复 (0)
返回