不得不说,在国内IP紧缺的现状下,NAT发挥了无比巨大的作用:它以把IP和端口重新分配的方式,满足了广大人民群众上网的强烈需求。但是对于个人服务器以及在内网中基于网络的嵌入式设备,却是个比较尴尬的事情:因为它把端口和IP进行了重新分配,外网客户端访问的时候很难知道server端的IP和监听端口,尤其是监听端口。这个时候,子网穿透技术就应运而生了。
      这两天看了一些简单的子网穿透的基本原理,大多数都是UDP的。但是看完后觉得TCP实现起来更为简单,因此简单的写了一个测试程序实验了一下,结果还真行。下面是实现原理:
      首先是搭建了一下网络模型,一个是在子网中的需要穿透的Server A,一个是在公网上的辅助穿透的Server S,另一个是需要连接Server A的ClientB,由于是客户端,在不在子网倒无所谓了。下面是简图:

其次简单介绍一下实现过程:首先是Server S建立一个监听socket,用于辅助打洞。然后在Server A上建立一个socket去连接ServerS的监听端口,然后这个时候NAT A就会给此连接分配一个端口和IP,而Server S是知道此IP和端口的,然后Server S将此信息记录下来。 接着Server A要在刚才那个连接ServerS上的端口上再建立一个监听socket。然后Client B先去和Server S建立联系,获取Server A的端口和IP信息,然后就可以去连接ServerA了。
      其中有两个地方要注意,其中ServerA上的两个socket要设置SO_REUSEADDR的属性;其次是建议ServerA在连接ServerS前也先绑定一下端口。
      最后是上代码,当然其中我为了省事儿,没有搭建NAT B,也没有去实现框图中的Step2,而是采取Server S打印的方式,然后配合手动修改ClientB的连接端口实现的……然后我的Server S的IP是10.10.10.66;然后client B的IP是10.10.10.88;NAT A用的是一个TP-LINK的家用路由器,WAN口的IP是10.10.10.77,LAN口是192.168.1.1;Server A的IP是192.168.1.99,网关是192.168.1.1。
      首先是ServerS上的代码,为了省事儿,请忽视句柄泄露等问题……

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <time.h> int main()
{
int fd_socket;
int fd_connect;
struct sockaddr_in serv_addr;
struct sockaddr_in client_addr;
socklen_t cli_len; fd_socket= socket(AF_INET, SOCK_STREAM, );
if(fd_socket < )
{
printf("Init socket failed!\n");
return -;
}
int iOption_value = ;
int iLength = sizeof(int);
if(setsockopt(fd_socket,SOL_SOCKET,SO_REUSEADDR,&iOption_value, iLength)<)
{
printf("setsockopt error\n");
return -;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons();
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(fd_socket, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < )
{
perror("bind");
printf("Bind failed!\n");
return -;
}
if(listen(fd_socket, ) < )
{
printf("Listen failed!\n");
}
printf("start to accept!\n");
while()
{
fd_connect = accept(fd_socket, (struct sockaddr *)&client_addr, &cli_len);
printf("the ServerA ip is %s, port is %d\n",inet_ntoa(client_addr.sin_addr),htons(client_addr.sin_port));
usleep();
}
printf("over\n"); return ;
}

然后是ClientB的代码,其中IP(10.10.10.77)和端口(1043)是在ServerS打印之后才写进去进行编译运行的。

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <time.h> int main()
{
int fd_socket;
struct sockaddr_in serv_addr;
fd_socket= socket(AF_INET, SOCK_STREAM, );
if(fd_socket < )
{
printf("Init socket failed!\n");
return -;
} memset(&serv_addr, , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons();
serv_addr.sin_addr.s_addr = inet_addr("10.10.10.77");
if(connect(fd_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < )
{
printf("connect error\n");
return -;
}
printf("connect success\n");
sleep();
return ;
}

最后是ServerA的代码:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <time.h> int main()
{
int fd_socket;
int fd_connect;
int fd_server;
struct sockaddr_in serv_addr;
struct sockaddr_in client_addr;
fd_socket= socket(AF_INET, SOCK_STREAM, );
if(fd_socket < )
{
printf("Init socket failed!\n");
return -;
}
int iOption_value = ;
int iLength = sizeof(int);
if(setsockopt(fd_socket,SOL_SOCKET,SO_REUSEADDR,&iOption_value, iLength)<)
{
printf("setsockopt error\n");
return -;
}
memset(&client_addr, , sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons();
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(fd_socket,(struct sockaddr *)&client_addr, sizeof(client_addr)) < )
{
perror("bind");
return -;
}
memset(&serv_addr, , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons();
serv_addr.sin_addr.s_addr = inet_addr("10.10.10.66");
if(connect(fd_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < )
{
printf("connect error\n");
return -;
}
memset(&client_addr, , sizeof(client_addr));
int iAddrLen = sizeof(client_addr);
if(getsockname(fd_socket, (struct sockaddr *)&client_addr, &iAddrLen) < )
{
printf("getsockname\n");
return -;
}
printf("the port is %d\n", htons(client_addr.sin_port));
fd_server = socket(AF_INET, SOCK_STREAM, );
if(fd_server < )
{
printf("Init socket failed!\n");
return -;
}
if(setsockopt(fd_server,SOL_SOCKET,SO_REUSEADDR,&iOption_value, iLength)<)
{
printf("setsockopt error\n");
return -;
}
memset(&serv_addr, , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = client_addr.sin_port;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(fd_server,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < )
{
perror("bind");
return -;
}
if(listen(fd_server, ) < )
{
perror("listen");
}
printf("listen success\n");
sleep(); return ;
}

根据打印消息以及抓包数据分析来看,clientB是可以和ServerA建立连接的。
      虽然这个实验是成功了,但是当我把这种方式应用到实际项目中却出现了两个问题:
      一是以这种模式建立的通讯模式,带宽很不稳定,有时候数据会阻塞很久才能发过去,应该是中间有丢包。
      二是一旦ServerA断掉ClientB的连接,ClientB就无法再与ServerA建立连接了。
      问题二我觉得还比较好理解,因为一般NAT建立TCP的端口映射是根据SYS和FIN包为起始终止标识来建立的,当ServerA向ClientB发FIN的时候,就会被NAT检测到,然后关掉这种端口映射关系。
     但是问题一我就想不明白了,因为当我把路由器的DMZ打开后,数据是很流畅的,可以证明物理线路是能保证足够带宽的,只有在这种应用下才会出现带宽下降问题,希望有哪位高手可以帮忙解答一下……

基于TCP的NAT子网穿透实验的更多相关文章

  1. [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)

     [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching) http://www.360doc.com/content/12/0428/17/6187784 ...

  2. 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

    本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...

  3. 基于TCP 协议的RPC

    前言: 环境: windown 10 Eclipse JDK 1.8 RPC的概念: RPC 是远程过程调用,是分布式网站的基础. 实验 SayHelloService.java 接口类,用于规范 S ...

  4. Nginx基于TCP的负载均衡的配置例子

    原文:https://blog.csdn.net/bigtree_3721/article/details/72833955 nginx-1.9.0 已发布,该版本增加了 stream 模块用于一般的 ...

  5. 网络编程----socket介绍、基于tcp协议的套接字实现、基于udp协议的套接字实现

    一.客户端/服务器架构(C/S架构)                                                即C/S架构,包括: 1.硬件C/S架构(打印机) 2.软件C/S架 ...

  6. 20169202 2016-2017-2《TCP/IP协议攻击》实验总结--十一周

    APR缓存中毒(ARP cache poisoning) 实验原理 ARP缓存是ARP协议的重要组成部分.ARP协议运行的目标就是建立MAC地址和IP地址的映射,然后把这一映射关系保存在ARP缓存中, ...

  7. 模拟一个简单的基于tcp的远程关机程序(转)

    最近在学习unix网络编程,现在正在学习tcp的通信.其实,只要建立起了tcp通信,操作远端的计算机就不是什么问题了.正向telnet一样,也是基于tcp/IP协议的.所以这个实验,也算是对telne ...

  8. 基于Tcp穿越的Windows远程桌面(远程桌面管理工具)

    基于Tcp穿越的Windows远程桌面(远程桌面管理工具) 1.<C# WinForm 跨线程访问控件(实用简洁写法)>            2.<基于.NET环境,C#语言 实现 ...

  9. 基于TCP的客户端、服务器端socket编程

    一.实验目的 理解tcp传输客户端服务器端通信流程 二.实验平台 MAC OS 三.实验内容 编写TCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的IP地址.端口号,并 ...

随机推荐

  1. IO(Input Output)流__字节流

    续: ------->>>>字节流 IntputStream  OutputStream 需求:想要操作图片数据,就需要用到字节流. 读写操作: FileOutputStrea ...

  2. 简约的返回顶部效果(jQuery)

    博客建好也快一个月了,主题是在原有主题的基础上做的修改,喜欢这样的清新简约风格,为了更好的体验,以后也会经常做修改. 一般博客的文章都比较长,看到下面不能快速返回到顶部还真是不方便,今天自己动手写了一 ...

  3. java 加减乘除错误

    有次做一个for循环(1000次左右),做的事情也是很简单的事情,就是   Double testValue = (long类型 / 8 ) * long类型 的一些加减乘除操作, 但是总是出现一些数 ...

  4. C#实现FTP文件夹下载功能【转载】

    网上有很多FTP单个文件下载的方法,前段时间需要用到一个FTP文件夹下载的功能,于是找了下网上的相关资料结合MSDN实现了一段FTP文件夹下载的代码. 实现的思路主要是通过遍历获得文件夹下的所有文件, ...

  5. FC网络学习笔记01

    1.Fibre Channel 也就是“网状通道”的意思,简称FC,可以称其为FC协议.FC网络或FC互联. 2.像TCP/IP一样,FC协议集同样具备TCP/IP协议集以及以太网中的概念,比如FC交 ...

  6. Html学习笔记4

    <span style="font-size:18px;">超链接: 1 标签 语法: <a href="链接跳转后的地址 " >链接文 ...

  7. (转载)OSI七层参考模型和TCP/IP四层参考模型

    Mallory   网络模型概念浅析 网络模型一般是指OSI七层参考模型和TCP/IP四层参考模型. #只是一种设计==模型# Open System Interconnect的缩写,意为开放式系统互 ...

  8. 一个类似于QQ语音聊天时的拖拽移动悬浮小球

    闲来无事,分享一个最近在某个地方借鉴的一个demo(原谅我真的忘了在哪里看到的了,不然也就贴地址了)这个demo的逻辑思路并不是很难,推敲一下,很快就能理解,只是觉得这样的一个组合控件用起来蛮能增色自 ...

  9. 在iis中mantisbt配置过程

    最近需要安装个mantisbt,由于不想再安装个apache服务器,因此直接使用iis作为php解析服务器.同时为了方便管理安装包,将php安装包和扩展包能够独立存放在D:\Program Files ...

  10. C#面向对象(一)

    一:面向对象的基本知识 C#程序分为面向过程和面向对象 什么是对象:一切皆为对象:Object,生活中常说的“东西”就是程序里面所指的对象:生活中遇到的东西我们都在下意识的归类:归类意味着抽象模型: ...