Monday, October 29, 2012

cpp net穿透源码-1.UDP穿透

大家好:

       今天我们的源码示例为"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连接不能成功.请看后面的继续穿透示例.

No comments: