⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 p2pclient.cpp

📁 实现P2P NAT 客户端之间的通信,可以在同一LAN之间通信,也可以在不同LAN中相互通信
💻 CPP
字号:


#include <assert.h>
#include <stdio.h>
#include "P2PClient.h"

P2PClient::P2PClient()//constructor function
	: m_sSocket(INVALID_SOCKET)
	, m_bExitThread(false)
	, m_bLogin(false)
	, m_bUserListCmp(false)
	, m_bMessageACK(false)
{
	Initialize();
}

P2PClient::~P2PClient()//deconstructor function
{
	Logout();//退出程序

	// 通知接收线程退出
	if(m_hThread != NULL)
	{
		m_bExitThread = TRUE;
		::WSASetEvent(m_ol.hEvent);
		::WaitForSingleObject(m_hThread, 300);
		::CloseHandle(m_hThread);
	}

	if(INVALID_SOCKET != m_sSocket)
	{
		::closesocket(m_sSocket);
	}

	::WSACloseEvent(m_ol.hEvent);

	::DeleteCriticalSection(&m_PeerListLock);
	::WSACleanup();
}

bool P2PClient::Initialize()
{
	if (INVALID_SOCKET != m_sSocket)
	{
		printf("Error: Socket Already Been Initialized!\n");
		return false;
	}

	// 初始化WS2_32.dll
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	if(::WSAStartup(sockVersion, &wsaData) != 0)
	{
		printf("Error: Initialize WS2_32.dll Failed!\n");
		exit(-1);
	}

	// 初始化临界区
	::InitializeCriticalSection(&m_PeerListLock);

	// 
	memset(&m_ol, 0, sizeof(m_ol));
	m_ol.hEvent = ::WSACreateEvent();

	// 创建一个重叠I/O socket
	m_sSocket = ::WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (INVALID_SOCKET == m_sSocket)
	{
		printf("Error: Initialize Socket Failed!\n");
		return false;
	}

	// 绑定socket
	// 注意这里分配地址是INADDR_ANY,表示让系统
	// 给这次连接随机分配一个端口号
	sockaddr_in addr = {0};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(0);
	addr.sin_addr.S_un.S_addr = INADDR_ANY;
	int nAddrLen = sizeof(addr);
	if (SOCKET_ERROR == ::bind(m_sSocket, (sockaddr*)(&addr), nAddrLen))
	{
		printf("Error: Bind Socket Failed!\n");
		::closesocket(m_sSocket);
		return false;
	}

	// 得到有效的端口号
	::getsockname(m_sSocket, (sockaddr*)(&addr), &nAddrLen);
	unsigned short usPort = ntohs(addr.sin_port);

	// 得到本机的IP地址
	char szHost[256];
	::gethostname(szHost, 256);
	hostent* pHost = ::gethostbyname(szHost);

	// 得到本机所有适配器的IP地址和端口号,这些就是私有地址/端口号
	char *pIP;
	for (int i = 0; i < MAX_ADDNUM - 1; ++i)
	{
		if (NULL == (pIP = pHost->h_addr_list[i]))
		{
			break;
		}
		
		memcpy(&(m_PeerInfo.IPAddr[i].dwIP), pIP, pHost->h_length);
		m_PeerInfo.IPAddr[i].usPort = usPort;
		++m_PeerInfo.nAddrNum;
	}

	// 创建接收进程
	if (NULL == (::CreateThread(NULL, 0, RecvThreadProc, this, 0, NULL)))
	{
		printf("Error: Create Thread Failed!\n");
		return false;
	}

	return true;
}

bool P2PClient::Login(char *pszUserName, char *pszServerIP)
{
	assert(NULL != pszUserName);
	assert(NULL != pszServerIP);

	int nUserNameLen;
	if ((nUserNameLen = (int)strlen(pszUserName)) > MAX_USERNAME - 1)
	{
		printf("Error: Input User Name Too Large!\n");
		return false;
	}

	if (true == m_bLogin)
	{
		printf("Error: Already Login Server!\n");
		return false;
	}

	// 保存server端IP地址和用户名
	m_dwServerIP = ::inet_addr(pszServerIP);
	memcpy(m_PeerInfo.szUserName, pszUserName, nUserNameLen);

	// 
	sockaddr_in serverAddr = {0};
	serverAddr.sin_addr.S_un.S_addr = m_dwServerIP;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(SERVER_PORT);

	// 向server发送登陆消息
	MSGDef::TMSG_USERLOGIN tUserLogin(m_PeerInfo);
	for(int i = 0; i< MAX_TRY_NUMBER; ++i)
	{
		::sendto(m_sSocket, (char*)(&tUserLogin), sizeof(MSGDef::TMSG_USERLOGIN), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));

		// 等待登陆成功才退出
		for(int j = 0; j < 10; j++)
		{
			if(m_bLogin)
			{
				return true;
			}
			::Sleep(300);
		}
	}

	printf("Error: Login Server Failed!\n");
	return false;
}

bool P2PClient::Logout()
{
	if (true == m_bLogin)
	{
		sockaddr_in serverAddr = {0};
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.S_un.S_addr = m_dwServerIP;
		serverAddr.sin_port = htons(SERVER_PORT);

		MSGDef::TMSG_USERLOGOUT tUserLogout(m_PeerInfo);
		::sendto(m_sSocket, (char*)(&tUserLogout), sizeof(tUserLogout), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));
		m_bLogin = false;

		return true;
	}
	else
	{
		return false;
	}
}

bool P2PClient::GetUserList()
{
	sockaddr_in serverAddr = {0};
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.S_un.S_addr = m_dwServerIP;
	serverAddr.sin_port = ntohs(SERVER_PORT);

	MSGDef::TMSG_GETUSERLIST tMsgGetUserList(m_PeerInfo);

	// 
	::EnterCriticalSection(&m_PeerListLock);
	m_bUserListCmp = false;
	m_PeerList.DeleteAllPeer();
	::LeaveCriticalSection(&m_PeerListLock);

	for (int i= 0; i < MAX_TRY_NUMBER; ++i)
	{
		::sendto(m_sSocket, (char*)(&tMsgGetUserList), sizeof(tMsgGetUserList), 0, (sockaddr*)(&serverAddr), sizeof(sockaddr_in));

		// 等待用户列表更新完毕
		for (int j = 0; j < 10; ++j)
		{
			if (true == m_bUserListCmp)
			{
				return true;
			}
			::Sleep(300);
		}
	}

	return false;
}

void P2PClient::DisplayUserList()
{
	int nCurrentSize = m_PeerList.GetCurrentSize();
	printf("Have %d Users Logined Server: \n", nCurrentSize);
	int nAddrNum;
	for(int i = 0; i< nCurrentSize; i++)
	{
		Peer_Info* pPeerInfo = m_PeerList[i];
		nAddrNum = pPeerInfo->nAddrNum;
		printf("User Name: %s(%s:%ld) \n", pPeerInfo->szUserName, 
			::inet_ntoa(*((in_addr*)&pPeerInfo->IPAddr[nAddrNum - 1].dwIP)), 
			pPeerInfo->IPAddr[nAddrNum - 1].usPort);
	}
}

bool P2PClient::SendText(char *pszUserName, char* pszText, int nTextLen)
{
	if (NULL == pszUserName || NULL == pszText 
		|| strlen(pszUserName) > MAX_USERNAME
		|| nTextLen > MAX_PACKET_SIZE- sizeof(Peer_Info) - sizeof(MSGDef::TMSG_HEADER))
	{
		return false;
	}

	MSGDef::TMSG_P2PMSG tP2PMsg(m_PeerInfo);
	strcpy(tP2PMsg.szMsg, pszText);
	m_bMessageACK = false;
	Peer_Info* pPeerInfo;
	int j;
	for (int i = 0; i < MAX_TRY_NUMBER; ++i)
	{
		pPeerInfo = m_PeerList.GetAPeer(pszUserName);
		if (NULL == pPeerInfo)
		{
			return false;
		}

		// 如果对方P2P地址不为0,就试图以它为目的地址发送数据,
		// 如果发送失败,则认为此P2P地址无效
		if (0 != pPeerInfo->P2PAddr.dwIP)
		{
			sockaddr_in peerAddr = {0};
			peerAddr.sin_family = AF_INET;
			peerAddr.sin_port = ntohs(pPeerInfo->P2PAddr.usPort);
			peerAddr.sin_addr.S_un.S_addr = pPeerInfo->P2PAddr.dwIP;

			::sendto(m_sSocket, (char*)(&tP2PMsg), sizeof(tP2PMsg), 0, (sockaddr*)(&peerAddr), sizeof(peerAddr));

			// 等待一段时间看发送成功了没有
			for (int j = 0; i < 10; ++j)
			{
				if (true == m_bMessageACK)
				{
					return true;
				}
				::Sleep(300);
			}
		}

		// 请求打洞,并且重新设置P2P地址
		pPeerInfo->P2PAddr.dwIP = 0;

		// 构建P2P打洞封包
		MSGDef::TMSG_P2PCONNECT tP2PConnect(m_PeerInfo);
		strcpy(tP2PConnect.szUserName, pszUserName);

		// 首先直接发向目标,向目标节点的所有适配器发送打洞消息
		sockaddr_in peerAddr = { 0 };
		peerAddr.sin_family = AF_INET;
		int nAddrNum = pPeerInfo->nAddrNum;
		for(j = 0; j < nAddrNum; ++j)
		{
			peerAddr.sin_addr.S_un.S_addr = pPeerInfo->IPAddr[j].dwIP;
			peerAddr.sin_port = htons(pPeerInfo->IPAddr[j].usPort);
			::sendto(m_sSocket, (char*)(&tP2PConnect), sizeof(tP2PConnect), 0, (sockaddr*)&peerAddr, sizeof(peerAddr));
		}

		// 然后通过服务器转发,请求对方向自己打洞
		sockaddr_in serverAddr = { 0 };
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_addr.S_un.S_addr = m_dwServerIP;
		serverAddr.sin_port = htons(SERVER_PORT);
		::sendto(m_sSocket, (char*)(&tP2PConnect), sizeof(tP2PConnect), 0, (sockaddr*)&serverAddr, sizeof(serverAddr));

		// 等待对方的P2PCONNECTACK消息
		for(j = 0; j < 10; ++j)
		{
			if (0 != pPeerInfo->P2PAddr.dwIP)
				break;
			::Sleep(300);
		}	

	}

	return false;
}

DWORD WINAPI P2PClient::RecvThreadProc(LPVOID lpParam)
{
	P2PClient* pThis = (P2PClient*)lpParam;
	char szBuff[MAX_PACKET_SIZE];
	int nRet;
	sockaddr_in remoteAddr = {0};
	int nAddrLen = sizeof(sockaddr_in);
	WSABUF wsaBuff;
	wsaBuff.buf = szBuff;
	wsaBuff.len = MAX_PACKET_SIZE;
	DWORD dwRecv, dwFlags = 0;
	MSGDef::TMSG_HEADER *pMsgHeader;

	while (true)
	{
		nRet = ::WSARecvFrom(pThis->m_sSocket, &wsaBuff, 1, &dwRecv, &dwFlags, 
							(sockaddr*)(&remoteAddr), &nAddrLen, &pThis->m_ol, NULL);
		
		// 
		if(SOCKET_ERROR == nRet && ::WSAGetLastError() == WSA_IO_PENDING)
		{
			::WSAGetOverlappedResult(pThis->m_sSocket, &pThis->m_ol, &dwRecv, TRUE, &dwFlags);
		}

		// 首先查看是否要退出
		if(pThis->m_bExitThread)
			break;

		// 解析不同的消息进行处理
		pMsgHeader = (MSGDef::TMSG_HEADER*)szBuff;
		switch (pMsgHeader->cMsgID)
		{
		case MSG_USERLOGACK:			// 接收到服务器发来的登陆确认
			{
				pThis->ProcUserLogAckMsg(pMsgHeader);
			}
			break;
		case MSG_GETUSERLIST:			// 更新用户列表
			{
				pThis->ProcGetUserList(pMsgHeader);
			}
			break;
		case MSG_USERLISTCMP:			// 更新用户列表完毕
			{
				pThis->ProcUserListCmpMsg();
			}
			break;
		case MSG_P2PMSG:				// 有一个节点向我们发送消息
			{
				pThis->ProcP2PMsg(pMsgHeader, remoteAddr);
			}
			break;
		case MSG_P2PMSGACK:				// 向某个节点发送消息之后的回复
			{
				pThis->m_bMessageACK = true;
			}
			break;
		case MSG_P2PCONNECT:			// 请求打洞
			{
				pThis->ProcP2PConnectMsg(pMsgHeader, remoteAddr);
			}
			break;
		case MSG_P2PCONNECTACK:			// 请求打洞的回复
			{
				pThis->ProcP2PConnectAckMsg(pMsgHeader, remoteAddr);
			}
			break;
		case MSG_USERACTIVEQUERY:
			{
				pThis->ProcUserActiveQueryMsg(pMsgHeader, remoteAddr);
			}
			break;
		}
	}

	return 0;
}

// 接收到服务器发来的登陆确认
bool P2PClient::ProcUserLogAckMsg(MSGDef::TMSG_HEADER *pMsgHeader)
{
	MSGDef::TMSG_USERLOGACK *pUserLogAckMsg = (MSGDef::TMSG_USERLOGACK *)pMsgHeader;

	memcpy(&m_PeerInfo, &pUserLogAckMsg->PeerInfo, sizeof(Peer_Info));
	int nAddrNum =  pUserLogAckMsg->PeerInfo.nAddrNum;
	in_addr LoginAddr;
	LoginAddr.S_un.S_addr = pUserLogAckMsg->PeerInfo.IPAddr[nAddrNum].dwIP;
	printf("Login IP: %s\n", ::inet_ntoa(LoginAddr));
	printf("Login port: %ld\n", ntohs(pUserLogAckMsg->PeerInfo.IPAddr[nAddrNum].usPort));

	m_bLogin = true;

	printf("Login P2P Server Success!\n");
	return true;
}

// 更新用户列表
bool P2PClient::ProcGetUserList(MSGDef::TMSG_HEADER *pMsgHeader)
{
	MSGDef::TMSG_GETUSERLIST* pMsgGetUserList = (MSGDef::TMSG_GETUSERLIST*)pMsgHeader;

	pMsgGetUserList->PeerInfo.P2PAddr.dwIP = 0;

	::EnterCriticalSection(&m_PeerListLock);
	m_PeerList.AddPeer(pMsgGetUserList->PeerInfo);
	::LeaveCriticalSection(&m_PeerListLock);

	return true;
}

// 更新用户列表完毕
bool P2PClient::ProcUserListCmpMsg()
{
	m_bUserListCmp = true;

	return true;
}

// 有一个节点向我们发送消息
bool P2PClient::ProcP2PMsg(MSGDef::TMSG_HEADER *pMsgHeader, const sockaddr_in& sockAddr)
{
	MSGDef::TMSG_P2PMSG *pP2PMsg = (MSGDef::TMSG_P2PMSG *)pMsgHeader;

	// 发送收到消息的确认消息
	MSGDef::TMSG_P2PMSGACK tP2PMsgAck(m_PeerInfo);
	::sendto(m_sSocket, (char*)(&tP2PMsgAck), sizeof(tP2PMsgAck), 0, (sockaddr*)(&sockAddr), sizeof(sockAddr));

	printf("Receive a Message from %s :  %s \n", pP2PMsg->PeerInfo.szUserName, pP2PMsg->szMsg);

	return true;
}

