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 = {};
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打洞原理和源代码。的更多相关文章
- UDP打洞原理及代码
来源:http://www.fenbi360.net/Content.aspx?id=1021&t=jc UDP"打洞"原理 1. NAT分类 根据Stun协议 ...
- UDP 打洞 原理解释
终于找到了一份满意的UDP打洞原理解释,附上正文,自己整理了一下源码 3.3. UDP hole punching UDP打洞技术 The third technique, and the one o ...
- [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)
NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...
- UDP"打洞"原理
1. NAT分类 根据Stun协议(RFC3489),NAT大致分为下面四类 1) Full Cone 这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口.然后外网的任何发到这个打开的端口 ...
- p2p的UDP打洞原理
>>>>>>>>>>>>>>>>>>>>>>>>> ...
- UDP打洞原理介绍
NAT穿越模块的设计与实现 Internet的快速发展以及IPv4地址数量的不足使得NAT设备得到了大规模的应用,然而这也给越来越多的端到端通信也带来了不少的麻烦.一般来说,NAT设备允许内网内主机 ...
- JedisPool使用原理和源代码
1,JedisPool的使用 <!-- 连接池的配置信息 --><beanid="jedisConfig"class="redis.clients.je ...
- udp打洞( NAT traversal )的方法介绍
http://www.cnblogs.com/whyandinside/archive/2010/12/08/1900492.html http://www.gzsec.com/oldversion/ ...
- UDP ------ UDP打洞
为什么需要UDP打洞 处于两个不同局域网的主机不能直接进行UDP通信 UDP"打洞"原理 1. NAT分类 根据Stun协议(RFC3489),NAT大致分为下面四类 ...
随机推荐
- leetcode个人题解——#34 Find First and Last Position of Element in Sorted Array
思路:先二分查找到一个和target相同的元素,然后再左边二分查找左边界,右边二分查找有边界. class Solution { public: , end = -; int ends; int lS ...
- “Hello World!”团队第三周召开的第二次会议
今天是我们团队“Hello World!”团队第三周召开的第二次会议.博客内容: 一.会议时间 二.会议地点 三.会议成员 四.会议内容 五.todo list 六.会议照片 七.燃尽图 一.会议时间 ...
- CS小分队第二阶段冲刺站立会议(5月29日)
昨日成果:昨天在为主界面设计自主添加应用快捷方式功能,连续遇到困难. 遇到的困难:1.string字符串数组无法在单击事件中使用,提示string无法在eventargs中检索,尝试了各种方式都不行 ...
- myeclipse和ecplise中安装git插件的问题
我的myeclipse10.7和ecplise helis一直安装不了git插件,myeclipse中details说我的myeclipse少了team_features等之类文件,helis的情况大 ...
- request内置对象在JSP
- <问吧>调查问卷心得体会
<问吧>调查问卷心得与体会 在这之前,我们已经组成了一个六个人的小团队---“走廊奔跑队”,我们这次做的这个项目的名称是:问吧.在项目实施之前,我们必做的一步就是需求分析,目的就是充分了解 ...
- PAT 甲级 1083 List Grades
https://pintia.cn/problem-sets/994805342720868352/problems/994805383929905152 Given a list of N stud ...
- mybatis_mysql
SELECT round(avg(c.AVG_DELAY_TIME)) as AVG_DELAY FROM `result_road_saturation_day` a LEFT JOIN info_ ...
- Linux 常用指令【持续更新】
在学校的时候学过一些简单的 Linux 命令,主要是文件的创建拷贝解压等操作,最近在电脑上安装了一个CentOS6.8版本的基本版,纯命令行操作. ../ 代表上一级目录 ./ 代表本级目录 / 代表 ...
- InnoDB 5.6 新特性之一:FullTEXT Indexes[1.简单介绍]
先来看一条SQL语句: SELECT * FROM aa where acol like '%like_normal%'; 当我们使用Innodb时,无论如何对这条语句进行优化,都是无意义的:有的人会 ...