提到UDP打洞,先提一个概念叫做NAT。NAT就是把局域网内多个ip地址映射到公网上,从而能完成局域网到公网的访问。但是NAT有一个特性,就是必须先从局域网到公网上建立映射,而后公网才能和局域网之间进行相互通信。如果两个局域网之间的机器要借助公网进行通信,这时就需要打洞了。UDP打洞要借助一个打洞服务器,打洞服务器在公网上。
核心思路:
1.机器A,B,C等众多需要打洞的服务向打洞服务器注册。
2.A向打洞服务器发起向B打洞的请求。
3.B接收到向A的打洞请求,发送任意数据。(此时会被丢弃)
4.A在向B发起打洞请求,即发送任意数据。(此时打洞成功)
注意:3.4步骤顺序可颠倒,但是第一个请求肯定都会被丢弃。
server端
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Iphlpapi.h>
#pragma comment (lib, "Ws2_32.lib")
#pragma comment(lib,"Iphlpapi.lib")
int ioctl(int fd, long cmd, u_long *ptr) {
return ioctlsocket(fd, cmd, ptr);
}
int close(int fd) {
return closesocket(fd);
}
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <error.h>
#endif // !_WIN32
//中间枢纽获得A客户端的外网ip和port发送给客户端B,获得客户端B的外网ip和port发送给A
//B通过A打的洞发数据给A,这时候A拒收B的消息,因为A的nat映射中没有B的信息,但是这次通
//信却在B的网管中添加了映射可以接受A的
//消息,同理A通过B打的洞发数据给B,这时候由于B可以接受A的消息,所以数据接收成功且在A
//的映射中加入了B的信息,从而A与B可以跨服务器通信。实现p2p
/* 由于已知的外网服务器S可能并没有AB客户端的映射关系,所以要先建立A与S 还有 B与S之间的映射,这样才能进行udp穿透。 */
#define ERR_EXIT(m)\
do{\
perror(m);\
exit(1);\
}while(0)
/* 用来记录客户端发送过来的外网ip+port */
typedef struct {
struct in_addr ip;
int port;
}clientInfo;
int main()
{
/* 一个客户端信息结构体数组,分别存放两个客户端的外网ip+port */
clientInfo info[2];
/* 作为心跳包需要接收的一个字节 */
/* char ch; */
char str[10] = { 0 };
#ifdef _WIN32
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
#endif //_WIN32
/* udp socket描述符 */
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
ERR_EXIT("SOCKET");
struct sockaddr_in serveraddr;
memset(&s