UDP打洞,客户端给主机发送信息后,主机发回的信息传不到客户端

wemustact 2015-09-06 04:37:52
UDP打洞过程如下:

1、客户端A登录服务器,服务器将客户端A的私网和公网终结点记录下来。
2、客户端B登陆服务器,服务器将客户端B的私网和公网终结点记录下来。
3、服务器将A的公网终结点发给客户端B。
4、客户端B向客户端A发一个数据包
(此数据包作用:在客户端B的NAT上建立一个session,
该session为B分配一个端口,即打了一个往A方向的洞口,
以后如果有数据包从公网到达此端口,NAT将会把此数据包直接转发给客户端B)。
但此时客户端A收不到此数据包,因为A端的NAT拦截了这个未曾谋面的数据包。
5、客户端B向服务器报告:已经向A方向打洞,此时服务器将B的公网终结点发给A,
并命令客户端A往B方向发一个数据包(作用同第4步)。
6、客户端A发送一个数据包到客户端B的公网终结点上。
此时,客户端B已经接收到A发送过来的这个数据包,
至此打洞成功,以后A和B可以直接互相发送信息了!

在第三步这里发生了问题,理论上来说,主机收到了消息,客户端和主机已经建立了链接才对,但是发回去的信息却没有被客户端收到,使用抓包工具抓包,在服务器端抓到了发出去的包,但是在客户端没有抓到发过来的包。

另,此程序在局域网内可用。出现问题的时候,两台客户端分别为处于不同内网的电脑,服务器为阿里云上的服务器。

附源代码:


server:
#include "stdafx.h"
#include <Winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib");

#define DEFAULT_PORT 5050
#define BUFFER_SIZE 100



int _tmain(int argc, _TCHAR* argv[])
{
//server即外网服务器
int serverPort = DEFAULT_PORT;
WSADATA wsaData;
SOCKET serverListen;
struct sockaddr_in serverAddr;

//检查协议栈
if (WSAStartup(MAKEWORD(2,2),&wsaData) != 0 ) {
printf("Failed to load Winsock.\n");
return -1;
}

//建立监听socket
serverListen = socket(AF_INET,SOCK_DGRAM,0);
if (serverListen == INVALID_SOCKET) {
printf("socket() failed:%d\n",WSAGetLastError());
return -1;
}

serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(serverPort);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(serverListen,(LPSOCKADDR)&serverAddr,sizeof(serverAddr)) == SOCKET_ERROR) {
printf("bind() failed:%d\n",WSAGetLastError());
return -1;
}

//接收来自客户端的连接,source1即先连接到S的客户端C1
struct sockaddr_in sourceAddr1;
int sourceAddrLen1 = sizeof(sourceAddr1);
SOCKET sockC1 = socket(AF_INET,SOCK_DGRAM,0);
char bufRecv1[BUFFER_SIZE];
int len;

len = recvfrom(serverListen, bufRecv1, sizeof(bufRecv1), 0,(struct sockaddr*)&sourceAddr1,&sourceAddrLen1);
if (len == SOCKET_ERROR) {
printf("recv() failed:%d\n", WSAGetLastError());
return -1;
}

printf("C1 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr1.sin_addr)
,ntohs(sourceAddr1.sin_port));

//接收来自客户端的连接,source2即后连接到S的客户端C2
struct sockaddr_in sourceAddr2;
int sourceAddrLen2 = sizeof(sourceAddr2);
SOCKET sockC2 = socket(AF_INET,SOCK_DGRAM,0);
char bufRecv2[BUFFER_SIZE];

len = recvfrom(serverListen, bufRecv2, sizeof(bufRecv2), 0,(struct sockaddr*)&sourceAddr2,&sourceAddrLen2);
if (len == SOCKET_ERROR) {
printf("recv() failed:%d\n", WSAGetLastError());
return -1;
}

printf("C2 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr2.sin_addr)
,ntohs(sourceAddr2.sin_port));

//向C1发送C2的外网ip和port
char bufSend1[BUFFER_SIZE];//bufSend1中存储C2的外网ip和port
memset(bufSend1,'\0',sizeof(bufSend1));
char* ip2 = inet_ntoa(sourceAddr2.sin_addr);//C2的ip
char port2[10];//C2的port
itoa(ntohs(sourceAddr2.sin_port),port2,10);//10代表10进制
for (int i=0;i<strlen(ip2);i++) {
bufSend1[i] = ip2[i];
}
bufSend1[strlen(ip2)] = '^';
for (int i=0;i<strlen(port2);i++) {
bufSend1[strlen(ip2) + 1 + i] = port2[i];
}

len = sendto(sockC1,bufSend1,sizeof(bufSend1),0,(struct sockaddr*)&sourceAddr1,sizeof(sourceAddr1));
if (len == SOCKET_ERROR) {
printf("send() failed:%d\n",WSAGetLastError());
return -1;
} else if (len == 0) {
return -1;
} else {
printf("send() byte:%d\n",len);
}

//向C2发送C1的外网ip和port
char bufSend2[BUFFER_SIZE];//bufSend2中存储C1的外网ip和port
memset(bufSend2,'\0',sizeof(bufSend2));
char* ip1 = inet_ntoa(sourceAddr1.sin_addr);//C1的ip
char port1[10];//C1的port
itoa(ntohs(sourceAddr1.sin_port),port1,10);
for (int i=0;i<strlen(ip1);i++) {
bufSend2[i] = ip1[i];
}
bufSend2[strlen(ip1)] = '^';
for (int i=0;i<strlen(port1);i++) {
bufSend2[strlen(ip1) + 1 + i] = port1[i];
}

len = sendto(sockC2,bufSend2,sizeof(bufSend2),0,(struct sockaddr*)&sourceAddr2,sizeof(sourceAddr2));
if (len == SOCKET_ERROR) {
printf("send() failed:%d\n",WSAGetLastError());
return -1;
} else if (len == 0) {
return -1;
} else {
printf("send() byte:%d\n",len);
}


Sleep(100000);



//server的中间人工作已完成,退出即可,剩下的交给C1与C2相互通信
closesocket(serverListen);
closesocket(sockC1);
closesocket(sockC2);
WSACleanup();

return 0;
}

Client1:


#include "stdafx.h"
#include<Winsock2.h>
#include<stdio.h>
#include<stdlib.h>

#pragma comment(lib,"ws2_32.lib");

#define PORT 7778
#define BUFFER_SIZE 100

//调用方式:UDPClient1 10.2.2.2 5050 (外网服务器S的ip和port)


char* THCAR2char(TCHAR* tchStr)
{
int iLen = 2*wcslen(tchStr);//CString,TCHAR汉字算一个字符,因此不用普通计算长度
char* chRtn = new char[iLen+1];
wcstombs(chRtn,tchStr,iLen+1);//转换成功返回为非负值
return chRtn;
}

int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
struct sockaddr_in serverAddr;
struct sockaddr_in thisAddr;
char * arg1 = NULL, *arg2 = NULL;
thisAddr.sin_family = AF_INET;
thisAddr.sin_port = htons(PORT);
thisAddr.sin_addr.s_addr = htonl(INADDR_ANY);

if (argc<3) {
printf("Usage: client1[server IP address , server Port]\n");
return -1;
}

if (WSAStartup(MAKEWORD(2,2),&wsaData) != 0) {
printf("Failed to load Winsock.\n");
return -1;
}
arg2 = THCAR2char(argv[2]);
arg1 = THCAR2char(argv[1]);
//初始化服务器S信息
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(atoi(arg2));
serverAddr.sin_addr.s_addr = inet_addr(arg1);

//建立与服务器通信的socket和与客户端通信的socket
SOCKET sockS = socket(AF_INET,SOCK_DGRAM,0);
if (sockS == INVALID_SOCKET) {
printf("socket() failed:%d\n",WSAGetLastError());
return -1;
}
if (bind(sockS,(LPSOCKADDR)&thisAddr,sizeof(thisAddr)) == SOCKET_ERROR) {
printf("bind() failed:%d\n",WSAGetLastError());
return -1;
}
SOCKET sockC = socket(AF_INET,SOCK_DGRAM,0);
if (sockC == INVALID_SOCKET) {
printf("socket() failed:%d\n",WSAGetLastError());
return -1;
}

char bufSend[] = "I am C1";
char bufRecv[BUFFER_SIZE];
memset(bufRecv,'\0',sizeof(bufRecv));
struct sockaddr_in sourceAddr;//暂存接受数据包的来源,在recvfrom中使用
int sourceAddrLen = sizeof(sourceAddr);//在recvfrom中使用
struct sockaddr_in oppositeSideAddr;//C2的地址信息

int len;

//C1给S发送数据包
len = sendto(sockS,bufSend,sizeof(bufSend),0,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if (len == SOCKET_ERROR) {
printf("sendto() failed:%d\n", WSAGetLastError());
return -1;
}

//C1从S返回的数据包中得到C2的外网ip和port
len = recvfrom(sockS, bufRecv, sizeof(bufRecv), 0,(struct sockaddr*)&sourceAddr,&sourceAddrLen);
printf("sendto() failed:%d\n", WSAGetLastError());
if (len == SOCKET_ERROR) {
printf("recvfrom() failed:%d\n", WSAGetLastError());
return -1;
}

//下面的处理是由于测试环境(本机+两台NAT联网的虚拟机)原因,若在真实环境中不需要这段处理。
/*
关闭与服务器通信的socket,并把与C2通信的socket绑定到相同的端口,真实环境中,路由器的NAT会将客户端
对外的访问从路由器的外网ip某固定端口发送出去,并在此端口接收
*/
/*closesocket(sockS);
if (bind(sockC,(LPSOCKADDR)&thisAddr,sizeof(thisAddr)) == SOCKET_ERROR) {
printf("bind() failed:%d\n",WSAGetLastError());
return -1;
} */

char ip[20];
char port[10];
int i;
for (i=0;i<strlen(bufRecv);i++)
if (bufRecv[i] != '^')
ip[i] = bufRecv[i];
else break;
ip[i] = '\0';
int j;
for (j=i+1;j<strlen(bufRecv);j++)
port[j - i - 1] = bufRecv[j];
port[j - i - 1] = '\0';

oppositeSideAddr.sin_family = AF_INET;
oppositeSideAddr.sin_port = htons(atoi(port));
oppositeSideAddr.sin_addr.s_addr = inet_addr(ip);

//下面的处理是由于测试环境(本机+两台NAT联网的虚拟机)原因,若在真实环境中不需要这段处理。
/*
此处由于是在本机,ip为127.0.0.1,但是如果虚拟机连接此ip的话,是与虚拟机本机通信,而不是
真实的本机,真实本机即此实验中充当NAT的设备,ip为10.0.2.2。
*/
/*oppositeSideAddr.sin_addr.s_addr = inet_addr("10.0.2.2"); */

//设置sockC为非阻塞
unsigned long ul = 1;
ioctlsocket(sockC, FIONBIO, (unsigned long*)&ul);

//C1向C2不停地发出数据包,得到C2的回应,与C2建立连接
while (1) {
Sleep(1000);
//C1向C2发送数据包
len = sendto(sockC,bufSend,sizeof(bufSend),0,(struct sockaddr*)&oppositeSideAddr,sizeof(oppositeSideAddr));
if (len == SOCKET_ERROR) {
printf("while sending package to C2 , sendto() failed:%d\n", WSAGetLastError());
return -1;
}else {
printf("successfully send package to C2\n");
}

//C1接收C2返回的数据包,说明C2到C1打洞成功,C2可以直接与C1通信了
len = recvfrom(sockC, bufRecv, sizeof(bufRecv), 0,(struct sockaddr*)&sourceAddr,&sourceAddrLen);
if (len == WSAEWOULDBLOCK) {
continue;//未收到回应
}else {
printf("C2 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr.sin_addr)
,ntohs(sourceAddr.sin_port));
printf("C2 says:%s\n",bufRecv);

}
}

closesocket(sockC);


return 0;
}


放不下了,Client2与Client1代码类似,求教~
...全文
204 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
欧阳春晖 2015-09-08
  • 打赏
  • 举报
回复
引用 1 楼 JiangWenjie2014 的回复:
服务器,客户端1,客户端2,各自只需要一个socket就够了。你搞的这么复杂,服务器sock1收到客户端1的包,然后服务器再用sock2向客户端1发回客户端2的地址时,由于sock1和和sock2的端口不一样,客户端1只能收到来自sock1的包,sock2发出去的包被客户端1那边的路由器拒绝了。另外,还有些路由器,处于内网的客户端用同一个udp socket向两个服务器发送数据,然后两个服务器看到的客户端socket地址不一样,即处于内网中的客户端用的是同一个socket,但是不同服务器得到客户端外网socket地址是不一样的,这样的话udp穿透做不来吧。
这条消息同时和楼主说,请楼主也注意下: UDP穿透的过程是这样的,首先,由A向服务器C发送数据包,B向服务器C发送数据包,这样,A B 网络地址将被服务器C获知,然后,C会把A主机的信息发给B,B主机的信息发给A,这就是信息交换,事已至此,A网络知道B网络的地址,B网络也已经知道A的地址,然后,A主机向B主机发送数据包(B肯定收不到,但A到B的路径被打通),B主机向A主机发送数据包(A主机将收到B主机的数据包,B到A的方向被打通,但B网络路由器的映射未完全建立),最后,A主机在给B主机发送任何数据包,穿透即成功,不知道你们听明白了没,如果不懂可以回复信息或者加我q 744439622,如果有不懂的可以问我。 ------------------------------------------------------- 最后发条广告啊,本人目前正在开发高级编辑器(个人开发者项目),如果谁有兴趣帮忙,也可以来找我,开发环境是qt。
wemustact 2015-09-08
  • 打赏
  • 举报
回复
引用 4 楼 dong364 的回复:
没仔细看,但nat类型有多种,对称nat类型打洞就失效了,不对称nat类型又可分为3种,自己网上可以搜下,了解下,p2p打洞并不是都可以打通的,lz可以参考stun、ice
前面的我知道,stun,ice,确实不知道,学习了~
dong364 2015-09-08
  • 打赏
  • 举报
回复
没仔细看,但nat类型有多种,对称nat类型打洞就失效了,不对称nat类型又可分为3种,自己网上可以搜下,了解下,p2p打洞并不是都可以打通的,lz可以参考stun、ice
wemustact 2015-09-06
  • 打赏
  • 举报
回复
引用 1 楼 JiangWenjie2014 的回复:
服务器,客户端1,客户端2,各自只需要一个socket就够了。你搞的这么复杂,服务器sock1收到客户端1的包,然后服务器再用sock2向客户端1发回客户端2的地址时,由于sock1和和sock2的端口不一样,客户端1只能收到来自sock1的包,sock2发出去的包被客户端1那边的路由器拒绝了。另外,还有些路由器,处于内网的客户端用同一个udp socket向两个服务器发送数据,然后两个服务器看到的客户端socket地址不一样,即处于内网中的客户端用的是同一个socket,但是不同服务器得到客户端外网socket地址是不一样的,这样的话udp穿透做不来吧。
不对啊,朋友,我有个疑问,我发送信息的时候,Client1和Client2都是向同一个端口5050发的,为什么就可以收到呢? 只是发出来的时候不行吗?
wemustact 2015-09-06
  • 打赏
  • 举报
回复
引用 1 楼 JiangWenjie2014 的回复:
服务器,客户端1,客户端2,各自只需要一个socket就够了。你搞的这么复杂,服务器sock1收到客户端1的包,然后服务器再用sock2向客户端1发回客户端2的地址时,由于sock1和和sock2的端口不一样,客户端1只能收到来自sock1的包,sock2发出去的包被客户端1那边的路由器拒绝了。另外,还有些路由器,处于内网的客户端用同一个udp socket向两个服务器发送数据,然后两个服务器看到的客户端socket地址不一样,即处于内网中的客户端用的是同一个socket,但是不同服务器得到客户端外网socket地址是不一样的,这样的话udp穿透做不来吧。
谢谢您的回答,我觉得可能是第一个原因,我去检查一下试试~ 我知道有些路由器UDP穿透是做不来的,不过这个应该不是,再次感谢~
JiangWenjie2014 2015-09-06
  • 打赏
  • 举报
回复
服务器,客户端1,客户端2,各自只需要一个socket就够了。你搞的这么复杂,服务器sock1收到客户端1的包,然后服务器再用sock2向客户端1发回客户端2的地址时,由于sock1和和sock2的端口不一样,客户端1只能收到来自sock1的包,sock2发出去的包被客户端1那边的路由器拒绝了。另外,还有些路由器,处于内网的客户端用同一个udp socket向两个服务器发送数据,然后两个服务器看到的客户端socket地址不一样,即处于内网中的客户端用的是同一个socket,但是不同服务器得到客户端外网socket地址是不一样的,这样的话udp穿透做不来吧。

5,530

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 模式及实现
社区管理员
  • 模式及实现社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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