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大致分为下面四类 ...
随机推荐
- Pearson Distance
Pearson Distance: where: 1. is the covariance 2. is the standard deviation of 3. is the standard ...
- moment.js学习总结
一. 介绍: moment.js不依赖任何第三方库,支持字符串.Date.时间戳以及数组等格式,可以像PHP的date()函数一样,格式化日期时间,计算相对时间,获取特定时间后的日期时间等等.下面是一 ...
- Bracket Sequences Concatenation Problem括号序列拼接问题(栈+map+思维)
A bracket(括号) sequence is a string containing only characters "(" and ")".A regu ...
- ACM-ICPC 2018 沈阳赛区网络预赛
Supreme Number 1000ms 131072K A prime number (or a prime) is a natural number greater than 111 tha ...
- 第八章 Mysql运算符
算术运算符 符号 表达式形式 作用 + x1+x2 加法 - x1-x2 减法 * x1*x2 乘法 / x1/x2 除法 div x1 div x2 同上 % x1%x2 取余 mod mod(x1 ...
- erlang调优方法
1. 来自Scaling Erlang的方法 内核调优: # Increase the ipv4 port range: sysctl -w net.ipv4.ip_local_port_range= ...
- Qt多线程-QThreadPool线程池与QRunnable
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt多线程-QThreadPool线程池与QRunnable 本文地址:https:/ ...
- PAT L1 - 046 整除光棍
https://pintia.cn/problem-sets/994805046380707840/problems/994805084284633088 这里所谓的“光棍”,并不是指单身汪啦~ 说的 ...
- java内存加载机制
什么是java类加载? 类加载是指将.class类中的二进制数据存放到内存中,会在内存中的推中建立一个java.lang.String的引用对象来存放方法区的数据结构,而类中的数据会放到方法区中 类加 ...
- npm和cnpm(windows)安装步骤
转载:https://blog.csdn.net/wjnf012/article/details/80422313