所谓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 = {};
if ( != WSAStartup(MAKEWORD(,), &wsaData))
{
printf ("WSAStartup failed. errno=[%d]\n", WSAGetLastError());
return -;
}
#endif #ifdef __linux__
std::list<sockaddr_in> addrList; #endif //addrList 是地址列表,每次存放最新到来的。
socketfd sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (- == sockServer)
{ #ifdef WIN32
printf ("socket server failed. errno=[%d]\n", WSAGetLastError());
#endif #ifdef __linx__
printf("socket server failed. errno=[%d]\n", errno);
#endif return -;
} sockaddr_in addrServer = {}; addrServer.sin_family = AF_INET;
addrServer.sin_addr.s_addr = INADDR_ANY;//inet_addr("192.168.1.2");
addrServer.sin_port = htons();
if ( != 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 -;
} cout << "okok6"<<endl;
while()
{
char pcContent1[] = {};
sockaddr_in addrUser1 = {};
#ifdef WIN32
int nLen1 = sizeof(addrUser1);
#endif #ifdef __linux__
socklen_t nLen1 = sizeof(addrUser1);
#endif
//服务器接收来自客户端的消息,并且用addrUser1保存地址和端口
if (- == recvfrom(sockServer, pcContent1, sizeof(pcContent1), , (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 -;
}
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 (- == sendto(sockServer, (char*)&addrUser1, nLen1, , (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 -;
} if (- == sendto(sockServer, (char*)&peerAddr, nLen2, , (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 -;
} addrList.pop_front();
}
else
{
//如果列表为空,那么将该地址放入列表中。
addrList.push_back(addrUser1);
}
}
} #ifdef WIN32
Sleep(INFINITE);
#endif #ifdef __linux__
//sleep(1000);
#endif
return ;
}

下面是客户端发送消息的代码,比较简单。

#include "stdafx.h"

#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib") int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData = {};
if ( != WSAStartup(MAKEWORD(,), &wsaData))
{
printf ("WSAStartup failed. errno=[%d]\n", WSAGetLastError());
return -;
}
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (SOCKET_ERROR == sockClient)
{
printf ("socket server failed. errno=[%d]\n", WSAGetLastError());
return -;
}
char pcContent1[UCHAR_MAX] = {};
SOCKADDR_IN addrServer = {};
addrServer.sin_family = AF_INET;
addrServer.sin_addr.s_addr = inet_addr("192.168.1.40");
addrServer.sin_port = htons();
int nLen1 = sizeof(addrServer);
//客户端发送自己的报文
if (SOCKET_ERROR == sendto(sockClient, pcContent1, , , (sockaddr*)&addrServer, nLen1))
{
printf ("recv user 1 failed.errno=[%d]", WSAGetLastError());
return -;
}
SOCKADDR_IN addrUser = {};
char pcContent2[UCHAR_MAX] = {};
//阻塞接收来自服务器的消息。
if (SOCKET_ERROR == recvfrom(sockClient, pcContent2, sizeof(pcContent2), , (sockaddr*)&addrServer, &nLen1))
{
printf ("recv user 1 failed.errno=[%d]", WSAGetLastError());
return -;
}
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), , (sockaddr*)&addrUser, nLen1))
{
printf ("recv user 1 failed.errno=[%d]", WSAGetLastError());
return -;
}
else
{
//阻塞接收另一个客户端发送过来的消息
if (SOCKET_ERROR == recvfrom(sockClient, pcContent2, sizeof(pcContent2), , (sockaddr*)&addrServer, &nLen1))
{
printf ("recv user 1 failed.errno=[%d]", WSAGetLastError());
return -;
}
printf ("%s", pcContent2);
}
}
Sleep(INFINITE);
return ; }

效果如下,服务器收到来自客户端A和客户端B的报文后打印他们的信息,并且互相转发消息。

客户端A和客户端B分别打印对方的地址和端口号

到此为止,udp打洞的代码介绍完了。可以关注我的公众号,谢谢。

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

  1. UDP打洞原理及代码

    来源:http://www.fenbi360.net/Content.aspx?id=1021&t=jc UDP"打洞"原理 1.       NAT分类 根据Stun协议 ...

  2. UDP 打洞 原理解释

    终于找到了一份满意的UDP打洞原理解释,附上正文,自己整理了一下源码 3.3. UDP hole punching UDP打洞技术 The third technique, and the one o ...

  3. [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)

    NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...

  4. UDP"打洞"原理

    1. NAT分类 根据Stun协议(RFC3489),NAT大致分为下面四类 1) Full Cone 这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口.然后外网的任何发到这个打开的端口 ...

  5. p2p的UDP打洞原理

    >>>>>>>>>>>>>>>>>>>>>>>>> ...

  6. UDP打洞原理介绍

     NAT穿越模块的设计与实现 Internet的快速发展以及IPv4地址数量的不足使得NAT设备得到了大规模的应用,然而这也给越来越多的端到端通信也带来了不少的麻烦.一般来说,NAT设备允许内网内主机 ...

  7. JedisPool使用原理和源代码

    1,JedisPool的使用 <!-- 连接池的配置信息 --><beanid="jedisConfig"class="redis.clients.je ...

  8. udp打洞( NAT traversal )的方法介绍

    http://www.cnblogs.com/whyandinside/archive/2010/12/08/1900492.html http://www.gzsec.com/oldversion/ ...

  9. UDP ------ UDP打洞

    为什么需要UDP打洞 处于两个不同局域网的主机不能直接进行UDP通信 UDP"打洞"原理 1.       NAT分类 根据Stun协议(RFC3489),NAT大致分为下面四类 ...

随机推荐

  1. Pearson Distance

    Pearson Distance: where: 1.  is the covariance 2.  is the standard deviation of 3.  is the standard ...

  2. moment.js学习总结

    一. 介绍: moment.js不依赖任何第三方库,支持字符串.Date.时间戳以及数组等格式,可以像PHP的date()函数一样,格式化日期时间,计算相对时间,获取特定时间后的日期时间等等.下面是一 ...

  3. Bracket Sequences Concatenation Problem括号序列拼接问题(栈+map+思维)

    A bracket(括号) sequence is a string containing only characters "(" and ")".A regu ...

  4. ACM-ICPC 2018 沈阳赛区网络预赛

    Supreme Number 1000ms 131072K   A prime number (or a prime) is a natural number greater than 111 tha ...

  5. 第八章 Mysql运算符

    算术运算符 符号 表达式形式 作用 + x1+x2 加法 - x1-x2 减法 * x1*x2 乘法 / x1/x2 除法 div x1 div x2 同上 % x1%x2 取余 mod mod(x1 ...

  6. erlang调优方法

    1. 来自Scaling Erlang的方法 内核调优: # Increase the ipv4 port range: sysctl -w net.ipv4.ip_local_port_range= ...

  7. Qt多线程-QThreadPool线程池与QRunnable

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt多线程-QThreadPool线程池与QRunnable     本文地址:https:/ ...

  8. PAT L1 - 046 整除光棍

    https://pintia.cn/problem-sets/994805046380707840/problems/994805084284633088 这里所谓的“光棍”,并不是指单身汪啦~ 说的 ...

  9. java内存加载机制

    什么是java类加载? 类加载是指将.class类中的二进制数据存放到内存中,会在内存中的推中建立一个java.lang.String的引用对象来存放方法区的数据结构,而类中的数据会放到方法区中 类加 ...

  10. npm和cnpm(windows)安装步骤

    转载:https://blog.csdn.net/wjnf012/article/details/80422313