UDP打洞失败不理解

c_flybird 2017-07-23 02:52:25
最近需要做一个非局域网通信的东西。需要用到打洞技术。
于是我按照网上说的做了一个非局域网打洞的程序。
但是,不管怎么样。A和B得到对方的NAT地址后始终无法收到对方的消息。
有没有高手能来看一下,到底是哪里的问题。
如果是代码的问题,我已经找了很久了,一直不知道毛病在哪里。
如果是方法的问题,能不能告诉我一下,让我别再浪费时间了。麻烦高手看一看

打洞原理如下


下面是代码。
服务器:

#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")

#define BUF_SIZE 100

int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

//创建套接字
SOCKET servSock = socket(AF_INET, SOCK_DGRAM, 0);
//绑定套接字
sockaddr_in sockAddr;//本机服务器地址
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取自己的ip地址
sockAddr.sin_port = htons(10086); //监听服务端口
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//绑定端口

//接收客户端请求
sockaddr_in clntAddr1; //客户端1地址信息
int nSize = sizeof(SOCKADDR);
char RecvBuffer[BUF_SIZE]; //缓冲区
int strLen = recvfrom(servSock, RecvBuffer, BUF_SIZE, 0, (struct sockaddr*)&clntAddr1, &nSize);

sockaddr_in clntAddr2; //客户端2地址信息
strLen = recvfrom(servSock, RecvBuffer, BUF_SIZE, 0, (struct sockaddr*)&clntAddr2, &nSize);

//把两边的地址准备好发送
char bufSend1[BUF_SIZE];
memset(bufSend1, '\0', sizeof(bufSend1));
char* ip1 = inet_ntoa(clntAddr2.sin_addr);
char port1[10];
_itoa_s(ntohs(clntAddr2.sin_port), port1, 10);
for (int i = 0; i<strlen(ip1); i++) {
bufSend1[i] = ip1[i];
}
bufSend1[strlen(ip1)] = '^';
for (int i = 0; i<strlen(port1); i++) {
bufSend1[strlen(ip1) + 1 + i] = port1[i];
}

char bufSend2[BUF_SIZE];
memset(bufSend2, '\0', sizeof(bufSend2));
char* ip2 = inet_ntoa(clntAddr1.sin_addr);
char port2[10];
_itoa_s(ntohs(clntAddr1.sin_port), port2, 10);
for (int i = 0; i<strlen(ip2); i++) {
bufSend2[i] = ip2[i];
}
bufSend2[strlen(ip2)] = '^';
for (int i = 0; i<strlen(port2); i++) {
bufSend2[strlen(ip2) + 1 + i] = port2[i];
}

//发送 交换地址
strLen = sendto(servSock, bufSend1, sizeof(bufSend1), 0, (struct sockaddr*)&clntAddr1, sizeof(clntAddr1));
strLen = sendto(servSock, bufSend2, sizeof(bufSend2), 0, (struct sockaddr*)&clntAddr2, sizeof(clntAddr2));
printf("发送目标1:ip:%s 端口号:%d\n", inet_ntoa(clntAddr1.sin_addr),ntohs(clntAddr1.sin_port));
printf("发送内容1:%s\n", bufSend1);
printf("发送目标2:ip:%s 端口号:%d\n", inet_ntoa(clntAddr2.sin_addr), ntohs(clntAddr2.sin_port));
printf("发送内容2:%s\n", bufSend2);
printf("交换工作已完成\n");

//停在这里等c1通知s来通知c2
sockaddr_in clntAddr3;
strLen = recvfrom(servSock, RecvBuffer, BUF_SIZE, 0, (struct sockaddr*)&clntAddr3, &nSize);
printf("已收到通知\n");
printf("C1现在的地址是:%s ;%d\n", inet_ntoa(clntAddr3.sin_addr), ntohs(clntAddr3.sin_port));

//区分clntAddr1和clntAddr2,发送给c2通知
if (strcmp(inet_ntoa(clntAddr3.sin_addr), inet_ntoa(clntAddr1.sin_addr)) == 0&& ntohs(clntAddr3.sin_port)== ntohs(clntAddr1.sin_port))
{
strLen = sendto(servSock, bufSend2, sizeof(bufSend2), 0, (struct sockaddr*)&clntAddr2, sizeof(clntAddr2));
printf("发送目标3:ip:%s 端口号:%d\n", inet_ntoa(clntAddr2.sin_addr), ntohs(clntAddr2.sin_port));
}
else
{
strLen = sendto(servSock, bufSend1, sizeof(bufSend1), 0, (struct sockaddr*)&clntAddr1, sizeof(clntAddr1));
printf("发送目标4:ip:%s 端口号:%d\n", inet_ntoa(clntAddr1.sin_addr), ntohs(clntAddr1.sin_port));
}
system("pause()");
closesocket(servSock);
WSACleanup();
return 0;
}


客户端A:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.*;
import java.sql.Time;

/**
* Created by lgtczh on 2017/7/21.
*/
public class TCPclientA {
public static void main(String[] args) throws IOException {
try {
// 向server发起请求
SocketAddress target1 = new InetSocketAddress("114.55.101.163", 10086);
DatagramSocket client = new DatagramSocket(10086);
String message = "";
byte[] sendbuf = message.getBytes();
DatagramPacket pack = new DatagramPacket(sendbuf, sendbuf.length, target1);
client.send(pack);

// 接收服务器返回的C2地址
byte[] buf = new byte[1024];
DatagramPacket recvPacket = new DatagramPacket(buf, buf.length);
client.receive(recvPacket);//会停在这里
String receiveMessage = new String(recvPacket.getData(), 0, recvPacket.getLength());

//解析B的IP和端口号
String[] params = receiveMessage.split("\\^");
String host = params[0].substring(0);
String port = params[1].substring(0);
System.out.println(host+" "+port);

SocketAddress target2 = new InetSocketAddress(host, Integer.parseInt(port.trim()));
DatagramPacket pack2 = new DatagramPacket(sendbuf, sendbuf.length, target2);

//向C2打洞
client.send(pack2);
System.out.println("已打洞");

//通知服务器通知C2已打洞
client.send(pack);

//等待接收C2发送过来
buf=new byte[1024];
recvPacket = new DatagramPacket(buf, buf.length);
client.receive(recvPacket);//会停在这里
client.send(pack2);//向C2发送过去,若发到则已打通
receiveMessage = new String(recvPacket.getData(), 0, recvPacket.getLength());
System.out.println("已成功打通 "+receiveMessage);
} catch (Exception e) {
e.printStackTrace();
}
}
}



客户端B:

#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 100

int main() {
//初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字
SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0);

//服务器地址信息
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充
servAddr.sin_family = PF_INET;
servAddr.sin_addr.s_addr = inet_addr("114.55.101.163");
servAddr.sin_port = htons(10086);

//发送给服务器自己地址
sockaddr fromAddr;
int addrLen = sizeof(fromAddr);
char SendBuffer[BUF_SIZE] = { 0 };
sendto(sock, SendBuffer, strlen(SendBuffer), 0, (struct sockaddr*)&servAddr, sizeof(servAddr));

//接收服务器传来的对面的地址
char RecvBuffer[BUF_SIZE] = { 0 };
int strLen = recvfrom(sock, RecvBuffer, BUF_SIZE, 0, &fromAddr, &addrLen);

//解析对面的地址
char ip[20];
char port[10];
int i;
for (i = 0; i<strlen(RecvBuffer); i++)
if (RecvBuffer[i] != '^')
ip[i] = RecvBuffer[i];
else break;
ip[i] = '\0';
int j;
for (j = i + 1; j<strlen(RecvBuffer); j++)
port[j - i - 1] = RecvBuffer[j];
port[j - i - 1] = '\0';
printf("收到的地址为: %s ,%s\n", ip, port);

