所謂udp打洞就是指客戶端A通過udp協議向服務器發送數據包,服務器收到后,獲取數據包,並且
可獲取客戶端A地址和端口號。同樣在客戶端B發送給服務器udp數據包后,服務器同樣在收到B發送過來
的數據包后獲取B的地址和端口號,將A和B的地址與端口號分別發送給對方,這樣雙方可以繼續用UDP協議
通信。這么做有什么用呢?因為對於一些應用或者需求,需要兩個客戶端臨時做一些通信,而這種通信
不需要建立tcp就可以完成,所以才去udp打洞。
下面附上測試代碼:
頭文件
// udphole.cpp : 定義控制台應用程序的入口點。 #ifdef WIN32 #include "stdafx.h" #include <winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") typedef SOCKET socketfd; typedef SOCKADDR_IN sockaddr_in; #endif #ifdef __linux__ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <time.h> #include <string.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> #include <iostream> #include <errno.h> #include <arpa/inet.h> #include <pthread.h> typedef int socketfd; #endif #include <list> #include <map> #include <iostream> using namespace std;
服務器端核心代碼。
#include <list> #include <map> #include <iostream> using namespace std; int main(int argc, char* argv[]) { #ifdef WIN32 std::list<SOCKADDR_IN> addrList; WSADATA wsaData = {0}; if (0 != WSAStartup(MAKEWORD(2,2), &wsaData)) { printf ("WSAStartup failed. errno=[%d]\n", WSAGetLastError()); return -1; } #endif #ifdef __linux__ std::list<sockaddr_in> addrList; #endif //addrList 是地址列表,每次存放最新到來的。 socketfd sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (-1 == sockServer) { #ifdef WIN32 printf ("socket server failed. errno=[%d]\n", WSAGetLastError()); #endif #ifdef __linx__ printf("socket server failed. errno=[%d]\n", errno); #endif return -2; } sockaddr_in addrServer = {0}; addrServer.sin_family = AF_INET; addrServer.sin_addr.s_addr = INADDR_ANY;//inet_addr("192.168.1.2"); addrServer.sin_port = htons(10000); if (0 != bind(sockServer, (sockaddr*)&addrServer, sizeof(addrServer))) { #ifdef WIN32 printf ("bind server failed.errno=[%d]\n", WSAGetLastError()); #endif #ifdef __linux__ printf("bind server failed.errno=[%d]\n", errno); #endif return -3; } cout << "okok6"<<endl; while(1) { char pcContent1[10240] = {0}; sockaddr_in addrUser1 = {0}; #ifdef WIN32 int nLen1 = sizeof(addrUser1); #endif #ifdef __linux__ socklen_t nLen1 = sizeof(addrUser1); #endif //服務器接收來自客戶端的消息,並且用addrUser1保存地址和端口 if (-1 == recvfrom(sockServer, pcContent1, sizeof(pcContent1), 0, (sockaddr*)&addrUser1, &nLen1)) { cout << "dfdfda"<<endl; #ifdef WIN32 printf ("recv user 1 failed.errno=[%d]", WSAGetLastError()); #endif #ifdef __linux__ printf ("recv user 1 failed.errno=[%d]", errno); #endif return -4; } else { // printf ("connect user ip=[%s] port=[%d]\n", inet_ntoa(addrUser1.sin_addr), htons(addrUser1.sin_port)); //如果地址列表非空,那么取出列表中的地址,並且與最新到來的客戶端通信 if(addrList.size()) { sockaddr_in peerAddr = addrList.front(); int nLen2 = sizeof(peerAddr); printf ("peer user ip=[%s] port=[%d]\n", inet_ntoa(peerAddr.sin_addr), htons(peerAddr.sin_port)); if (-1 == sendto(sockServer, (char*)&addrUser1, nLen1, 0, (sockaddr*)&peerAddr, nLen2)) { #ifdef WIN32 printf ("send to peer user data failed.\n", WSAGetLastError()); #endif #ifdef __linux__ printf ("send to peer user data failed.\n", errno); #endif return -6; } if (-1 == sendto(sockServer, (char*)&peerAddr, nLen2, 0, (sockaddr*)&addrUser1, nLen1)) { #ifdef WIN32 printf ("send to connect user data failed.\n", WSAGetLastError()); #endif #ifdef __linux__ printf ("send to connect user data failed.\n", errno); #endif return -6; } addrList.pop_front(); } else { //如果列表為空,那么將該地址放入列表中。 addrList.push_back(addrUser1); } } } #ifdef WIN32 Sleep(INFINITE); #endif #ifdef __linux__ //sleep(1000); #endif return 0; }
下面是客戶端發送消息的代碼,比較簡單。
#include "stdafx.h" #include <winsock2.h> #include <stdio.h> #pragma comment(lib, "Ws2_32.lib") int _tmain(int argc, _TCHAR* argv[]) { WSADATA wsaData = {0}; if (0 != WSAStartup(MAKEWORD(2,2), &wsaData)) { printf ("WSAStartup failed. errno=[%d]\n", WSAGetLastError()); return -1; } SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (SOCKET_ERROR == sockClient) { printf ("socket server failed. errno=[%d]\n", WSAGetLastError()); return -2; } char pcContent1[UCHAR_MAX] = {0}; SOCKADDR_IN addrServer = {0}; addrServer.sin_family = AF_INET; addrServer.sin_addr.s_addr = inet_addr("192.168.1.40"); addrServer.sin_port = htons(10000); int nLen1 = sizeof(addrServer); //客戶端發送自己的報文 if (SOCKET_ERROR == sendto(sockClient, pcContent1, 1, 0, (sockaddr*)&addrServer, nLen1)) { printf ("recv user 1 failed.errno=[%d]", WSAGetLastError()); return -3; } SOCKADDR_IN addrUser = {0}; char pcContent2[UCHAR_MAX] = {0}; //阻塞接收來自服務器的消息。 if (SOCKET_ERROR == recvfrom(sockClient, pcContent2, sizeof(pcContent2), 0, (sockaddr*)&addrServer, &nLen1)) { printf ("recv user 1 failed.errno=[%d]", WSAGetLastError()); return -5; } else { memcpy (&addrUser, pcContent2, sizeof(addrUser)); sprintf (pcContent2, "hello, user ip=[%s] port=[%d]\n", inet_ntoa(addrUser.sin_addr), htons(addrUser.sin_port)); //解析服務器消息后發送消息給另一個客戶端。 if (SOCKET_ERROR == sendto(sockClient, pcContent2, strlen(pcContent2), 0, (sockaddr*)&addrUser, nLen1)) { printf ("recv user 1 failed.errno=[%d]", WSAGetLastError()); return -3; } else { //阻塞接收另一個客戶端發送過來的消息 if (SOCKET_ERROR == recvfrom(sockClient, pcContent2, sizeof(pcContent2), 0, (sockaddr*)&addrServer, &nLen1)) { printf ("recv user 1 failed.errno=[%d]", WSAGetLastError()); return -5; } printf ("%s", pcContent2); } } Sleep(INFINITE); return 0; }
效果如下,服務器收到來自客戶端A和客戶端B的報文后打印他們的信息,並且互相轉發消息。
客戶端A和客戶端B分別打印對方的地址和端口號
到此為止,udp打洞的代碼介紹完了。可以關注我的公眾號,謝謝。