需求:
实现局域网穿透
环境背景:
某一台主机通过互联网即外网和另外一台主机进行通信,主机首先通过拨号上网,这台主机就会分配一个临时ip地址,这时主机就可以和互联网进行通信;同样的另外一台主机也通过相同的方式进行上网,两边就可以进行通信了。在因互联网有很多的服务器,其ip地址是固定的,我们主机通过拨号上网向服务器发起请求,在互联网有个域名服务器会将请求转发给对应的服务器,服务器收到请求后进行验证,验证用户名和密码,验证通过就保持连接,否则断开连接。
产生问题:
①每一台主机都有ip,那么当前主机怎么知道另外一台主机的IP?
②由于主机的IP是临时IP,每天上网的IP都可能不一样,那么当IP发生改变是,当前主机和另外一台主机的连接怎么保持?
③主机进行上网都需要通过路由器或者猫,路由器和猫都有防火墙功能,一般不允许主机放到外网当做服务器的,不允许主机的IP暴露在互联网上,假如主机IP能够暴露在公网上,由于在互联网中需要通过域名服务器进行转发,假如另外一台主机所在的域名服务器没有你主机的ip,他需要向其他域名服务器进行查询,这个过程比较缓慢。而进行上网的是整个局域网的统一一个临时IP暴露在互联网上,那么当前主机怎么知道另外一台主机在局域网的IP。
协议分析:
TCP协议:是可靠、稳定的传输,会有三次握手来建立连接。
UDP协议:是不可靠、不稳定的传输,没有三次握手。
方案:
①准备一个公网服务器,分配一个用于UDP通信的IP和端口。
②准备两台主机,主机使用UDP连接服务器进行登录,服务器发送数据到主机,这时防火墙会打开一个开口,这时主机不要关闭连接,服务器进行通信后就知道了这台主机的IP和端口,另外一台主机也进行相同的操作,服务器再将另外一台主机的IP和端口发送给当前主机,当前主机就使用UDP和另外一台主机进行通信。
③因为UDP是不可靠的传输,所以当另外一台主机接收到数据时不会验证数据是从哪里发过来的,UDP不安全,用户可以在上海模拟长沙的登录;而TCP是可靠传输,发送数据前需要通过三次握手建立连接,而握手请求实发送到服务器的,主机1不能向主机2发起握手,因此TCP无法实现局域网穿透。
示例
服务端代码:
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define BUFFER_SIZE 1024
int udp_server();
sockaddr_in addrCli[2];//目的套接字地址族
int len[2] = { sizeof(addrCli[0]), sizeof(addrCli[1]) };
int size = 0;
int main()
{
//printf("%s(%d):%s\r\n", __FILE__,__LINE__,__FUNCTION__);
printf("this is server\r\n");
udp_server();//服务器代码
return 0;
}
int udp_server()
{
WSADATA data;
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
printf("WSAStartup errorNum = %d\r\n", GetLastError());
return -1;
}
SOCKET server = socket(PF_INET, SOCK_DGRAM, 0);
if (server == INVALID_SOCKET) {
printf("socket errorNum = %d\r\n", GetLastError());
return -1;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9527);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
if (SOCKET_ERROR == bind(server, (sockaddr*)&addr, sizeof(addr)))
{
printf("bind errorNum = %d\r\n", GetLastError());
return -1;
}
char recvBuf[BUFFER_SIZE] = { 0 };
char sendBuf[BUFFER_SIZE] = { 0 };
for (int i = 0; i < 2; i++) {
memset(&addrCli[i], 0, len[i]);
}
while (size<2)
{
memset(recvBuf, 0, BUFFER_SIZE);
recvfrom(server, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrCli[size], &len[size]);
printf("user%d ip=[%s] port=[%d]\n", size+1, inet_ntoa(addrCli[size].sin_addr), htons(addrCli[size].sin_port));
size++;
}
for (int i = 0; i < 2; i++) {
sendto(server, (char*)&addrCli[i], len[i], 0, (sockaddr*)&addrCli[2-i-1], len[2-i-1]);
}
getchar();
closesocket(server);
WSACleanup();
return 0;
}
客户端代码:
#include <iostream>
#include <WinSock2.h>
#include<process.h>
#pragma comment(lib,"ws2_32.lib")
#define BUFFER_SIZE 409600
int udp_client();
unsigned WINAPI SendMsg(void* arg);
unsigned WINAPI RecvMsg(void* arg);
sockaddr_in addrServer;
int len = sizeof(addrServer);
sockaddr_in addrUser;
int lenSend = sizeof(addrUser);
bool isExit = false;
int main()
{
printf("this is client\r\n");
udp_client();
return 0;
}
int udp_client()
{
WSADATA data;
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
printf("WSAStartup errorNum = %d\r\n", GetLastError());
return -1;
}
HANDLE hSendThread;
HANDLE hRecvThread;
SOCKET client = socket(PF_INET, SOCK_DGRAM, 0);
if (client == INVALID_SOCKET) {
printf("socket errorNum = %d\r\n", GetLastError());
return -1;
}
memset(&addrServer, 0, len);
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(9527);
addrServer.sin_addr.s_addr = inet_addr("127.0.0.1");
std::cout <<"服务器:"<<inet_ntoa(addrServer.sin_addr)<<","<<ntohs(addrServer.sin_port) << std::endl;
char sendBuf[BUFFER_SIZE] = { 0 };
char recvBuf[BUFFER_SIZE] = { 0 };
sendto(client, sendBuf, strlen(sendBuf) + 1, 0, (sockaddr*)&addrServer, len);
int ret = recvfrom(client, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrServer, &len);
memcpy(&addrUser, recvBuf, sizeof(addrUser));
hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&client, 0, NULL);
hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&client, 0, NULL);
if (hSendThread != NULL)
WaitForSingleObject(hSendThread, INFINITE);
if (hRecvThread != NULL)
WaitForSingleObject(hRecvThread, INFINITE);
closesocket(client);
WSACleanup();
return 0;
}
unsigned WINAPI SendMsg(void* arg)
{
SOCKET client = *((SOCKET*)arg);
char sendBuf[BUFFER_SIZE] = {0};
while (true)
{
memset(sendBuf, 0, BUFFER_SIZE);
fgets(sendBuf, BUFFER_SIZE, stdin);
if (sendBuf == NULL)
continue;
if (!strcmp(sendBuf, "Q\n") || !strcmp(sendBuf, "q\n"))
{
isExit = true;
break;
}
sendto(client, sendBuf, strlen(sendBuf), 0, (sockaddr*)&addrUser, lenSend);
}
return 0;
}
unsigned WINAPI RecvMsg(void* arg)
{
SOCKET client = *((SOCKET*)arg);
char recvBuf[BUFFER_SIZE] = {0};
while (!isExit)
{
memset(recvBuf, 0, BUFFER_SIZE);
int ret = recvfrom(client, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addrServer, &len);
if (ret > 0) {
std::cout << recvBuf << std::endl;
}
}
return 0;
}
运行结果图片: