您的位置:首页 > 编程语言

NAT穿透技术实现代码

2015-01-21 15:19 92 查看
利用NAT共用IP的原理实现

设计思路:

1:启动服务器,监听端口8877

2:第一次启动客户端(称为client1),连上服务器,服务器将返回字符串first,标识这个是client1,同时,服务器将记录下这个客户端的(经过转换之后的)IP和端口。

3:第二次启动客户端(称为client2),连上服务器,服务器将向其返回自身的发送端口(称为port2),以及client1的(经过转换之后的)IP和端口。

4:然后服务器再发client1返回client2(经过转换之后的)IP和端口,然后断开与这两个客户端的连接(此时,服务器的工作已经全部完成了)

5:client2尝试连接client1,这次肯定会失败,但它会在路由器上留下记录,以帮忙client1成功穿透,连接上自己,然后设置port2端口为可重用端口,并监听端口port2。

6:client1尝试去连接client2,前几次可能会失败,因为穿透还没成功,如果连接10次都失败,就证明穿透失败了(可能是硬件不支持),如果成功,则每秒向client2发送一次hello, world

7:如果client1不断出现send message: Hello, world,client2不断出现recv message: Hello, world,则证明实验成功了,否则就是失败了。

/*
文件:server.c
PS:第一个连接上服务器的客户端,称为client1,第二个连接上服务器的客户端称为client2
这个服务器的功能是:
1:对于client1,它返回"first",并在client2连接上之后,将client2经过转换后的IP和port发给client1;
2:对于client2,它返回client1经过转换后的IP和port和自身的port,并在随后断开与他们的连接。
*/

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>

#define MAXLINE 128
#define SERV_PORT 8877

//发生了致命错误,退出程序
void error_quit(const char *str)
{
fprintf(stderr, "%s", str);
//如果设置了错误号,就输入出错原因
if( errno != 0 )
fprintf(stderr, " : %s", strerror(errno));
printf("\n");
exit(1);
}

int main(void)
{
int i, res, cur_port;
int connfd, firstfd, listenfd;
int count = 0;
char str_ip[MAXLINE];    //缓存IP地址
char cur_inf[MAXLINE];   //当前的连接信息[IP+port]
char first_inf[MAXLINE];    //第一个链接的信息[IP+port]
char buffer[MAXLINE];    //临时发送缓冲区
socklen_t clilen;
struct sockaddr_in cliaddr;
struct sockaddr_in servaddr;

//创建用于监听TCP协议套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

//把socket和socket地址结构联系起来
res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if( -1 == res )
error_quit("bind error");

//开始监听端口
res = listen(listenfd, INADDR_ANY);
if( -1 == res )
error_quit("listen error");

while( 1 )
{
//接收来自客户端的连接
connfd = accept(listenfd,(struct sockaddr *)&cliaddr, &clilen);
if( -1 == connfd )
error_quit("accept error");
inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, str_ip, sizeof(str_ip));

count++;
//对于第一个链接,将其的IP+port存储到first_inf中,
//并和它建立长链接,然后向它发送字符串'first',
if( count == 1 )
{
firstfd = connfd;
cur_port = ntohs(cliaddr.sin_port);
snprintf(first_inf, MAXLINE, "%s %d", str_ip, cur_port);
strcpy(cur_inf, "first\n");
write(connfd, cur_inf, strlen(cur_inf)+1);
}
//对于第二个链接,将其的IP+port发送给第一个链接,
//将第一个链接的信息和他自身的port返回给它自己,
//然后断开两个链接,并重置计数器
else if( count == 2 )
{
cur_port = ntohs(cliaddr.sin_port);
snprintf(cur_inf, MAXLINE, "%s %d\n", str_ip, cur_port);
snprintf(buffer, MAXLINE, "%s %d\n", first_inf, cur_port);
write(connfd, buffer, strlen(buffer)+1);
write(firstfd, cur_inf, strlen(cur_inf)+1);
close(connfd);
close(firstfd);
count = 0;
}
//如果程序运行到这里,那肯定是出错了
else
error_quit("Bad required");
}
return 0;
}


/*

文件:client.c

PS:第一个连接上服务器的客户端,称为client1,第二个连接上服务器的客户端称为client2

这个程序的功能是:先连接上服务器,根据服务器的返回决定它是client1还是client2,

若是client1,它就从服务器上得到client2的IP和Port,连接上client2,

若是client2,它就从服务器上得到client1的IP和Port和自身经转换后的port,

在尝试连接了一下client1后(这个操作会失败),然后根据服务器返回的port进行监听。

这样以后,就能在两个客户端之间进行点对点通信了。

*/

#include <stdio.h>

#include <unistd.h>

#include <signal.h>

#include <sys/socket.h>

#include <fcntl.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <arpa/inet.h>

#define MAXLINE 128

#define SERV_PORT 8877

typedef struct

{

char ip[32];

int port;

}server;

//发生了致命错误,退出程序

void error_quit(const char *str)

{

fprintf(stderr, "%s", str);

//如果设置了错误号,就输入出错原因

if( errno != 0 )

fprintf(stderr, " : %s", strerror(errno));

printf("\n");

exit(1);

}

int main(int argc, char **argv)

{

int i, res, port;

int connfd, sockfd, listenfd;

unsigned int value = 1;

char buffer[MAXLINE];

socklen_t clilen;

struct sockaddr_in servaddr, sockaddr, connaddr;

server other;

if( argc != 2 )

error_quit("Using: ./client <IP Address>");

//创建用于链接(主服务器)的套接字

sockfd = socket(AF_INET, SOCK_STREAM, 0);

memset(&sockaddr, 0, sizeof(sockaddr));

sockaddr.sin_family = AF_INET;

sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

sockaddr.sin_port = htons(SERV_PORT);

inet_pton(AF_INET, argv[1], &sockaddr.sin_addr);

//设置端口可以被重用

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));

//连接主服务器

res = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));

if( res < 0 )

error_quit("connect error");

//从主服务器中读取出信息

res = read(sockfd, buffer, MAXLINE);

if( res < 0 )

error_quit("read error");

printf("Get: %s", buffer);

//若服务器返回的是first,则证明是第一个客户端

if( 'f' == buffer[0] )

{

//从服务器中读取第二个客户端的IP+port

res = read(sockfd, buffer, MAXLINE);

sscanf(buffer, "%s %d", other.ip, &other.port);

printf("ff: %s %d\n", other.ip, other.port);

//创建用于的套接字

connfd = socket(AF_INET, SOCK_STREAM, 0);

memset(&connaddr, 0, sizeof(connaddr));

connaddr.sin_family = AF_INET;

connaddr.sin_addr.s_addr = htonl(INADDR_ANY);

connaddr.sin_port = htons(other.port);

inet_pton(AF_INET, other.ip, &connaddr.sin_addr);

//尝试去连接第二个客户端,前几次可能会失败,因为穿透还没成功,

//如果连接10次都失败,就证明穿透失败了(可能是硬件不支持)

while( 1 )

{

static int j = 1;

res = connect(connfd, (struct sockaddr *)&connaddr, sizeof(connaddr));

if( res == -1 )

{

if( j >= 10 )

error_quit("can't connect to the other client\n");

printf("connect error, try again. %d\n", j++);

sleep(1);

}

else

break;

}

strcpy(buffer, "Hello, world\n");

//连接成功后,每隔一秒钟向对方(客户端2)发送一句hello, world

while( 1 )

{

res = write(connfd, buffer, strlen(buffer)+1);

if( res <= 0 )

error_quit("write error");

printf("send message: %s", buffer);

sleep(1);

}

}

//第二个客户端的行为

else

{

//从主服务器返回的信息中取出客户端1的IP+port和自己公网映射后的port

sscanf(buffer, "%s %d %d", other.ip, &other.port, &port);

//创建用于TCP协议的套接字

sockfd = socket(AF_INET, SOCK_STREAM, 0);

memset(&connaddr, 0, sizeof(connaddr));

connaddr.sin_family = AF_INET;

connaddr.sin_addr.s_addr = htonl(INADDR_ANY);

connaddr.sin_port = htons(other.port);

inet_pton(AF_INET, other.ip, &connaddr.sin_addr);

//设置端口重用

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));

//尝试连接客户端1,肯定会失败,但它会在路由器上留下记录,

//以帮忙客户端1成功穿透,连接上自己

res = connect(sockfd, (struct sockaddr *)&connaddr, sizeof(connaddr));

if( res < 0 )

printf("connect error\n");

//创建用于监听的套接字

listenfd = socket(AF_INET, SOCK_STREAM, 0);

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons(port);

//设置端口重用

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));

//把socket和socket地址结构联系起来

res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

if( -1 == res )

error_quit("bind error");

//开始监听端口

res = listen(listenfd, INADDR_ANY);

if( -1 == res )

error_quit("listen error");

while( 1 )

{

//接收来自客户端1的连接

connfd = accept(listenfd,(struct sockaddr *)&sockaddr, &clilen);

if( -1 == connfd )

error_quit("accept error");

while( 1 )

{

//循环读取来自于客户端1的信息

res = read(connfd, buffer, MAXLINE);

if( res <= 0 )

error_quit("read error");

printf("recv message: %s", buffer);

}

close(connfd);

}

}

return 0;

}

在局域网内部能够是实现通过第三方通讯,外网能够 获得彼此的IP和端口号,但是打洞不成功。

分析原因:在内网使用的时,没用用到外部的端NAT端口转换,对于在公网上能够实现公网的IP和端口的获取,说明设计思路上是正确的,需要进行研究NAT的设置类型。

NAT设备的类型对于TCP穿越NAT,有着十分重要的影响,根据端口映射方式,NAT可分为如下4类,前3种NAT类型可统称为cone类型。

  (1)全克隆( Full Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。任何一个外部主机均可通过该映射发送IP包到该内部主机。

  (2)限制性克隆(Restricted Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。但是,只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包。

  (3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆与限制性克隆类似,只是多了端口号的限制,即只有内部主机先向IP地址为X,端口号为P的外部主机发送1个IP包,该外部主机才能够把源端口号为P的IP包发送给该内部主机。

  (4)对称式NAT ( Symmetric NAT) :这种类型的NAT与上述3种类型的不同,在于当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, NAT对该内部主机的映射会有所不同。对称式NAT不保证所有会话中的私有地址和公开IP之间绑定的一致性。相反,它为每个新的会话分配一个新的端口号。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: