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打洞的代码介绍完了。可以关注我的公众号,谢谢。

时间: 2024-10-19 15:36:46

Udp打洞原理和源代码。的相关文章

UDP打洞原理

源:UDP打洞原理

UDP 打洞 原理解释

终于找到了一份满意的UDP打洞原理解释,附上正文,自己整理了一下源码 3.3. UDP hole punching UDP打洞技术 The third technique, and the one of primary interest in this document, is widely known as "UDP Hole Punching." UDP hole punching relies on the properties of common firewalls and c

UDP打洞原理介绍

 NAT穿越模块的设计与实现 Internet的快速发展以及IPv4地址数量的不足使得NAT设备得到了大规模的应用,然而这也给越来越多的端到端通信也带来了不少的麻烦.一般来说,NAT设备允许内网内主机主动向公网内主机发送数据,但却禁止内网外的主机主动向内网内的主机传递数据.由于很多的会话双方处于不同的NAT设备后,它们通信一般通过公网服务器中转,而要建立P2P通信,则必须解决NAT穿越问题才能建立通信. NAT 是介于内网和公网之间的设备,公网中的IP 地址是全球唯一的,而在内网中的IP 地址可

JedisPool使用原理和源代码

1,JedisPool的使用 <!-- 连接池的配置信息 --><beanid="jedisConfig"class="redis.clients.jedis.JedisPoolConfig"><!-- 说明一个pool可以有多少个Jedis实例 --><propertyname="maxActive"value="10" /><!-- 最大Idle--><pr

P2P UPD打洞原理

转自:http://blog.pfan.cn/fengfei/18828.html 首先先介绍一些基本概念:            NAT(Network Address             Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用.NAT分为两大类,基本的NAT和NAPT(Network             Address/Port Translator).            最开始NAT是运行在路由器

Linux下二进制包和源代码包的区分

主要提供三种格式的mysql包:rpm格式.二进制格式.源码格式:(tar打包,gz压缩) rpm格式: libjpeg-devel-6b-33.x86_64.rpm       #rpm格式很好区分, 二进制包: mysql-3.23.58-pc-linux-i686.tar.gz   #二进制格式的包名字很长,有版本号.适应平台.适应的硬件类型等,格式:mysql-<版本>-<OS>-tar.gz 源码包:    php-5.2.14.tar.gz              

UDP打洞、P2P组网方式研究

catalogue 1. NAT概念 2. P2P概念 3. UDP打洞 4. P2P DEMO 5. ZeroNet P2P 1. NAT概念 在STUN协议中,根据内部终端的地址(LocalIP:LocalPort)到NAT出口的公网地址(PublicIP:PublicPort)的影射方式,把NAT分为四种类型(rfc3489: http://www.ietf.org/rfc/rfc3489.txt) 1. Full Cone: 这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口

NAT穿透(UDP打洞)

1.NAT(Network Address Translator)介绍 NAT有两大类,基本NAT和NAPT. 1.1.基本NAT 静态NAT:一个公网IP对应一个内部IP,一对一转换 动态NAT:N个公网IP对应M个内部IP,不固定的一对一转换关系  1.2.NAPT(Network Address/Port Translator) 现在基本使用这种,又分为对称和锥型NAT. 锥型NAT,有完全锥型.受限制锥型.端口受限制锥型三种: a)Full Cone NAT(完全圆锥型):从同一私网地址

【原创】IP摄像头技术纵览(七)---P2P技术—UDP打洞实现内网NAT穿透

[原创]IP摄像头技术纵览(七)-P2P技术-UDP打洞实现内网NAT穿透 本文属于<IP摄像头技术纵览>系列文章之一: Author: chad Mail: [email protected] 本文可以自由转载,但转载请务必注明出处以及本声明信息. NAT技术的实际需求在10几年前就已经出现,为了解决这个问题,10几年来全世界的牛人早已经研究好了完整的解决方案,网上有大量优秀的解决方案文章,笔者自知无法超越,所以秉承拿来主义,将优秀文章根据个人实验及理解整理汇录于此,用于解释IP摄像头整个技