C语言实现UDP打洞

首先,如果你不是很清楚UDP打洞原理,建议先看下这篇博文,写的很好。

  http://blog.csdn.net/wenhuiqiao/article/details/5929186

  废话不多说,上代码,过程请看代码注释,其中Client1和Client2实现代码相同,只是方向上有变化。

  UDPServer:

  1. /* 
  2. 某局域网内客户端C1先与外网服务器S通信,S记录C1经NAT设备转换后在外网中的ip和port; 
  3. 然后另一局域网内客户端C2与S通信,S记录C2经NAT设备转换后在外网的ip和port;S将C1的 
  4. 外网ip和port给C2,C2向其发送数据包;S将C2的外网ip和port给C1,C1向其发送数据包,打 
  5. 洞完成,两者可以通信。(C1、C2不分先后) 
  6.  
  7. 测试假设C1、C2已确定是要与对方通信,实际情况下应该通过C1给S的信息和C2给S的信息,S 
  8. 判断是否给两者搭桥。(因为C1可能要与C3通信,此时需要等待C3的连接,而不是给C1和 
  9. C2搭桥) 
  10.  
  11. 编译:gcc UDPServer.c -o UDPServer -lws2_32 
  12. */  
  13. #include <Winsock2.h>  
  14. #include <stdio.h>  
  15. #include <stdlib.h>  
  16.   
  17. #define DEFAULT_PORT 5050  
  18. #define BUFFER_SIZE 100  
  19.   
  20. int main() {  
  21.     //server即外网服务器  
  22.     int serverPort = DEFAULT_PORT;  
  23.     WSADATA wsaData;  
  24.     SOCKET serverListen;  
  25.     struct sockaddr_in serverAddr;  
  26.   
  27.     //检查协议栈  
  28.     if (WSAStartup(MAKEWORD(2,2),&wsaData) != 0 ) {  
  29.         printf("Failed to load Winsock.\n");  
  30.         return -1;  
  31.     }  
  32.       
  33.     //建立监听socket  
  34.     serverListen = socket(AF_INET,SOCK_DGRAM,0);  
  35.     if (serverListen == INVALID_SOCKET) {  
  36.         printf("socket() failed:%d\n",WSAGetLastError());  
  37.         return -1;  
  38.     }  
  39.   
  40.     serverAddr.sin_family = AF_INET;  
  41.     serverAddr.sin_port = htons(serverPort);  
  42.     serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  43.   
  44.     if (bind(serverListen,(LPSOCKADDR)&serverAddr,sizeof(serverAddr)) == SOCKET_ERROR) {  
  45.         printf("bind() failed:%d\n",WSAGetLastError());  
  46.         return -1;  
  47.     }  
  48.   
  49.     //接收来自客户端的连接,source1即先连接到S的客户端C1  
  50.     struct sockaddr_in sourceAddr1;  
  51.     int sourceAddrLen1 = sizeof(sourceAddr1);  
  52.     SOCKET sockC1 = socket(AF_INET,SOCK_DGRAM,0);  
  53.     char bufRecv1[BUFFER_SIZE];  
  54.     int len;  
  55.   
  56.     len = recvfrom(serverListen, bufRecv1, sizeof(bufRecv1), 0,(struct sockaddr*)&sourceAddr1,&sourceAddrLen1);  
  57.     if (len == SOCKET_ERROR) {  
  58.         printf("recv() failed:%d\n", WSAGetLastError());  
  59.         return -1;  
  60.     }  
  61.   
  62.     printf("C1 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr1.sin_addr)  
  63.                 ,ntohs(sourceAddr1.sin_port));  
  64.   
  65.     //接收来自客户端的连接,source2即后连接到S的客户端C2  
  66.     struct sockaddr_in sourceAddr2;  
  67.     int sourceAddrLen2 = sizeof(sourceAddr2);  
  68.     SOCKET sockC2 = socket(AF_INET,SOCK_DGRAM,0);  
  69.     char bufRecv2[BUFFER_SIZE];  
  70.   
  71.     len = recvfrom(serverListen, bufRecv2, sizeof(bufRecv2), 0,(struct sockaddr*)&sourceAddr2,&sourceAddrLen2);  
  72.     if (len == SOCKET_ERROR) {  
  73.         printf("recv() failed:%d\n", WSAGetLastError());  
  74.         return -1;  
  75.     }  
  76.   
  77.     printf("C2 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr2.sin_addr)  
  78.                 ,ntohs(sourceAddr2.sin_port));    
  79.       
  80.     //向C1发送C2的外网ip和port  
  81.     char bufSend1[BUFFER_SIZE];//bufSend1中存储C2的外网ip和port  
  82.     memset(bufSend1,'\0',sizeof(bufSend1));  
  83.     char* ip2 = inet_ntoa(sourceAddr2.sin_addr);//C2的ip  
  84.     char port2[10];//C2的port  
  85.     itoa(ntohs(sourceAddr2.sin_port),port2,10);//10代表10进制  
  86.     for (int i=0;i<strlen(ip2);i++) {  
  87.         bufSend1[i] = ip2[i];  
  88.     }  
  89.     bufSend1[strlen(ip2)] = '^';  
  90.     for (int i=0;i<strlen(port2);i++) {  
  91.         bufSend1[strlen(ip2) + 1 + i] = port2[i];  
  92.     }  
  93.   
  94.     len = sendto(sockC1,bufSend1,sizeof(bufSend1),0,(struct sockaddr*)&sourceAddr1,sizeof(sourceAddr1));  
  95.     if (len == SOCKET_ERROR) {  
  96.         printf("send() failed:%d\n",WSAGetLastError());  
  97.         return -1;  
  98.     } else if (len == 0) {  
  99.         return -1;  
  100.     } else {  
  101.         printf("send() byte:%d\n",len);  
  102.     }  
  103.   
  104.     //向C2发送C1的外网ip和port  
  105.     char bufSend2[BUFFER_SIZE];//bufSend2中存储C1的外网ip和port  
  106.     memset(bufSend2,'\0',sizeof(bufSend2));  
  107.     char* ip1 = inet_ntoa(sourceAddr1.sin_addr);//C1的ip  
  108.     char port1[10];//C1的port  
  109.     itoa(ntohs(sourceAddr1.sin_port),port1,10);  
  110.     for (int i=0;i<strlen(ip1);i++) {  
  111.         bufSend2[i] = ip1[i];  
  112.     }  
  113.     bufSend2[strlen(ip1)] = '^';  
  114.     for (int i=0;i<strlen(port1);i++) {  
  115.         bufSend2[strlen(ip1) + 1 + i] = port1[i];  
  116.     }  
  117.       
  118.     len = sendto(sockC2,bufSend2,sizeof(bufSend2),0,(struct sockaddr*)&sourceAddr2,sizeof(sourceAddr2));  
  119.     if (len == SOCKET_ERROR) {  
  120.         printf("send() failed:%d\n",WSAGetLastError());  
  121.         return -1;  
  122.     } else if (len == 0) {  
  123.         return -1;  
  124.     } else {  
  125.         printf("send() byte:%d\n",len);  
  126.     }  
  127.   
  128.     //server的中间人工作已完成,退出即可,剩下的交给C1与C2相互通信  
  129.     closesocket(serverListen);  
  130.     closesocket(sockC1);  
  131.     closesocket(sockC2);  
  132.     WSACleanup();  
  133.   
  134.     return 0;  
  135. }  

