├── README.md ├── sever.c └── client.c /README.md: -------------------------------------------------------------------------------- 1 | # UDP-Hole-Punching 2 | 基于UDP穿越非对称NAT建立P2P网络的Windows实现(UDP打洞) 3 | 4 | ## 原理 5 | AB设备在连接的时候访问一次服务器,服务器协助他们获取对方端口。 辅助完成后服务器断线,剩下的通讯由两个设备进行。 6 | 7 | ## 注意 8 | #### NAT类型 9 | 并不是所有网络环境都可以打洞,完全圆锥形NAT(Full Cone NAT)可以实现全程P2P连接,其中一方为对称锥形NAT(symmetric NAT)不能通过此方法直接打洞,但是可以尝试通过猜测上下文端口来进行连接。若双方设备都在对称锥形NATT(symmetric NAT)之后,则无法打洞。 10 | 11 | #### 防火墙 12 | 若在交换机处部署了防火墙或系统内开启了域防火墙等过滤端口的情况,有可能穿越失败。建议关闭~ 13 | #### 保持连接 14 | 需要防止NAT设备所分配的临时端口被回收,可以通过维持一个心跳包来保证连接。 15 | 16 | ## 运行机制 17 | A设备连接S服务器,B设备连接S服务器。S服务器同时记录A、B临时端口和IP。然后S告诉A,B目前的临时端口,S告诉B,A目前的临时端口。此时A、B均拿到对方此时的临时IP和临时端口,就可以断开服务器连接,由他们自己之间完成剩下通信,然后服务器接着协调下一次的两台设备连接。但有点要注意的是这个临时端口长时间不使用会被NAT设备回收,所以A、B他们之间还需要通过心跳包维持通信状态。 18 | 19 | 感谢 **吴米/阿毛/ian** 提供的帮助 20 | -------------------------------------------------------------------------------- /sever.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll 7 | #define BUF_SIZE 100 8 | 9 | /* 记录对端IP端口结构体 */ 10 | typedef struct { 11 | struct in_addr ip; 12 | int port; 13 | }clientInfo; 14 | 15 | int main(void) { 16 | WSADATA wsaData; 17 | WSAStartup(MAKEWORD(2, 2), &wsaData); 18 | 19 | //对端信息结构体 20 | clientInfo info[2]; 21 | //创建套接字 22 | SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); 23 | //绑定套接字 24 | sockaddr_in servAddr; 25 | memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充 26 | servAddr.sin_family = PF_INET; //使用IPv4地址 27 | servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址 28 | servAddr.sin_port = htons(6623); //端口 29 | bind(sock, (SOCKADDR*)& servAddr, sizeof(SOCKADDR)); 30 | //接收客户端请求 31 | //SOCKADDR clntAddr; //客户端地址信息 32 | sockaddr_in clntAddr;//客户端地址信息 33 | int nSize = sizeof(SOCKADDR); 34 | char buffer[BUF_SIZE]; //缓冲区 35 | 36 | while (1) { 37 | memset(&info, 0, sizeof(clientInfo) * 2); 38 | 39 | //接收A客户端 40 | recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*) & clntAddr, &nSize); 41 | memcpy(&info[0].ip, &clntAddr.sin_addr, sizeof(struct in_addr)); //复制IP 42 | info[0].port = clntAddr.sin_port;//复制端口 43 | printf("A 客户端 IP:%s \t端口:%d 已创建链接!\n", inet_ntoa(info[0].ip), ntohs(info[0].port)); 44 | 45 | //此处A服务端进行链接以后,等待进行B服务端链接 46 | 47 | //接收B客户端 48 | recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*) & clntAddr, &nSize); 49 | memcpy(&info[1].ip, &clntAddr.sin_addr, sizeof(struct in_addr)); //复制IP 50 | info[1].port = clntAddr.sin_port;//复制端口 51 | printf("B 客户端 IP:%s \t端口:%d 已创建链接!\n", inet_ntoa(info[1].ip), ntohs(info[1].port)); 52 | 53 | printf("A服务端传输就绪,准备发送数据\n"); 54 | clntAddr.sin_addr = info[0].ip; 55 | clntAddr.sin_port = info[0].port; 56 | sendto(sock, (const char*)&info[1], sizeof(clientInfo), 0, (struct sockaddr*) & clntAddr, nSize); 57 | printf("A已发送\n"); 58 | 59 | printf("B服务端传输就绪,准备发送数据\n"); 60 | clntAddr.sin_addr = info[1].ip; 61 | clntAddr.sin_port = info[1].port; 62 | sendto(sock, (const char*)& info[0], sizeof(clientInfo), 0, (struct sockaddr*) & clntAddr, nSize); 63 | printf("B已发送\n"); 64 | } 65 | closesocket(sock); 66 | WSACleanup(); 67 | return 0; 68 | } -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll 4 | #define BUF_SIZE 512 5 | 6 | /*对端IP和端口结构体*/ 7 | typedef struct { 8 | struct in_addr ip; 9 | int port; 10 | }clientInfo; 11 | 12 | int main() { 13 | //对端数据结构体 14 | clientInfo info; 15 | 16 | //初始化DLL 17 | WSADATA wsaData; 18 | WSAStartup(MAKEWORD(2, 2), &wsaData); 19 | //创建套接字 20 | SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0); 21 | //服务器地址信息 22 | sockaddr_in servAddr; 23 | memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充 24 | servAddr.sin_family = PF_INET; 25 | servAddr.sin_addr.s_addr = inet_addr("0.0.0.0"); 26 | servAddr.sin_port = htons(6623); 27 | 28 | //不断获取用户输入并发送给服务器,然后接受服务器数据 29 | sockaddr fromAddr; 30 | int addrLen = sizeof(fromAddr); 31 | 32 | //刷新结构体 33 | memset(&info, 0, sizeof(info)); 34 | char severText[BUF_SIZE] = "服务器你好!"; 35 | char buffer[BUF_SIZE] = "你好,对方客户端!"; 36 | char other[BUF_SIZE] = { 0 };//对端数据 37 | //getchar(); 38 | sendto(sock, severText, strlen(severText), 0, (struct sockaddr*) & servAddr, sizeof(servAddr)); 39 | 40 | printf("已向服务器 %s 发包\n", inet_ntoa(servAddr.sin_addr)); 41 | recvfrom(sock, (char*)& info, sizeof(clientInfo), 0, &fromAddr, &addrLen); 42 | printf("收到响应 IP:%s \t端口:%d \n", inet_ntoa(info.ip), ntohs(info.port)); 43 | 44 | /*将服务器方转为对方端口*/ 45 | servAddr.sin_addr = info.ip; 46 | servAddr.sin_port = info.port; 47 | 48 | //预先发送一次数据,保障对方消息顺利通过我方NAT设备 49 | sendto(sock, buffer, sizeof(buffer), 0, (struct sockaddr*) & servAddr, sizeof(struct sockaddr_in)); 50 | 51 | while (1) 52 | { 53 | char other[BUF_SIZE] = { 0 };//对端数据 54 | sockaddr_in otherInfo;//客户端地址信息 55 | int otherInfoSize = sizeof(SOCKADDR); 56 | struct in_addr ip; 57 | 58 | //向对端发送数据 59 | int textByte = sendto(sock, buffer, sizeof(buffer), 0, (struct sockaddr*) & servAddr, sizeof(struct sockaddr_in)); 60 | printf("发送字节 %d \n", textByte); 61 | 62 | //接收对端数据 63 | int strLen = recvfrom(sock, other, sizeof(other), 0, (struct sockaddr*) & otherInfo, &otherInfoSize); 64 | memcpy(&ip, &otherInfo.sin_addr, sizeof(struct in_addr)); //复制IP 65 | printf("收到 IP:%s \t端口:%d 消息:%s\n", inet_ntoa(ip), ntohs(otherInfo.sin_port), other); 66 | 67 | /*将服务器方转为对方端口,这一步主要是防止中途端口变动*/ 68 | servAddr.sin_addr.s_addr = inet_addr(inet_ntoa(ip)); 69 | servAddr.sin_port = htons(ntohs(otherInfo.sin_port)); 70 | 71 | Sleep(5000); 72 | } 73 | closesocket(sock); 74 | WSACleanup(); 75 | return 0; 76 | } 77 | --------------------------------------------------------------------------------