模擬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; 原文出處:http://hi.baidu.com/%CF%D0%B5%AD%CA%B0%D2%E4/blog/item/c817b378513cf0fb2e73b3d5.html
相關文章
相關標籤/搜索