UDPClient1:
  1. /* 
  2. 客户端C1,连接到外网服务器S,并从S的返回信息中得到它想要连接的C2的外网ip和port,然后 
  3. C1给C2发送数据包进行连接。 
  4. */  
  5. #include<Winsock2.h>  
  6. #include<stdio.h>  
  7. #include<stdlib.h>  
  8.   
  9. #define PORT 7777  
  10. #define BUFFER_SIZE 100  
  11.   
  12. //调用方式:UDPClient1 10.2.2.2 5050 (外网服务器S的ip和port)  
  13. int main(int argc,char* argv[]) {  
  14.     WSADATA wsaData;  
  15.     struct sockaddr_in serverAddr;  
  16.     struct sockaddr_in thisAddr;  
  17.   
  18.     thisAddr.sin_family = AF_INET;  
  19.     thisAddr.sin_port = htons(PORT);  
  20.     thisAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  21.   
  22.     if (argc<3) {  
  23.         printf("Usage: client1[server IP address , server Port]\n");  
  24.         return -1;  
  25.     }  
  26.   
  27.     if (WSAStartup(MAKEWORD(2,2),&wsaData) != 0) {  
  28.         printf("Failed to load Winsock.\n");  
  29.         return -1;  
  30.     }  
  31.   
  32.     //初始化服务器S信息  
  33.     serverAddr.sin_family = AF_INET;  
  34.     serverAddr.sin_port = htons(atoi(argv[2]));  
  35.     serverAddr.sin_addr.s_addr = inet_addr(argv[1]);  
  36.       
  37.     //建立与服务器通信的socket和与客户端通信的socket  
  38.     SOCKET sockS = socket(AF_INET,SOCK_DGRAM,0);  
  39.     if (sockS == INVALID_SOCKET) {  
  40.         printf("socket() failed:%d\n",WSAGetLastError());  
  41.         return -1;  
  42.     }  
  43.     if (bind(sockS,(LPSOCKADDR)&thisAddr,sizeof(thisAddr)) == SOCKET_ERROR) {  
  44.         printf("bind() failed:%d\n",WSAGetLastError());  
  45.         return -1;  
  46.     }  
  47.     SOCKET sockC = socket(AF_INET,SOCK_DGRAM,0);  
  48.     if (sockC == INVALID_SOCKET) {  
  49.         printf("socket() failed:%d\n",WSAGetLastError());  
  50.         return -1;  
  51.     }  
  52.   
  53.     char bufSend[] = "I am C1";  
  54.     char bufRecv[BUFFER_SIZE];  
  55.     memset(bufRecv,'\0',sizeof(bufRecv));  
  56.     struct sockaddr_in sourceAddr;//暂存接受数据包的来源,在recvfrom中使用  
  57.     int sourceAddrLen = sizeof(sourceAddr);//在recvfrom中使用  
  58.     struct sockaddr_in oppositeSideAddr;//C2的地址信息  
  59.   
  60.     int len;  
  61.       
  62.     //C1给S发送数据包  
  63.     len = sendto(sockS,bufSend,sizeof(bufSend),0,(struct sockaddr*)&serverAddr,sizeof(serverAddr));  
  64.     if (len == SOCKET_ERROR) {  
  65.         printf("sendto() failed:%d\n", WSAGetLastError());  
  66.         return -1;  
  67.     }  
  68.   
  69.     //C1从S返回的数据包中得到C2的外网ip和port  
  70.     len = recvfrom(sockS, bufRecv, sizeof(bufRecv), 0,(struct sockaddr*)&sourceAddr,&sourceAddrLen);  
  71.     if (len == SOCKET_ERROR) {  
  72.         printf("recvfrom() failed:%d\n", WSAGetLastError());  
  73.         return -1;  
  74.     }  
  75.   
  76.     //下面的处理是由于测试环境(本机+两台NAT联网的虚拟机)原因,若在真实环境中不需要这段处理。  
  77.     /* 
  78.       关闭与服务器通信的socket,并把与C2通信的socket绑定到相同的端口,真实环境中,路由器的NAT会将客户端 
  79.       对外的访问从路由器的外网ip某固定端口发送出去,并在此端口接收 
  80.     */  
  81.     closesocket(sockS);  
  82.     if (bind(sockC,(LPSOCKADDR)&thisAddr,sizeof(thisAddr)) == SOCKET_ERROR) {  
  83.         printf("bind() failed:%d\n",WSAGetLastError());  
  84.         return -1;  
  85.     }  
  86.   
  87.     char ip[20];  
  88.     char port[10];  
  89.     int i;  
  90.     for (i=0;i<strlen(bufRecv);i++)  
  91.         if (bufRecv[i] != '^')  
  92.             ip[i] = bufRecv[i];  
  93.         else break;  
  94.     ip[i] = '\0';  
  95.     int j;  
  96.     for (j=i+1;j<strlen(bufRecv);j++)  
  97.         port[j - i - 1] = bufRecv[j];  
  98.     port[j - i - 1] = '\0';  
  99.   
  100.     oppositeSideAddr.sin_family = AF_INET;  
  101.     oppositeSideAddr.sin_port = htons(atoi(port));  
  102.     oppositeSideAddr.sin_addr.s_addr = inet_addr(ip);  
  103.   
  104.     //下面的处理是由于测试环境(本机+两台NAT联网的虚拟机)原因,若在真实环境中不需要这段处理。  
  105.     /* 
  106.       此处由于是在本机,ip为127.0.0.1,但是如果虚拟机连接此ip的话,是与虚拟机本机通信,而不是 
  107.       真实的本机,真实本机即此实验中充当NAT的设备,ip为10.0.2.2。 
  108.     */  
  109.     oppositeSideAddr.sin_addr.s_addr = inet_addr("10.0.2.2");  
  110.   
  111.     //设置sockC为非阻塞  
  112.     unsigned long ul = 1;  
  113.     ioctlsocket(sockC, FIONBIO, (unsigned long*)&ul);  
  114.   
  115.     //C1向C2不停地发出数据包,得到C2的回应,与C2建立连接  
  116.     while (1) {  
  117.         Sleep(1000);  
  118.         //C1向C2发送数据包  
  119.         len = sendto(sockC,bufSend,sizeof(bufSend),0,(struct sockaddr*)&oppositeSideAddr,sizeof(oppositeSideAddr));  
  120.         if (len == SOCKET_ERROR) {  
  121.             printf("while sending package to C2 , sendto() failed:%d\n", WSAGetLastError());  
  122.             return -1;  
  123.         }else {  
  124.             printf("successfully send package to C2\n");  
  125.         }  
  126.       
  127.         //C1接收C2返回的数据包,说明C2到C1打洞成功,C2可以直接与C1通信了  
  128.         len = recvfrom(sockC, bufRecv, sizeof(bufRecv), 0,(struct sockaddr*)&sourceAddr,&sourceAddrLen);  
  129.         if (len == WSAEWOULDBLOCK) {  
  130.             continue;//未收到回应  
  131.         }else {  
  132.             printf("C2 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr.sin_addr)  
  133.                 ,ntohs(sourceAddr.sin_port));  
  134.             printf("C2 says:%s\n",bufRecv);  
  135.               
  136.         }  
  137.     }  
  138.   
  139.     closesocket(sockC);  
  140.   
  141.       
  142. }  