//收到通知,给C1发送消息
sockaddr_in opposite;
opposite.sin_family = PF_INET;
opposite.sin_addr.s_addr = inet_addr(ip);
opposite.sin_port = htons(atoi(port));
memset(SendBuffer, 0, BUF_SIZE);
strcpy(SendBuffer,"i am c2");
strLen = sendto(sock, SendBuffer, strlen(SendBuffer), 0, (struct sockaddr*)&opposite, sizeof(opposite));
printf("opposite:%s ;%d\n", inet_ntoa(opposite.sin_addr), ntohs(opposite.sin_port));
printf("%d\n", strLen);

//停下等待服务器通知
strLen = recvfrom(sock, RecvBuffer, BUF_SIZE, 0, &fromAddr, &addrLen);
printf("收到通知\n");

//给C1发信息
strLen = sendto(sock, SendBuffer, strlen(SendBuffer), 0, (struct sockaddr*)&opposite, sizeof(opposite));
printf("%d\n", strLen);

//等待C1返回,若返回则成功
printf("等待C1返回\n");
strLen = recvfrom(sock, RecvBuffer, BUF_SIZE, 0, &fromAddr, &addrLen);
printf("打洞成功\n");

system("pause()");
closesocket(sock);
WSACleanup();
return 0;
}


已经确定,A和B接收到的对方的IP地址和端口号都是服务器收到包里获得的那个。
...全文
2404 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
macWhale 2020-02-11
  • 打赏
  • 举报
回复
引用 10 楼 shanelikewind 的回复:
[quote=引用 9 楼 macWhale 的回复:]
妈蛋,我的也不行。感觉路由器对于“打洞”会有限制。检测到是打洞,就把原来的源端口立马更改了,导致两连端口不一致,打洞失败。

老兄,你说的这个改源端口是个啥现象啊?帮忙详细描述一下呗[/quote]
就是说,我是商品限制型,开始往外发包时,被NAT成10000源端口,然后,我另外一边从这个洞(10000端口)进来,会发现不成功。
不过后面我找到其它方法规避了。
shanelikewind 2019-12-26
  • 打赏
  • 举报
回复
引用 9 楼 macWhale 的回复:
妈蛋,我的也不行。感觉路由器对于“打洞”会有限制。检测到是打洞,就把原来的源端口立马更改了,导致两连端口不一致,打洞失败。
老兄,你说的这个改源端口是个啥现象啊?帮忙详细描述一下呗
macWhale 2019-07-17
  • 打赏
  • 举报
回复
妈蛋,我的也不行。感觉路由器对于“打洞”会有限制。检测到是打洞,就把原来的源端口立马更改了,导致两连端口不一致,打洞失败。
倾心软件 2019-02-22
  • 打赏
  • 举报
回复
正想做UDP打洞,看了你们这个,我觉得就是做出来了,也会不稳定.
Evil-Teddy 2018-05-11
  • 打赏
  • 举报
回复
老哥后来实现了吗,我最近在做这个,但是也是发现穿透不了。后来查资料说是中国的网络类型非常繁杂,发送一次消息可能经过很多层NAT,硬件是不支持打洞的。在中国做P2P没什么意义,大部分都要通过STUN/TRUN/ICE这样的协议来实现。
c_flybird 2017-07-25
  • 打赏
  • 举报
回复
引用 3 楼 pxkyyqh 的回复:
引用这句话,原文地址:http://blog.csdn.net/ustcgy/article/details/5655050 “假如 CLIENT A 开始发送一个UDP信息到CLIENT B的公网地址上,与此同时,他又通过S中转发送了一个邀请信息给CLIENT B,请求CLIENT B也给CLIENT A发送一个UDP信息到 CLIENT A的公网地址上.这时CLIENT A向CLIENT B的公网IP(138.76.29.7:31000)发送的信息导致 NAT A 打开一个处于CLIENT A的私有地址和CLIENT B的公网地址之间的新的通信会话,与此同时NAT B也打开了一个处于CLIENT B的私有地址和CLIENT A的公网地址(155.99.25.11:62000)之间的新的通信会话.一旦这个新的UDP会话各自向对方打开了,CLIENT A和CLIENT B之间就可以直接通信,而无需S来牵线搭桥了” 你的思路没有问题,但是打洞不会发一次就成功,你需要多发送几次,最好是通过中间服务器来协调两端发送的时机,还有一点需要注意,你的代码层面是同步的,建议采用异步方式处理接收和发送。
但是B一直可以收到服务器的通知,一直循环给A发,说明B的地址没有问题,端口号中途也没有变。 会不会是JAVA里面的split() 解析服务器发来的地址那里有问题? 但是我解析完,打印出来的时候,和服务器上打印的又是一样的
c_flybird 2017-07-25
  • 打赏
  • 举报
回复
引用 3 楼 pxkyyqh 的回复:
引用这句话,原文地址:http://blog.csdn.net/ustcgy/article/details/5655050 “假如 CLIENT A 开始发送一个UDP信息到CLIENT B的公网地址上,与此同时,他又通过S中转发送了一个邀请信息给CLIENT B,请求CLIENT B也给CLIENT A发送一个UDP信息到 CLIENT A的公网地址上.这时CLIENT A向CLIENT B的公网IP(138.76.29.7:31000)发送的信息导致 NAT A 打开一个处于CLIENT A的私有地址和CLIENT B的公网地址之间的新的通信会话,与此同时NAT B也打开了一个处于CLIENT B的私有地址和CLIENT A的公网地址(155.99.25.11:62000)之间的新的通信会话.一旦这个新的UDP会话各自向对方打开了,CLIENT A和CLIENT B之间就可以直接通信,而无需S来牵线搭桥了” 你的思路没有问题,但是打洞不会发一次就成功,你需要多发送几次,最好是通过中间服务器来协调两端发送的时机,还有一点需要注意,你的代码层面是同步的,建议采用异步方式处理接收和发送。
还是不行呢,我写了一个2秒超时重传的程序, 还是每次A给B发,然后让S通知B,B回A,看看A能否收到,超过2秒重新来一遍。结果还是一直收不到,不断在循环。 另外,我现在手上用的两台电脑,一台连的wifi ,一个接的手机的热点,这两个作为客户端,在服务器上收到的ip地址不一样,应该是在不同的NAT下面。
c_flybird 2017-07-24
  • 打赏
  • 举报
回复
引用 1 楼 kawenmai 的回复:
原理没什么大问题, 但打洞第一次都是不通的, 你在peer里for个循环多send几次给对方试试?
不行呢,我试过了。先是客户端给对方循环发送20次,出了循环后再都调用recvfrom() ,还是会停在recvfrom这里不动
  • 打赏
  • 举报
回复
引用这句话,原文地址:http://blog.csdn.net/ustcgy/article/details/5655050 “假如 CLIENT A 开始发送一个UDP信息到CLIENT B的公网地址上,与此同时,他又通过S中转发送了一个邀请信息给CLIENT B,请求CLIENT B也给CLIENT A发送一个UDP信息到 CLIENT A的公网地址上.这时CLIENT A向CLIENT B的公网IP(138.76.29.7:31000)发送的信息导致 NAT A 打开一个处于CLIENT A的私有地址和CLIENT B的公网地址之间的新的通信会话,与此同时NAT B也打开了一个处于CLIENT B的私有地址和CLIENT A的公网地址(155.99.25.11:62000)之间的新的通信会话.一旦这个新的UDP会话各自向对方打开了,CLIENT A和CLIENT B之间就可以直接通信,而无需S来牵线搭桥了” 你的思路没有问题,但是打洞不会发一次就成功,你需要多发送几次,最好是通过中间服务器来协调两端发送的时机,还有一点需要注意,你的代码层面是同步的,建议采用异步方式处理接收和发送。
kawenmai 2017-07-23
  • 打赏
  • 举报
回复
原理没什么大问题, 但打洞第一次都是不通的, 你在peer里for个循环多send几次给对方试试?

4,356

社区成员

发帖
与我相关
我的任务
社区描述
通信技术相关讨论
社区管理员
  • 网络通信
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