// 一个节点请求建立P2P连接(打洞),可能是服务器发来的,也可能是其它节点发来的
bool P2PClient::ProcP2PConnectMsg(MSGDef::TMSG_HEADER *pMsgHeader, const sockaddr_in& sockAddr)
{
	MSGDef::TMSG_P2PCONNECT *pP2PConnect = (MSGDef::TMSG_P2PCONNECT *)pMsgHeader;
	MSGDef::TMSG_P2PCONNECTACK tP2PConnectAck(m_PeerInfo);

	if (sockAddr.sin_addr.S_un.S_addr != m_dwServerIP)	// 节点发过来的消息
	{
		::EnterCriticalSection(&m_PeerListLock);
		Peer_Info *pPeerInfo = m_PeerList.GetAPeer( pP2PConnect->PeerInfo.szUserName);

		if (NULL != pPeerInfo)
		{
			if (0 == pPeerInfo->P2PAddr.dwIP)
			{
				// 更新该节点用于P2P通信的IP地址和端口号
				pPeerInfo->P2PAddr.dwIP = sockAddr.sin_addr.S_un.S_addr;
				pPeerInfo->P2PAddr.usPort = ntohs(sockAddr.sin_port);
				printf("Set P2P Address For %s -> %s:%ld \n", pPeerInfo->szUserName, 
					::inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port));
			}
		}
		::LeaveCriticalSection(&m_PeerListLock);
		::sendto(m_sSocket, (char*)(&tP2PConnectAck), sizeof(tP2PConnectAck), 0, (sockaddr*)(&sockAddr), sizeof(sockAddr));
	}
	else												// 服务器转发的消息
	{
		// 向节点的所有终端发送打洞消息
		sockaddr_in peerAddr = { 0 };
		peerAddr.sin_family = AF_INET;
		for(int i = 0, nAddrNum = pP2PConnect->PeerInfo.nAddrNum; i < nAddrNum; ++i)
		{
			peerAddr.sin_addr.S_un.S_addr = pP2PConnect->PeerInfo.IPAddr[i].dwIP;
			peerAddr.sin_port = htons(pP2PConnect->PeerInfo.IPAddr[i].usPort);
			::sendto(m_sSocket, (char*)(&tP2PConnectAck), sizeof(tP2PConnectAck), 0, (sockaddr*)(&peerAddr), sizeof(peerAddr));
		}
	}

	return true;
}

// 接收到节点的打洞消息,在这里设置该节点的P2P通信地址
bool P2PClient::ProcP2PConnectAckMsg(MSGDef::TMSG_HEADER *pMsgHeader, const sockaddr_in& sockAddr)
{
	MSGDef::TMSG_P2PCONNECTACK* pP2PConnectAck = (MSGDef::TMSG_P2PCONNECTACK*)pMsgHeader;

	::EnterCriticalSection(&m_PeerListLock);
	Peer_Info* pPeerInfo = m_PeerList.GetAPeer(pP2PConnectAck->PeerInfo.szUserName);

	if (NULL != pPeerInfo)
	{
		if (0 == pPeerInfo->P2PAddr.dwIP)
		{
			pPeerInfo->P2PAddr.dwIP = sockAddr.sin_addr.S_un.S_addr;
			pPeerInfo->P2PAddr.usPort = ntohs(sockAddr.sin_port);

			printf("Set P2P address for %s -> %s:%ld \n", pP2PConnectAck->PeerInfo.szUserName, 
				::inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port));
		}
	}
	::LeaveCriticalSection(&m_PeerListLock);

	return true;
}

// 服务器询问是否存活
bool P2PClient::ProcUserActiveQueryMsg(MSGDef::TMSG_HEADER *pMsgHeader, const sockaddr_in& sockAddr)
{
	MSGDef::TMSG_USERACTIVEQUERY tUserActiveQuery(m_PeerInfo);
	::sendto(m_sSocket, (char*)(&tUserActiveQuery), sizeof(tUserActiveQuery), 0, (sockaddr*)&sockAddr, sizeof(sockAddr));

	return true;
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -