linux下C/C++网络编程基本-NAT穿透成功

Home / C++ MrLee 2018-9-25 3085

NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。
这种方法需要在专用网连接到因特网的路由器上安装NAT软件。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址。这样,所有使用本地地址的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址,才能和因特网连接。
另外,这种通过使用少量的公有IP 地址代表较多的私有IP 地址的方式,将有助于减缓可用的IP地址空间的枯竭。在RFC1632中有对NAT的说明。

服务端是linux下面跑的,可以稍等改下就是windows版本了

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
typedef struct sockaddr		SA;
typedef struct sockaddr_in	SA_IN;//服务器网络地址结构体
//sockaddr,sockaddr_in二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。
//sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。
//而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。
//使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了
typedef struct
{
	struct	in_addr	ip;
	int	port;
}Client; /* 记录ip与端口 */
int main(int argc, char **argv)
{
	SA_IN server;//服务器网络地址结构体
	int sockfd;
	Client clients[2];
	char s;
	socklen_t addrlen = sizeof(SA_IN);
	bzero(&server,sizeof(SA_IN));			//初始化
	server.sin_port = htons(8877);			//服务器端口号
	server.sin_family = AF_INET;			//设置为IP通信
	server.sin_addr.s_addr	= INADDR_ANY;	//允许连接到所有本地地址上
	
	/*创建服务器端套接字--IPv4协议,面向无连接通信,SOCK_DGRAM是UDP协议*/
	sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd < 0)
	{
		perror("server_sockfd creation failed");  
		return -1;
	}
	
	if(bind(sockfd,(SA *)&server,sizeof(SA_IN)) < 0)
	{
		perror("server socket bind failed");  
		return -1;
	}
	while(1)
	{
		bzero(clients, sizeof(Client) * 2);
		/* 接收两个心跳包并记录其与此连接的ip与端口 */
		printf("waiting for a packet...\n");										//阻塞读包recvfrom
		recvfrom(sockfd,&s,sizeof(char),0,(SA *) &server,&addrlen);					//读取一个字节
		memcpy(&clients[0].ip,&server.sin_addr,sizeof(struct in_addr));				//拷贝第一个连接信息
		clients[0].port = server.sin_port;											//赋值端口
		printf("%s\t%d OK\n", inet_ntoa(clients[0].ip), ntohs(clients[0].port));	//输出IP地址和端口
		printf("waiting for a packet...\n");										//阻塞读包recvfrom
		recvfrom(sockfd,&s,sizeof(char),0,(SA *)&server,&addrlen);					//继续读取
		memcpy(&clients[1].ip,&server.sin_addr,sizeof(struct in_addr));				//拷贝第二个连接信息
		clients[1].port = server.sin_port;											//赋值端口
		printf( "%s\t%d OK\n", inet_ntoa(clients[1].ip),ntohs(clients[1].port));	//输出IP地址和端口
		/* 分别向两台机器发送与此服务连接的ip与端口 */
		server.sin_addr = clients[0].ip;
		server.sin_port = clients[0].port;
		sendto(sockfd,&clients[1],sizeof(Client),0,(SA *)&server,sizeof(SA_IN));
		server.sin_addr = clients[1].ip;
		server.sin_port = clients[1].port;
		sendto(sockfd,&clients[0],sizeof(Client),0,(SA *)&server,sizeof(SA_IN));
	}
	return(0);
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
#define SER	"*.*.*.*"
#define PORT	8877
typedef struct
{
	struct in_addr	ip;
	int		port;
}IP;
typedef struct sockaddr		SA;
typedef struct sockaddr_in	SA_IN;
typedef struct
{
	int sockfd;
	SA *addr;
	int len;
}Conn,*LPConn;
void echo_cli( int sockfd, SA *addr, socklen_t *len )
{
	char buf[1024];
	while (1)
	{
		memset(buf,0,sizeof(buf));
		printf( ">" );
		gets(buf);
		sendto(sockfd,buf,strlen(buf),0,addr,sizeof(SA_IN));
		if(strcmp(buf,"exit") == 0)
			break;
	}
}
static void* receive_data(void* lpData)
{
	LPConn lpCon = (LPConn)lpData;
	char buf[1024];
	while(1)
	{
		memset(buf,0,sizeof(buf));
		recvfrom(lpCon->sockfd,buf,sizeof(buf)-1,0,lpCon->addr,&lpCon->len);
		buf[strlen( buf ) - 1] = '\0';
		printf("%s\n",buf);
		if(strcmp(buf,"exit") == 0)
			break;
	}
	free(lpData);
	return NULL;
}
int main( int argc, char **argv )
{
	SA_IN server;
	IP ip;
	socklen_t addrlen = sizeof(SA_IN);
	char s = 'a';
	int sockfd = socket( AF_INET, SOCK_DGRAM, 0 );
	if(sockfd < 0)
	{
		perror("client_sockfd creation failed");  
		return -1;
	}
	bzero(&server, sizeof(SA_IN));
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = inet_addr(SER);
	server.sin_port = htons(PORT);
	sendto(sockfd,&s,sizeof(char),0,(SA *)&server,sizeof(SA_IN));
	recvfrom(sockfd,&ip,sizeof(IP),0,(SA *)&server,&addrlen);
	printf( "IP:%s\tPort:%d\n", inet_ntoa(ip.ip), ntohs(ip.port));
	server.sin_addr = ip.ip;
	server.sin_port = ip.port;
	
	LPConn lpCon = (LPConn)malloc(sizeof(Conn));
	lpCon->addr = (SA *)&server;
	lpCon->len = addrlen;
	lpCon->sockfd = sockfd;
	
	pthread_t tidp;
	/* 创建线程pthread */
    if ((pthread_create(&tidp, NULL, receive_data, (void*)lpCon)) == -1)
    {
        printf("create thread error!\n");
        return 1;
    }
	
	echo_cli( sockfd, (SA *) &server, &addrlen );
	/* 等待线程pthread释放 */
	if (pthread_join(tidp, NULL))
	{
		printf("thread is not exit...\n");
		return -2;
	}
	
	close(sockfd);
	return 0;
}

改了一个windows的客户端测试程序

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define SERVER	"*.*.*.*"
#define PORT	8877
typedef struct
{
	struct in_addr	ip;
	int		port;
}Client; /* ip与端口 */
typedef struct sockaddr		SA;
typedef struct sockaddr_in	SA_IN;
typedef struct
{
	int sockfd;
	SA *addr;
	int len;
}Conn,*LPConn;
void echo_cli( int sockfd, SA *addr, int *len )
{
	char buf[1024];
	while (true)
	{
		memset(buf,0,sizeof(buf));
		printf(">");
		gets_s(buf,sizeof(buf));
		sendto(sockfd,buf,strlen(buf),0,addr,sizeof(SA_IN));
		if (strcmp( buf,"exit") == 0)
			break;
	}
}
int receive_data(void* lpData)
{
	LPConn lpCon = (LPConn)lpData;
	char buf[1024];
	while(true)
	{
		memset(buf,0,sizeof(buf));
		recvfrom(lpCon->sockfd,buf,sizeof(buf)-1,0,lpCon->addr,&lpCon->len);
		printf( "%s", buf );
		buf[strlen( buf ) - 1] = '\0';
	}
	delete lpCon;
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	//初始化网络环境
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		printf("WSAStartup failed\n");
		return -1;
	}
	int sockfd;
	SA_IN server;
	Client client;
	int addrlen = sizeof(SA_IN);
	char s = 'a';
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == INVALID_SOCKET)
	{
		printf("create socket failed\n");
		return -1;
	}
	memset(&server,0,sizeof(SA_IN));
	server.sin_family = AF_INET;
	server.sin_addr.S_un.S_addr = inet_addr(SERVER);
	server.sin_port = htons(PORT);
	int i = sendto(sockfd,&s,1,0,(SA*)&server,sizeof(SA_IN));
	char* pBuf = new char[sizeof(Client)];
	recvfrom(sockfd,pBuf,sizeof(Client),0,(SA*)&server,&addrlen);
	memcpy(&client,pBuf,sizeof(Client));
	printf("向 ip:%s\tPort:%d发数据\n", inet_ntoa(client.ip),ntohs(client.port));
	server.sin_addr = client.ip;
	server.sin_port = client.port;
	LPConn lpCon = new Conn;
	lpCon->addr = (SA *)&server;
	lpCon->len = addrlen;
	lpCon->sockfd = sockfd;
	//启动接收线程
	DWORD threadId;
	HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)receive_data,lpCon,0,&threadId);
	CloseHandle(hThread);
	//启动输入方法
	echo_cli(sockfd,(SA*)&server,&addrlen);
	//清理网络环境
	WSACleanup();
	system("pause");
	return 0;
}

有些光猫不知道为啥跑不通,可能是因为NAT的4种类型不完全支持。下面再贴出转过来的4种类型

1. NAT的原理与类型

        NAT是IETF标准,它通过将局域网内的主机IP地址映射为Internet上有效的公网IP地址,从而实现了网络地址的复用。使用NAT技术,局域网内的多台PC可以共享单个、全局路由的IP地址,减少了所需的IP地址的数量。

        NAT主要可以分为两类:基本NAT和NAPT ( Network Address Port Translation )

        基本NAT一般是用于NAT勇夺多个公网IP的情形下,将公网IP地址与内网主机进行静态绑定,基本上这种类型的NAT设备已经很少了。或许根本我们就没机会见到。

        NAPT(Network Address/Port Translators):其实这种才是我们常说的 NAT。NAPT将内部连接映射到外部网络中的一个单独IP地址上,同时在该地址上加上一个由NAT设备选定的端口号。根据映射方式不同,NAPT可以分为圆锥型NAT和对称性NAT,其中圆锥型NAT包括完全圆锥型NAT、地址限制圆锥型NAT和端口限制圆锥型NAT

1.1完全圆锥型NAT( Full Cone NAT )

完全圆锥型NAT,将从一个内部IP地址和端口来的所有请求,都映射到相同的外部IP地址和端口。并且,任何外部主机通过向映射的外部地址发送报文,都可以实现和内部主机进行通信。这是一种比较宽松的策略,只要建立了内部网络的IP地址和端口与公网IP地址和端口的映射关系,所有的Internet上的主机都可以访问该NAT之后的主机


1.2地址限制圆锥型NAT( Address Restricted Cone NAT )

地址限制圆锥型NAT也是将从相同的内部IP地址和端口来的所有请求映射到相同的公网IP地址和端口。但是与完全圆锥型NAT不同,当且仅当内部主机之前已经向公网主机发送过报文,此时公网主机才能向内网主机发送报文

1.3端口限制圆锥型NAT( Port Restricted Cone NAT )

类似与地址限制圆锥型NAT,但是更严格。端口受限圆锥型NAT增加了端口号的限制,当前仅当内网主机之前已经向公网主机发送了报文,公网主机才能和此内网主机通信。


1.4对称型NAT( Symmetric NAT)

对称型NAT把从同一内网地址和端口到相同目的地址和端口的所有请求,都映射到同一个公网地址和端口。如果同一个内网主机,用相同的内网地址和端口向另外一个目的地址发送报文,则会用不同的映射。这和端口限制型NAT不同,端口限制型NAT是所有请求映射到相同的公网IP地址和端口,而对称型NAT是不同的请求有不同的映射。

本文链接:https://www.it72.com/12406.htm

推荐阅读
最新回复 (0)
返回