大家好:
今天我们的源码示例为"cpp net穿透源码-1.UDP穿透".目标是完成两个内网的用户进行网络穿透的P2P.其中A,B为不同内网的主机,C为一可以公共访问的外网主机.
以下是客户端Client的源码,P2PClient.cpp的Source:
#pragma comment(lib,"ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
#include <iostream>
using namespace std;
UserList ClientList;
#define COMMANDMAXC 256
#define MAXRETRY 5
SOCKET PrimaryUDP;
sockaddr_in PrimaryUDP_sin;
SOCKET PrimaryTCP = 0;
char UserName[10];
char ServerIP[20] = "50.18.251.0";
bool RecvedACK;
void InitWinSock()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Windows sockets 2.2 startup");
throw Exception("");
}
else{
printf("Using %s (Status: %s)\n",
wsaData.szDescription, wsaData.szSystemStatus);
printf("with API versions %d.%d to %d.%d\n\n",
LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
}
}
SOCKET mksock(int type)
{
SOCKET sock = socket(AF_INET, type, 0);
if (sock < 0)
{
printf("create socket error");
throw Exception("");
}
return sock;
}
stUserListNode GetUser(char *username)
{
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), username) == 0 )
return *(*UserIterator);
}
throw Exception("not find this user");
}
void BindSock()
{
PrimaryUDP_sin.sin_addr.S_un.S_addr = INADDR_ANY;
PrimaryUDP_sin.sin_family = AF_INET;
PrimaryUDP_sin.sin_port = 0;
int flag=1,flaglen=sizeof(int);
// 重复利用套接字
flag = setsockopt(PrimaryUDP, SOL_SOCKET, SO_REUSEADDR, (const char*)&flag, flaglen);
if (bind(PrimaryUDP, (struct sockaddr*)&PrimaryUDP_sin, sizeof(PrimaryUDP_sin)) < 0)
throw Exception("bind error");
int addr_len = sizeof(PrimaryUDP_sin);
if (0 != getsockname(PrimaryUDP, (struct sockaddr*)&PrimaryUDP_sin, &addr_len))
{
throw Exception("bind getsockname error");
}
}
// 接受消息线程
DWORD WINAPI AcceptThreadProc(LPVOID lpParameter)
{
SOCKET connfd;
sockaddr_in sin;
int addr_len = sizeof(sin);
for(;;)
{
if( (connfd =accept(PrimaryTCP,(struct sockaddr*)NULL,NULL)) == -1)
{
cout<<"accept error" << endl;
continue;
}
if (0 != getpeername(connfd, (struct sockaddr*)&sin, &addr_len))
{
cout<<"getpeername accept client error" << endl;
}else{
cout<<"new client connected ok:" << inet_ntoa(sin.sin_addr) << ":" << htons(sin.sin_port) << endl;
}
closesocket(connfd);
}
}
// 绑定本地的UDP端口来监听TCP连接
void ListenTcpSocket()
{
if(PrimaryTCP!=0)
return;
PrimaryTCP = mksock(SOCK_STREAM);
int flag=1,flaglen=sizeof(int);
flag = setsockopt(PrimaryTCP, SOL_SOCKET, SO_REUSEADDR, (const char*)&flag, flaglen);
if (bind(PrimaryTCP, (struct sockaddr*)&PrimaryUDP_sin, sizeof(PrimaryUDP_sin)) < 0)
throw Exception("bind tcp socket error");
if( listen(PrimaryTCP,0) == -1) {
throw Exception("listen tcp socket error");
}
cout << "listen tcp socket ok:" << ntohs(PrimaryUDP_sin.sin_port) << endl;
HANDLE threadhandle = CreateThread(NULL, 0, AcceptThreadProc, NULL, NULL, NULL);
CloseHandle(threadhandle);
}
DWORD WINAPI ConnectThreadProc(LPVOID lpParameter)
{
sockaddr_in* remote = (sockaddr_in*)lpParameter;
int remotelen = sizeof(sockaddr_in);
SOCKET tcp = mksock(SOCK_STREAM);
int flag=1,flaglen=sizeof(int);
flag = setsockopt(tcp, SOL_SOCKET, SO_REUSEADDR, (const char*)&flag, flaglen);
if (bind(tcp, (struct sockaddr*)&PrimaryUDP_sin, sizeof(PrimaryUDP_sin)) < 0){
remotelen = GetLastError();
throw Exception("bind tcp socket error");
}
unsigned long uio = 1;
ioctlsocket(tcp,FIOASYNC,&uio);
if(0==connect(tcp,(sockaddr*)remote,remotelen)){
cout << "tcp connect ok to:" << inet_ntoa(remote->sin_addr) << ":" << htons(remote->sin_port) << endl;
}else{
cout << "tcp connect error to:" << inet_ntoa(remote->sin_addr) << ":" << htons(remote->sin_port) << endl;
}
closesocket(tcp);
}
// 绑定本地的UDP端口来连接远程TCP连接
void ConnectTcpSocket(sockaddr_in* remote, int remotelen)
{
HANDLE threadhandle = CreateThread(NULL, 0, ConnectThreadProc, remote, NULL, NULL);
CloseHandle(threadhandle);
}
void ConnectToServer(SOCKET sock,char *username, char *serverip)
{
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = inet_addr(serverip);
remote.sin_family = AF_INET;
remote.sin_port = htons(SERVER_PORT);
stMessage sendbuf;
sendbuf.iMessageType = LOGIN;
strncpy(sendbuf.message.loginmember.userName, username, 10);
sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote));
int usercount;
int fromlen = sizeof(remote);
int iread = recvfrom(sock, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
if(iread<=0)
{
throw Exception("Login error\n");
}
// 登录到服务端后,接收服务端发来的已经登录的用户的信息
cout<<"Have "<<usercount<<" users logined server:"<<endl;
for(int i = 0;i<usercount;i++)
{
stUserListNode *node = new stUserListNode;
recvfrom(sock, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
ClientList.push_back(node);
cout<<"Username:"<<node->userName<<endl;
in_addr tmp;
tmp.S_un.S_addr = htonl(node->ip);
cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
cout<<"UserPort:"<<node->port<<endl;
cout<<""<<endl;
}
}
void OutputUsage()
{
cout<<"You can input you command:\n"
<<"Command Type:\"send\",\"exit\",\"getu\"\n"
<<"Example : send Username Message\n"
<<" exit\n"
<<" getu\n"
<<endl;
}
/* 这是主要的函数:发送一个消息给某个用户(C)
*流程:直接向某个用户的外网IP发送消息,如果此前没有联系过
* 那么此消息将无法发送,发送端等待超时。
* 超时后,发送端将发送一个请求信息到服务端,
* 要求服务端发送给客户C一个请求,请求C给本机发送打洞消息
* 以上流程将重复MAXRETRY次
*/
bool SendMessageTo(char *UserName, char *Message)
{
char realmessage[256];
unsigned int UserIP;
unsigned short UserPort;
bool FindUser = false;
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
{
UserIP = (*UserIterator)->ip;
UserPort = (*UserIterator)->port;
FindUser = true;
}
}
if(!FindUser)
return false;
strcpy(realmessage, Message);
for(int i=0;i<MAXRETRY;i++)
{
RecvedACK = false;
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = htonl(UserIP);
remote.sin_family = AF_INET;
remote.sin_port = htons(UserPort);
stP2PMessage MessageHead;
MessageHead.iMessageType = P2PMESSAGE;
MessageHead.iStringLen = (int)strlen(realmessage)+1;
int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));
// 等待接收线程将此标记修改
for(int j=0;j<10;j++)
{
if(RecvedACK)
return true;
else
Sleep(300);
}
// 没有接收到目标主机的回应,认为目标主机的端口映射没有
// 打开,那么发送请求信息给服务器,要服务器告诉目标主机
// 打开映射端口(UDP打洞)
sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
stMessage transMessage;
transMessage.iMessageType = P2PTRANS;
strcpy(transMessage.message.translatemessage.userName, UserName);
sendto(PrimaryUDP, (const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr*)&server, sizeof(server));
Sleep(100);// 等待对方先发送信息。
}
return false;
}
// 解析命令,暂时只有exit和send命令
// 新增getu命令,获取当前服务器的所有用户
void ParseCommand(char * CommandLine)
{
if(strlen(CommandLine)<4)
return;
char Command[10];
strncpy(Command, CommandLine, 4);
Command[4]='\0';
if(strcmp(Command,"exit")==0)
{
stMessage sendbuf;
sendbuf.iMessageType = LOGOUT;
strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
sendto(PrimaryUDP,(const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server, sizeof(server));
shutdown(PrimaryUDP, 2);
closesocket(PrimaryUDP);
exit(0);
}
else if(strcmp(Command,"send")==0)
{
char sendname[20];
char message[COMMANDMAXC];
int i;
for(i=5;;i++)
{
if(CommandLine[i]!=' ')
sendname[i-5]=CommandLine[i];
else
{
sendname[i-5]='\0';
break;
}
}
strcpy(message, &(CommandLine[i+1]));
if(SendMessageTo(sendname, message))
printf("Send OK!\n");
else
printf("Send Failure!\n");
}
else if(strcmp(Command,"getu")==0)
{
int command = GETALLUSER;
sockaddr_in server;
server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
sendto(PrimaryUDP,(const char*)&command, sizeof(command), 0, (const sockaddr *)&server, sizeof(server));
}
}
// 接受消息线程
DWORD WINAPI RecvThreadProc(LPVOID lpParameter)
{
sockaddr_in remote;
int sinlen = sizeof(remote);
stP2PMessage recvbuf;
for(;;)
{
int iread = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr *)&remote, &sinlen);
if(iread<=0)
{
printf("recv error\n");
continue;
}
switch(recvbuf.iMessageType)
{
case P2PMESSAGE:
{
// 接收到P2P的消息
char *comemessage= new char[recvbuf.iStringLen];
int iread1 = recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote, &sinlen);
comemessage[iread1-1] = '\0';
if(iread1<=0)
throw Exception("Recv Message Error\n");
else
{
printf("Recv a Message:%s\n",comemessage);
stP2PMessage sendbuf;
sendbuf.iMessageType = P2PMESSAGEACK;
sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
}
delete []comemessage;
break;
}
case P2PSOMEONEWANTTOCALLYOU:
{
// 接收到打洞命令,向指定的IP地址打洞
printf("Recv p2someonewanttocallyou data\n");
ListenTcpSocket();
sockaddr_in remote;
ZeroMemory(&remote,sizeof(remote));
remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
remote.sin_family = AF_INET;
remote.sin_port = htons(recvbuf.Port);
ConnectTcpSocket(&remote,sizeof(remote));
// UDP hole punching
stP2PMessage message;
message.iMessageType = P2PTRASH;
sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));
break;
}
case P2PMESSAGEACK:
{
// 发送消息的应答
RecvedACK = true;
printf("Recv P2PMESSAGEACK\n");
break;
}
case P2PTRASH:
{
// 对方发送的打洞消息,忽略掉。
//do nothing ...
ListenTcpSocket();
printf("Recv p2ptrash data prepare tcp connect\n");
ConnectTcpSocket(&remote,sinlen);
break;
}
case GETALLUSER:
{
int usercount;
int fromlen = sizeof(remote);
int iread = recvfrom(PrimaryUDP, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
if(iread<=0)
{
throw Exception("Login error\n");
}
ClientList.clear();
cout<<"Have "<<usercount<<" users logined server:"<<endl;
for(int i = 0;i<usercount;i++)
{
stUserListNode *node = new stUserListNode;
recvfrom(PrimaryUDP, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
ClientList.push_back(node);
cout<<"Username:"<<node->userName<<endl;
in_addr tmp;
tmp.S_un.S_addr = htonl(node->ip);
cout<<"UserIP:"<<inet_ntoa(tmp)<<endl;
cout<<"UserPort:"<<node->port<<endl;
cout<<""<<endl;
}
break;
}
}
}
}
int main(int argc, char* argv[])
{
try
{
InitWinSock();
PrimaryUDP = mksock(SOCK_DGRAM);
BindSock();
if(!ServerIP[0]){
cout<<"Please input server ip:";
cin>>ServerIP;
}
cout<<"Please input your name:";
cin>>UserName;
ConnectToServer(PrimaryUDP, UserName, ServerIP);
HANDLE threadhandle = CreateThread(NULL, 0, RecvThreadProc, NULL, NULL, NULL);
CloseHandle(threadhandle);
OutputUsage();
for(;;)
{
char Command[COMMANDMAXC];
gets(Command);
ParseCommand(Command);
}
}
catch(Exception &e)
{
printf(e.GetMessage());
return 1;
}
return 0;
}
以下是客户端Server的源码,P2PServer.cpp的Source:
#pragma comment(lib, "ws2_32.lib")
#include "windows.h"
#include "..\proto.h"
#include "..\Exception.h"
UserList ClientList;
void InitWinSock()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Windows sockets 2.2 startup");
throw Exception("");
}
else{
printf("Using %s (Status: %s)\n",
wsaData.szDescription, wsaData.szSystemStatus);
printf("with API versions %d.%d to %d.%d\n\n",
LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
}
}
SOCKET mksock(int type)
{
SOCKET sock = socket(AF_INET, type, 0);
if (sock < 0)
{
printf("create socket error");
throw Exception("");
}
return sock;
}
stUserListNode GetUser(char *username)
{
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), username) == 0 )
return *(*UserIterator);
}
throw Exception("not find this user");
}
int main(int argc, char* argv[])
{
try{
InitWinSock();
SOCKET PrimaryUDP;
PrimaryUDP = mksock(SOCK_DGRAM);
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port= htons(SERVER_PORT);
local.sin_addr.s_addr = htonl(INADDR_ANY);
int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
throw Exception("bind error");
sockaddr_in sender;
stMessage recvbuf;
memset(&recvbuf,0,sizeof(stMessage));
// 开始主循环.
// 主循环负责下面几件事情:
// 一:读取客户端登陆和登出消息,记录客户列表
// 二:转发客户p2p请求
for(;;)
{
int dwSender = sizeof(sender);
int ret = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
if(ret <= 0)
{
printf("recv error");
continue;
}
else
{
int messageType = recvbuf.iMessageType;
switch(messageType){
case LOGIN:
{
// 将这个用户的信息记录到用户列表中
printf("has a user login : %s\n", recvbuf.message.loginmember.userName);
stUserListNode *currentuser = new stUserListNode();
strcpy(currentuser->userName, recvbuf.message.loginmember.userName);
currentuser->ip = ntohl(sender.sin_addr.S_un.S_addr);
currentuser->port = ntohs(sender.sin_port);
ClientList.push_back(currentuser);
// 发送已经登陆的客户信息
int nodecount = (int)ClientList.size();
sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
}
break;
}
case LOGOUT:
{
// 将此客户信息删除
printf("has a user logout : %s\n", recvbuf.message.logoutmember.userName);
UserList::iterator removeiterator = ClientList.end();
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
{
removeiterator = UserIterator;
break;
}
}
if(removeiterator != ClientList.end())
ClientList.remove(*removeiterator);
break;
}
case P2PTRANS:
{
// 某个客户希望服务端向另外一个客户发送一个打洞消息
printf("%s wants to p2p %s\n",inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
stUserListNode node = GetUser(recvbuf.message.translatemessage.userName);
sockaddr_in remote;
remote.sin_family=AF_INET;
remote.sin_port= htons(node.port);
remote.sin_addr.s_addr = htonl(node.ip);
in_addr tmp;
tmp.S_un.S_addr = htonl(node.ip);
printf("the address is %s,and port is %d\n",inet_ntoa(tmp), node.port);
stP2PMessage transMessage;
transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
transMessage.Port = ntohs(sender.sin_port);
sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote));
break;
}
case GETALLUSER:
{
int command = GETALLUSER;
sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
int nodecount = (int)ClientList.size();
sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
}
break;
}
}
}
}
}
catch(Exception &e)
{
printf(e.GetMessage());
return 1;
}
return 0;
}
以下是公用文件proto.h的source:
1:
2: #pragma once
3: #include <list>
4:
5: // 定义iMessageType的值
6: #define LOGIN 1
7: #define LOGOUT 2
8: #define P2PTRANS 3
9: #define GETALLUSER 4
10:
11: // 服务器端口
12: #define SERVER_PORT 2280
13:
14: // Client登录时向服务器发送的消息
15: struct stLoginMessage
16: {
17: char userName[10];
18: char password[10];
19: };
20:
21: // Client注销时发送的消息
22: struct stLogoutMessage
23: {
24: char userName[10];
25: };
26:
27: // Client向服务器请求另外一个Client(userName)向自己方向发送UDP打洞消息
28: struct stP2PTranslate
29: {
30: char userName[10];
31: };
32:
33: // Client向服务器发送的消息格式
34: struct stMessage
35: {
36: int iMessageType;
37: union _message
38: {
39: stLoginMessage loginmember;
40: stLogoutMessage logoutmember;
41: stP2PTranslate translatemessage;
42: }message;
43: };
44:
45: // 客户节点信息
46: struct stUserListNode
47: {
48: char userName[10];
49: unsigned int ip;
50: unsigned short port;
51: };
52:
53: // Server向Client发送的消息
54: struct stServerToClient
55: {
56: int iMessageType;
57: union _message
58: {
59: stUserListNode user;
60: }message;
61:
62: };
63:
64: //======================================
65: // 下面的协议用于客户端之间的通信
66: //======================================
67: #define P2PMESSAGE 100 // 发送消息
68: #define P2PMESSAGEACK 101 // 收到消息的应答
69: #define P2PSOMEONEWANTTOCALLYOU 102 // 服务器向客户端发送的消息
70: // 希望此客户端发送一个UDP打洞包
71: #define P2PTRASH 103 // 客户端发送的打洞包,接收端应该忽略此消息
72:
73: // 客户端之间发送消息格式
74: struct stP2PMessage
75: {
76: int iMessageType;
77: int iStringLen; // or IP address
78: unsigned short Port;
79: };
80:
81: using namespace std;
82: typedef list<stUserListNode *> UserList;
83:
以下是公共文件Exception.h的源码:
1:
2: #ifndef __HZH_Exception__
3: #define __HZH_Exception__
4:
5: #define EXCEPTION_MESSAGE_MAXLEN 256
6: #include "string.h"
7:
8: class Exception
9: {
10: private:
11: char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
12: public:
13: Exception(char *msg)
14: {
15: strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
16: }
17:
18: char *GetMessage()
19: {
20: return m_ExceptionMessage;
21: }
22: };
23:
24:
25:
26: #endif
经过测试,以上示例A向S请求向A发消息后,B够接收S转发的A的消息.但是A收不到B的直接消息.且TCP连接不能成功.请看后面的继续穿透示例.