UDPClient2:
  1. /* 
  2. 客户端C2,连接到外网服务器S,并从S的返回信息中得到它想要连接的C1的外网ip和port,然后 
  3. C2给C1发送数据包进行连接。 
  4. */  
  5. #include<Winsock2.h>  
  6. #include<stdio.h>  
  7. #include<stdlib.h>  
  8.   
  9. #define PORT 8888  
  10. #define BUFFER_SIZE 100  
  11.   
  12. //调用方式:UDPClient2 10.2.2.2 5050 (外网服务器S的ip和port)  
  13. int main(int argc,char* argv[]) {  
  14.     WSADATA wsaData;  
  15.     struct sockaddr_in serverAddr;  
  16.     struct sockaddr_in thisAddr;  
  17.   
  18.     thisAddr.sin_family = AF_INET;  
  19.     thisAddr.sin_port = htons(PORT);  
  20.     thisAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  21.   
  22.     if (argc<3) {  
  23.         printf("Usage: client1[server IP address , server Port]\n");  
  24.         return -1;  
  25.     }  
  26.   
  27.     if (WSAStartup(MAKEWORD(2,2),&wsaData) != 0) {  
  28.         printf("Failed to load Winsock.\n");  
  29.         return -1;  
  30.     }  
  31.   
  32.     //初始化服务器S信息  
  33.     serverAddr.sin_family = AF_INET;  
  34.     serverAddr.sin_port = htons(atoi(argv[2]));  
  35.     serverAddr.sin_addr.s_addr = inet_addr(argv[1]);  
  36.       
  37.     //建立与服务器通信的socket和与客户端通信的socket,注意必须绑定到相同端口。  
  38.     SOCKET sockS = socket(AF_INET,SOCK_DGRAM,0);  
  39.     if (sockS == INVALID_SOCKET) {  
  40.         printf("socket() failed:%d\n",WSAGetLastError());  
  41.         return -1;  
  42.     }  
  43.     if (bind(sockS,(LPSOCKADDR)&thisAddr,sizeof(thisAddr)) == SOCKET_ERROR) {  
  44.         printf("bind() failed:%d\n",WSAGetLastError());  
  45.         return -1;  
  46.     }  
  47.     SOCKET sockC = socket(AF_INET,SOCK_DGRAM,0);  
  48.     if (sockC == INVALID_SOCKET) {  
  49.         printf("socket() failed:%d\n",WSAGetLastError());  
  50.         return -1;  
  51.     }  
  52.       
  53.     char bufSend[] = "I am C2";  
  54.     char bufRecv[BUFFER_SIZE];  
  55.     memset(bufRecv,'\0',sizeof(bufRecv));  
  56.     struct sockaddr_in sourceAddr;//暂存接受数据包的来源,在recvfrom中使用  
  57.     int sourceAddrLen = sizeof(sourceAddr);//在recvfrom中使用  
  58.     struct sockaddr_in oppositeSideAddr;//C1的地址信息  
  59.   
  60.     int len;  
  61.       
  62.     //C2给S发送数据包  
  63.     len = sendto(sockS,bufSend,sizeof(bufSend),0,(struct sockaddr*)&serverAddr,sizeof(serverAddr));  
  64.     if (len == SOCKET_ERROR) {  
  65.         printf("sendto() failed:%d\n", WSAGetLastError());  
  66.         return -1;  
  67.     }  
  68.   
  69.     //C2从S返回的数据包中得到C1的外网ip和port  
  70.     len = recvfrom(sockS, bufRecv, sizeof(bufRecv), 0,(struct sockaddr*)&sourceAddr,&sourceAddrLen);  
  71.     if (len == SOCKET_ERROR) {  
  72.         printf("recvfrom() failed:%d\n", WSAGetLastError());  
  73.         return -1;  
  74.     }  
  75.   
  76.     //下面的处理是由于测试环境(本机+两台NAT联网的虚拟机)原因,若在真实环境中不需要这段处理。  
  77.     /* 
  78.       关闭与服务器通信的socket,并把与C1通信的socket绑定到相同的端口;真实环境中,路由器的NAT会将客户端 
  79.       对外的访问从路由器的外网ip某固定端口发送出去,并在此端口接收 
  80.     */  
  81.     closesocket(sockS);  
  82.     if (bind(sockC,(LPSOCKADDR)&thisAddr,sizeof(thisAddr)) == SOCKET_ERROR) {  
  83.         printf("bind() failed:%d\n",WSAGetLastError());  
  84.         return -1;  
  85.     }  
  86.   
  87.     char ip[20];  
  88.     char port[10];  
  89.     int i;  
  90.     for (i=0;i<strlen(bufRecv);i++)  
  91.         if (bufRecv[i] != '^')  
  92.             ip[i] = bufRecv[i];  
  93.         else break;  
  94.     ip[i] = '\0';  
  95.     int j;  
  96.     for (j=i+1;j<strlen(bufRecv);j++)  
  97.         port[j - i - 1] = bufRecv[j];  
  98.     port[j - i - 1] = '\0';  
  99.   
  100.     oppositeSideAddr.sin_family = AF_INET;  
  101.     oppositeSideAddr.sin_port = htons(atoi(port));  
  102.     oppositeSideAddr.sin_addr.s_addr = inet_addr(ip);  
  103.   
  104.     //下面的处理是由于测试环境(本机+两台NAT联网的虚拟机)原因,若在真实环境中不需要这段处理。  
  105.     /* 
  106.       此处由于是在本机,ip为127.0.0.1,但是如果虚拟机连接此ip的话,是与虚拟机本机通信,而不是 
  107.       真实的本机,真实本机即此实验中充当NAT的设备,ip为10.0.2.2。 
  108.     */  
  109.     oppositeSideAddr.sin_addr.s_addr = inet_addr("10.0.2.2");  
  110.   
  111.     //设置sockC为非阻塞  
  112.     unsigned long ul = 1;  
  113.     ioctlsocket(sockC, FIONBIO, (unsigned long*)&ul);  
  114.   
  115.     //C2向C1不停地发出数据包,得到C1的回应,与C1建立连接  
  116.     while (1) {  
  117.         Sleep(1000);  
  118.         //C2向C1发送数据包  
  119.         len = sendto(sockC,bufSend,sizeof(bufSend),0,(struct sockaddr*)&oppositeSideAddr,sizeof(oppositeSideAddr));  
  120.         if (len == SOCKET_ERROR) {  
  121.             printf("while sending package to C1 , sendto() failed:%d\n", WSAGetLastError());  
  122.             return -1;  
  123.         }else {  
  124.             printf("successfully send package to C1\n");  
  125.         }  
  126.       
  127.         //C2接收C1返回的数据包,说明C1到C2打洞成功,C1可以直接与C2通信了  
  128.         len = recvfrom(sockC, bufRecv, sizeof(bufRecv), 0,(struct sockaddr*)&sourceAddr,&sourceAddrLen);  
  129.         if (len == WSAEWOULDBLOCK) {  
  130.             continue;//未收到回应  
  131.         }else {  
  132.             printf("C1 IP:[%s],PORT:[%d]\n",inet_ntoa(sourceAddr.sin_addr)  
  133.                 ,ntohs(sourceAddr.sin_port));  
  134.             printf("C1 says:%s\n",bufRecv);  
  135.               
  136.         }  
  137.     }  
  138.   
  139.     closesocket(sockC);  
  140.   
  141.       
  142. }  

测试结果:
  1. 本机Server输出  
  2. D:\>UDPServer  
  3. C1 IP:[127.0.0.1],PORT:[49724]  
  4. C2 IP:[127.0.0.1],PORT:[49725]  
  5. send() byte:100  
  6. send() byte:100  
  7.   
  8. 虚拟机Client1输出  
  9. D:\>UDPClient1 10.0.2.2 5050  
  10. successfully send package to C2  
  11. C2 IP:[10.0.2.2],PORT:[49725]  
  12. C2 says:I am C2  
  13. successfully send package to C2  
  14. C2 IP:[10.0.2.2],PORT:[49725]  
  15. C2 says:I am C2  
  16. successfully send package to C2  
  17. C2 IP:[10.0.2.2],PORT:[49725]  
  18. C2 says:I am C2  
  19.   
  20. 虚拟机Client2输出  
  21. D:\>UDPClient2 10.0.2.2 5050  
  22. successfully send package to C1  
  23. C1 IP:[10.0.2.2],PORT:[49727]  
  24. C1 says:127.0.0.1^49724  
  25. successfully send package to C1  
  26. C1 IP:[10.0.2.2],PORT:[49724]  
  27. C1 says:I am C1  
  28. successfully send package to C1  
  29. C1 IP:[10.0.2.2],PORT:[49724]  
  30. C1 says:I am C1  
  31.   
  32. 说明:  
  33. 该实验使两个NAT联网的虚拟机实现了相互通信,同时证明了VirtualBox的NAT可以实现UDP打洞,  
  34. 只要将两个Client中那两处声明的与测试环境有关的代码删除,即可置于实体环境中使用,现在百  
  35. 分之八十的路由器支持UDP打洞,该技术在qq、网游等需要两个不同局域网内的电脑可以相互通信的  
  36. 领域大有用途。 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值