UDP打洞,客户端给主机发送信息后,主机发回的信息传不到客户端
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代码类似,求教~