Udp打洞原理和源代碼。


所謂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打洞的代碼介紹完了。可以關注我的公眾號,謝謝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM