P2P ?UDPIKNAT的原理与实现Q附源代码)
原创Qshootingstars
参考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生Q源代码Q。呵呵,在这里我q自己实现的一个源代码来说明UDPI越NAT的原理?/p>
首先先介l一些基本概念:
NAT(Network Address Translators)Q网l地址转换Q网l地址转换是在IP地址日益~Z的情况下产生的,它的主要目的是Z能够地址重用。NAT分ؓ两大c,基本的NAT和NAPT(Network Address/Port Translator)?br /> 最开始NAT是运行在路由器上的一个功能模块?br />
最先提出的是基本的NATQ它的生基于如下事实:一个私有网l(域)中的节点中只有很的节点需要与外网q接Q呵呵,q是在上世纪90q代中期提出的)。那么这个子|中其实只有数的节炚w要全球唯一的IP地址Q其他的节点的IP地址应该是可以重用的?br /> 因此Q基本的NAT实现的功能很单,在子|内使用一个保留的IP子网D,q些IP对外是不可见的。子|内只有数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节炚w要访问外部网l,那么基本NATp责将q个节点的子|内IP转化Z个全球唯一的IP然后发送出厅R?基本的NAT会改变IP包中的原IP地址Q但是不会改变IP包中的端?
关于基本的NAT可以参看RFC 1631
另外一UNAT叫做NAPTQ从名称上我们也可以看得出,NAPT不但会改变经q这个NAT讑֤的IP数据报的IP地址Q还会改变IP数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵Q我没有见到q)QNAPT才是我们真正讨论的主角。看下图Q?br /> Server S1
18.181.0.31:1235
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 155.99.25.11:62000 v |
|
NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 10.0.0.1:1234 v |
|
Client A
10.0.0.1:1234
有一个私有网l?0.*.*.*QClient A是其中的一台计机Q这个网l的|关Q一个NAT讑֤Q的外网IP?55.99.25.11(应该q有一个内|的IP地址Q比?0.0.0.10)。如果Client A中的某个q程Q这个进E创Z一个UDP Socket,q个Socketl定1234端口Q想讉K外网L18.181.0.31?235端口Q那么当数据包通过NAT时会发生什么事情呢Q?br /> 首先NAT会改变这个数据包的原IP地址Q改?55.99.25.11。接着NAT会ؓq个传输创徏一个SessionQSession是一个抽象的概念Q如果是TCPQ也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的W一个UDP开始,l束呢,呵呵Q也许是几分钟,也许是几时Q这要看具体的实CQƈ且给q个Session分配一个端口,比如62000Q然后改变这个数据包的源端口?2000。所以本来是Q?0.0.0.1:1234->18.181.0.31:1235Q的数据包到了互联网上变ZQ?55.99.25.11:62000->18.181.0.31:1235Q?br /> 一旦NAT创徏了一个Session后,NAT会记?2000端口对应的是10.0.0.1?234端口Q以后从18.181.0.31发送到62000端口的数据会被NAT自动的{发到10.0.0.1上。(注意Q这里是?8.181.0.31发送到62000端口的数据会被{发,其他的IP发送到q个端口的数据将被NAT抛弃Q这样Client A׃Server S1建立以了一个连接?/p>
呵呵Q上面的基础知识可能很多人都知道了,那么下面是关键的部分了?br /> 看看下面的情况:
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子Q如果Client A的原来那个Socket(l定?234端口的那个UDP Socket)又接着向另外一个Server S2发送了一个UDP包,那么q个UDP包在通过NAT时会怎么样呢Q?br /> q时可能会有两种情况发生Q一U是NAT再次创徏一个SessionQƈ且再ơؓq个Session分配一个端口号Q比如:62001Q。另外一U是NAT再次创徏一个SessionQ但是不会新分配一个端口号Q而是用原来分配的端口?2000。前一UNAT叫做Symmetric NATQ后一U叫做Cone NAT。我们期望我们的NAT是第二种Q呵呵,如果你的NAT刚好是第一U,那么很可能会有很多P2P软gq。(可以庆幸的是Q现在绝大多数的NAT属于后者,即Cone NATQ?br />
好了Q我们看刎ͼ通过NAT,子网内的计算机向外连l是很容易的QNAT相当于透明的,子网内的和外|的计算Z用知道NAT的情况)?br /> 但是如果外部的计机惌问子|内的计机比较困难了Q而这正是P2P所需要的Q?br /> 那么我们如果想从外部发送一个数据报l内|的计算机有什么办法呢Q首先,我们必须在内|的NAT上打上一个“洞”(也就是前面我们说的在NAT上徏立一个SessionQ,q个z不能由外部来打Q只能由内网内的L来打。而且q个z是有方向的Q比如从内部某台LQ比如:192.168.0.10Q向外部的某个IP(比如Q?19.237.60.1)发送一个UDP包,那么在q个内网的NAT讑֤上打了一个方向ؓ219.237.60.1的“洞”,Q这是UCؓUDP Hole Punching的技术)以后219.237.60.1可以通过q个z与内网?92.168.0.10联系了。(但是其他的IP不能利用q个z)?/p>
呵呵Q现在该轮到我们的正题P2P了。有了上面的理论Q实C个内|的L通讯差最后一步了Q那是鸡生蛋还是蛋生鸡的问题了Q两辚w无法d发出q接hQ谁也不知道谁的公网地址Q那我们如何来打q个z呢Q我们需要一个中间h来联p这两个内网L?br /> 现在我们来看看一个P2P软g的流E,以下图ؓ例:
Server S Q?19.237.60.1Q?br /> |
|
+----------------------+----------------------+
| |
NAT A (外网IP:202.187.45.3) NAT B (外网IP:187.34.1.56)
| (内网IP:192.168.0.1) | (内网IP:192.168.0.1)
| |
Client A (192.168.0.20:4000) Client B (192.168.0.10:40000)
首先QClient Ad服务器,NAT Aơ的Session分配了一个端?0000Q那么Server S收到的Client A的地址?02.187.45.3:60000Q这是Client A的外|地址了。同PClient BdServer SQNAT Bl此ơSession分配的端口是40000Q那么Server S收到的B的地址?87.34.1.56:40000?br /> 此时QClient A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client BQ那么他可以从Server S那儿获得B的公|地址187.34.1.56:40000Q是不是Client A向这个地址发送信息Client Bp收到了呢Q答案是不行Q因为如果这样发送信息,NAT B会将q个信息丢弃Q因L信息是不误来的Qؓ了安全,大多数NAT都会执行丢弃动作Q。现在我们需要的是在NAT B上打一个方向ؓ202.187.45.3Q即Client A的外|地址Q的z,那么Client A发送到187.34.1.56:40000的信?Client Bp收到了。这个打z命令由谁来发呢Q呵呵,当然是Server S?br /> ȝ一下这个过E:如果Client A惛_Client B发送信息,那么Client A发送命令给Server SQ请求Server S命oClient B向Client A方向打洞。呵呵,是不是很l口Q不q没关系Q想一惛_很清楚了Q何况还有源代码呢(侯老师说过Q在源代码面前没有秘?8Q)Q然后Client A可以通过Client B的外|地址与Client B通信了?br />
注意Q以上过E只适合于Cone NAT的情况,如果是Symmetric NATQ那么当Client B向Client A打洞的端口已l重新分配了QClient B无法知道这个端口(如果Symmetric NAT的端口是序分配的,那么我们或许可以猜测q个端口P可是׃可能Dp|的因素太多,我们不推荐这U猜端口的ҎQ?br />
下面是一个模拟P2P聊天的过E的源代码,q程很简单,P2PServerq行在一个拥有公|IP的计机上,P2PClientq行在两个不同的NAT后(注意Q如果两个客Lq行在一个NAT后,本程序很可能不能q行正常Q这取决于你的NAT是否支持loopback translationQ详?a >http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txtQ当Ӟ此问题可以通过双方先尝试连接对方的内网IP来解冻I但是q个代码只是Z验证原理Qƈ没有处理q些问题Q,后登录的计算机可以获得先d计算机的用户名,后登录的计算机通过send username message的格式来发送消息。如果发送成功,说明你已取得了直接与Ҏq接的成功?br /> E序现在支持三个命oQsend , getu , exit
send格式Qsend username message
功能Q发送信息给username
getu格式Qgetu
功能Q获得当前服务器用户列表
exit格式Qexit
功能Q注销与服务器的连接(服务器不会自动监客h否吊U)
代码很短Q相信很Ҏ懂,如果有什么问题,可以l我发邮?a href="mailto:zhouhuis22@sina.com">zhouhuis22@sina.com 或者在CSDN上发送短消息。同ӞƢ迎转发此文Q但希望保留作者版?-Q?br />
最后感谢CSDN|友 PiggyXP ?Seilfer的测试帮?/p>
P2PServer.c
/* P2P E序服务?br /> *
* 文g名:P2PServer.c
*
* 日期Q?004-5-21
*
* 作者:shootingstars(zhouhuis22@sina.com)
*
*/
#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));
// 开始主循环.
// d@环负责下面几件事?
// 一:d客户端登陆和d消息,记录客户列表
// ?转发客户p2ph
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:
{
// 这个用L信息记录到用户列表中
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);
// 发送已l登陆的客户信息
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 = NULL;
for(UserList::iterator UserIterator=ClientList.begin();
UserIterator!=ClientList.end();
++UserIterator)
{
if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
{
removeiterator = UserIterator;
break;
}
}
if(removeiterator != NULL)
ClientList.remove(*removeiterator);
break;
}
case P2PTRANS:
{
// 某个客户希望服务端向另外一个客户发送一个打z消?br /> 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;
}
/* P2P E序客户?br /> *
* 文g名:P2PClient.c
*
* 日期Q?004-5-21
*
* 作者:shootingstars(zhouhuis22@sina.com)
*
*/
#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;
char UserName[10];
char ServerIP[20];
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(SOCKET sock)
{
sockaddr_in sin;
sin.sin_addr.S_un.S_addr = INADDR_ANY;
sin.sin_family = AF_INET;
sin.sin_port = 0;
if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
throw Exception("bind error");
}
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");
}
// d到服务端后,接收服务端发来的已经d的用L信息
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;
}
/* q是主要的函敎ͼ发送一个消息给某个用户(C)
*程Q直接向某个用户的外|IP发送消息,如果此前没有联系q?br /> * 那么此消息将无法发送,发送端{待时?br /> * 时后,发送端发送一个请求信息到服务端,
* 要求服务端发送给客户C一个请求,hCl本机发送打z消?br /> * 以上程重复MAXRETRY?br /> */
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));
// {待接收U程此标记修改
for(int j=0;j<10;j++)
{
if(RecvedACK)
return true;
else
Sleep(300);
}
// 没有接收到目标主机的回应Q认为目标主机的端口映射没有
// 打开Q那么发送请求信息给服务器,要服务器告诉目标L
// 打开映射端口QUDP打洞Q?br /> 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);// {待Ҏ先发送信息?br /> }
return false;
}
// 解析命oQ暂时只有exit和send命o
// 新增getu命oQ获取当前服务器的所有用?br />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));
}
}
// 接受消息U程
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的消?br /> 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:
{
// 接收到打z命令,向指定的IP地址打洞
printf("Recv p2someonewanttocallyou data\n");
sockaddr_in remote;
remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
remote.sin_family = AF_INET;
remote.sin_port = htons(recvbuf.Port);
// 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;
break;
}
case P2PTRASH:
{
// Ҏ发送的打洞消息Q忽略掉?br /> //do nothing ...
printf("Recv p2ptrash data\n");
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(PrimaryUDP);
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;
}
/* 异常c?br /> *
* 文g名:Exception.h
*
* 日期Q?004.5.5
*
* 作者:shootingstars(zhouhuis22@sina.com)
*/
#ifndef __HZH_Exception__
#define __HZH_Exception__
#define EXCEPTION_MESSAGE_MAXLEN 256
#include "string.h"
class Exception
{
private:
char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
public:
Exception(char *msg)
{
strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
}
char *GetMessage()
{
return m_ExceptionMessage;
}
};
#endif
/* P2P E序传输协议
*
* 日期Q?004-5-21
*
* 作者:shootingstars(zhouhuis22@sina.com)
*
*/
#pragma once
#include <list>
// 定义iMessageType的?br />#define LOGIN 1
#define LOGOUT 2
#define P2PTRANS 3
#define GETALLUSER 4
// 服务器端?br />#define SERVER_PORT 2280
// Clientd时向服务器发送的消息
struct stLoginMessage
{
char userName[10];
char password[10];
};
// Client注销时发送的消息
struct stLogoutMessage
{
char userName[10];
};
// Client向服务器h另外一个Client(userName)向自己方向发送UDP打洞消息
struct stP2PTranslate
{
char userName[10];
};
// Client向服务器发送的消息格式
struct stMessage
{
int iMessageType;
union _message
{
stLoginMessage loginmember;
stLogoutMessage logoutmember;
stP2PTranslate translatemessage;
}message;
};
// 客户节点信息
struct stUserListNode
{
char userName[10];
unsigned int ip;
unsigned short port;
};
// Server向Client发送的消息
struct stServerToClient
{
int iMessageType;
union _message
{
stUserListNode user;
}message;
};
//======================================
// 下面的协议用于客L之间的通信
//======================================
#define P2PMESSAGE 100 // 发送消?br />#define P2PMESSAGEACK 101 // 收到消息的应{?br />#define P2PSOMEONEWANTTOCALLYOU 102 // 服务器向客户端发送的消息
// 希望此客L发送一个UDP打洞?br />#define P2PTRASH 103 // 客户端发送的打洞包,接收端应该忽略此消息
// 客户端之间发送消息格?br />struct stP2PMessage
{
int iMessageType;
int iStringLen; // or IP address
unsigned short Port;
};
using namespace std;
typedef list<stUserListNode *> UserList;