p2p 打洞

下面是一个模拟P2P聊天的过程的源代码,过程很简单,P2PServer运行在一个拥有公网IP的计算机上,P2PClient运行在两个不同的NAT 后(注意,如果两个客户端运行在一个NAT后,本程序很可能不能运行正常,这取决于你的NAT是否支持loopback translation,详见http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p- 01.txt,当然,此问题可以通过双方先尝试连接对方的内网IP来解决,但是这个代码只是为了验证原理,并没有处理这些问题),后登录的计算机可以获得 先登录计算机的用户名,后登录的计算机通过send username message的格式来发送消息。如果发送成功,说明你已取得了直接与对方连接的成功。 
程序现在支持三个命令:send , getu , exit 

send格式:send username message 
功能:发送信息给username 

getu格式:getu 
功能:获得当前服务器用户列表 

exit格式:exit 
功能:注销与服务器的连接(服务器不会自动监测客户是否吊线) 

代码很短,相信很容易懂,如果有什么问题,可以给我发邮件zhouhuis22@sina.com 或者在CSDN上发送短消息。同时,欢迎转发此文,但希望保留作者版权8-)。 

最后感谢CSDN网友 PiggyXP 和 Seilfer的测试帮助 

P2PServer.c 

/* P2P 程序服务端 

* 文件名:P2PServer.c 

* 日期:2004-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.%dnn", 
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 : %sn", 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 : %sn", 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: 

// 某个客户希望服务端向另外一个客户发送一个打洞消息 
printf("%s wants to p2p %sn",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 %dn",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 程序客户端 

* 文件名:P2PClient.c 

* 日期:2004-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.%dnn", 
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 errorn"); 


// 登录到服务端后,接收服务端发来的已经登录的用户的信息 
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 Messagen" 
<<" exitn" 
<<" getun" 
<<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]=''; 

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]=''; 
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 errorn"); 
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] = ''; 
if(iread1<=0) 
throw Exception("Recv Message Errorn"); 
else 

printf("Recv a Message:%sn",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 datan"); 
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: 

// 对方发送的打洞消息,忽略掉。 
//do nothing ... 
printf("Recv p2ptrash datan"); 
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 errorn"); 


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; 



/* 异常类 

* 文件名:Exception.h 

* 日期:2004.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 程序传输协议 

* 日期:2004-5-21 

* 作者:shootingstars(zhouhuis22@sina.com) 

*/ 

#pragma once 
#include <list> 

// 定义iMessageType的值 
#define LOGIN 1 
#define LOGOUT 2 
#define P2PTRANS 3 
#define GETALLUSER 4 

// 服务器端口 
#define SERVER_PORT 2280 

// Client登录时向服务器发送的消息 
struct stLoginMessage 

char userName[10]; 
char password[10]; 
}; 

// Client注销时发送的消息 
struct stLogoutMessage 

char userName[10]; 
}; 

// Client向服务器请求另外一个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; 

}; 

//====================================== 
// 下面的协议用于客户端之间的通信 
//====================================== 
#define P2PMESSAGE 100 // 发送消息 
#define P2PMESSAGEACK 101 // 收到消息的应答 
#define P2PSOMEONEWANTTOCALLYOU 102 // 服务器向客户端发送的消息 
// 希望此客户端发送一个UDP打洞包 
#define P2PTRASH 103 // 客户端发送的打洞包,接收端应该忽略此消息 

// 客户端之间发送消息格式 
struct stP2PMessage 

int iMessageType; 
int iStringLen; // or IP address 
unsigned short Port; 
}; 

using namespace std; 
typedef list<stUserListNode *> UserList;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值