一、初识NAT NAT 类型 NAT 分为四种类型,分别为 NAT1234 🙃
NAT1 - 完全锥形NAT(Full Clone NAT) 
NAT2 - 限制性锥形NAT(Restricted Clone NAT) 
NAT3 - 端口限制性锥形NAT( Port Restricted Clone NAT) 
NAT4 - 对称式NAT(Symmetric NAT) 
 
绿色 
现在假设
192.168.0.100 8000 114.135.246.90 9001 
经过NAT之后,会形成这样一种结果:
内网的 192.168.0.100:8000 120.230.117.10:9999  
服务端  114.135.246.90:9001 120.230.117.10:9999  
 
然后如果:紫色 紫色 浅蓝色 紫色 浅蓝色 绿色 
NAT3 和 NAT4 的区别:同一 ip:port 时,都会从 9999 端口出去,但通过 8000 端口访问其他 ip:port 时,就会绑定 NAT 其他端口,比如 9990
我这移动大内网,一般都是NAT3,手机开数据流量,基本都是NAT4 
NAT 打洞原理 打洞,说白了就是让 NAT 留下一条访问记录
( 对于NAT来说,因为没记录,所以也不知道转发给内网的谁,就算知道 如果没有记录,则数据包的来源也不一定合法 )
二、UDP打洞 看图 
这里我们通过一个服务端,然后让A B两个内网主机进行UDP打洞并通信。适用于 NAT123
说明:90.20.88.99 为服务端主机,下方称为 S 
 
图解:
首先我们让 A:8001 S:9001 NA A:8001 NA:3333 NA:3333 S:9001 A:8001 NA:3333 S:9001 
接着让 B NB B:6000 NB:4444 NB:4444 S:9001 B:6000 NB:4444 S:9001 
此时 S NA NB (看着上面的两个‘即’ 继续往下看) A S:9001 NA:3333 B S:9001 NB:4444 A B A NB B NA NB:4444  通过 S:9001 NA:3333 A NB:4444 B NA:3333  通过 S:9001 NB:4444 B NA:3333 A 
若此时 B:6000 NB:4444 NA:3333 NA 不存在  A:8001 NA:3333 NB:4444 (同理 A NB B:6000 NB:4444 NA:3333 这一步就是所谓的 “打洞” 
NB:4444 NA:3333 A:8001 NA:3333 NB:4444 NB B:6000 NB:4444 NA:3333 NA:3333 B:6000 NA A:8001 NA:3333 NB:4444 
之后 因为NA A:8001 NA:3333 NB:4444 A:8001 NA:3333 S:9001 NB B:6000 NB:4444 NA:3333 B:6000 NB:4444 S:9001 A B S A B 
 
看码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 const  dgram = require ("dgram" );const  option = process.argv .splice (2 )let  localPORT = 6677 const  SERVER_PORT  = option[0 ] || 9001 const  SERVER_ADDR  = option[1 ] || "114.135.246.90" let  localInfo = "" let  serverInfo = "" let  timeout = setInterval (() =>  {    createClient () }, 1000 ) let  getConnect  = (client ) => {    console .log (`与${serverInfo} 连接成功。` );     client.send ("" )                     } let  getMessage  = (client, data, rinfo ) => {    console .log (`[${rinfo.address} :${rinfo.port} ]:${data.toString()} ` )     let  res = JSON .parse (data.toString ())     if  (res.msg  == "wait" ) {     }     if  (res.msg  == "toClient" ) {         let  second = res.second          createNewClient (second.addr , second.port )         client.close ()     }     if  (res.msg  == "toServer" ) {         let  first = res.first          createServer (first.addr , first.port )         client.close ()     } } function  createClient (    const  client = dgram.createSocket ("udp4" );     try  {         client.bind (localPORT, "" , () =>  {             clearInterval (timeout)         })         client.connect (SERVER_PORT , SERVER_ADDR )         client.on ('connect' , () =>  getConnect (client))         client.on ('message' , (data, rinfo ) =>  {             getMessage (client, data, rinfo)         })         client.on ("listening" , (res ) =>  {             localInfo = `[${client.address().address} :${client.address().port} ]`              serverInfo = `[${SERVER_ADDR} :${SERVER_PORT} ]`              console .log (`${localInfo}  - ${serverInfo} ` );                      })         client.on ("close" , (res ) =>  {                      })         client.on ("error" , (err ) =>  {             console .log ("error" , "发生一个错误" );             console .log ("正在重连..." );             localPORT++                      })     } catch  (error) {         console .log ("错啦!!!" )         console .log (error)     } } function  createNewClient (serverAddr, serverPort ) {    const  client = dgram.createSocket ("udp4" );     try  {         client.bind (localPORT)         client.connect (serverPort, serverAddr)         client.on ('connect' , () =>  {             console .log (`与${serverInfo} 连接成功。` );             client.send (`${localInfo} ` )             console .log ("回车发送消息:" );             process.stdin .on ('data' , data  =>                 let  sendData = data.toString ().trim ()                 console .log (`${localInfo} : ${sendData} ` );                 client.send (sendData);             })         })         client.on ('message' , (data, rinfo ) =>  {             console .log (`[${rinfo.address} :${rinfo.port} ]: ${data.toString()} ` )         })         client.on ("listening" , (res ) =>  {             localInfo = `[${client.address().address} :${client.address().port} ]`              serverInfo = `[${serverAddr} :${serverPort} ]`              console .log (`${localInfo}  - ${serverInfo} ` );                      })         client.on ("close" , (res ) =>  {             console .log ("close" , res);         })         client.on ("error" , (err ) =>  {             console .log ("error" , "发生一个错误" );             console .log ("正在重连..." );             localPORT++                      })     } catch  (error) {         console .log ("错啦!!!" )         console .log (error)     } } function  createServer (clientAddr, clientPort ) {    const  server = dgram.createSocket ('udp4' )     server.bind (localPORT)          server.send ("" , clientPort, clientAddr)          server.send ("ready" , SERVER_PORT , SERVER_ADDR )     server.on ("listening" , () =>  {         console .log (`start to listening: ${server.address().address} :${server.address().port} ` )         console .log ("回车发送消息:" );         process.stdin .on ('data' , data  =>             let  sendData = data.toString ().trim ()             console .log (`${localInfo} : ${sendData} ` );             server.send (sendData, clientPort, clientAddr);         })     })     server.on ("connect" , (res ) =>  {         console .log ("connect:" , res);     })     server.on ("message" , (msg, rinfo ) =>  {         console .log (`[${rinfo.address} :${rinfo.port} ]: ${msg.toString()} ` )         let  message = msg.toString ()              })     server.on ("close" , (res ) =>  {         console .log ("close" , res);     })     server.on ("error" , (err ) =>  {         console .log ("error" , err);     }) } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 const  dgram = require ('dgram' )const  server = dgram.createSocket ('udp4' )let  first = "" let  second = "" let  alive = {}server.bind (9001 ) server.on ("listening" , () =>  {     console .log (`start to listening: ${server.address().address} :${server.address().port} ` ) }) server.on ("connect" , (res ) =>  {     console .log ("connect:" , res); }) server.on ("message" , (message, rinfo ) =>  {     let  msg = message.toString ()     console .log (`[${rinfo.address} :${rinfo.port} ]:${msg.toString()} ` )     let  count = Object .keys (alive).length      if  (count == 0 ) {         first = {             addr : rinfo.address ,             port : rinfo.port          }         server.send (`{"res": "wait"}` , rinfo.port , rinfo.address )     }     else  if (count == 1 ){         second = {             addr : rinfo.address ,             port : rinfo.port          }         let  res = {             first,             msg : "toServer"          }         server.send (JSON .stringify (res), rinfo.port , rinfo.address )     }     alive[`${rinfo.address} :${rinfo.port} ` ] = {         addr : rinfo.address ,         port : rinfo.port      }     console .log (alive)     if  (msg == "ready" ) {         let  res = {             second,             msg : "toClient"          }         server.send (JSON .stringify (res), first.port , first.addr )         alive = {}     }           }) server.on ("close" , (res ) =>  {     console .log ("close" , res); }) server.on ("error" , (err ) =>  {     console .log ("error" , err); }) 
这是个小的 demo,分别对应 client.js 和 server.jsclient.js,S 部署 server.js 即可
用的时候先在 S 启动 server.jsclient.js 即可,连接成功后server.js停掉,A B仍能正常通信,则试验成功
三、TCP打洞 
TCP打洞,打洞的原理其实还是那个原理,就是在NAT上留下记录。
TCP的过程就不细说了,但是TCP打洞会比UDP打洞困难得多 (因为我也没成功) 
主要是因为,TCP和UDP不同:
UDP是无链接,同一个端口既可以发送 同时可以监听接收。同一个端口,可以给很多人发送,也可以接收很多人发来的信息
但TCP是套接字,自己的 IP:PORT 是与对方的 IP:PORT 是绑定的,期间端口处于被占用的状态,只能发送或者是收到response回来的信息,不能直接从 发送状态 改为 监听状态,需要先进行断开 或者 设置为端口复用
但在端口复用的过程中,NAT可能会因此而改变端口,其原因可能是因为处于 TIME_WAIT 状态,又或是像 NAT4 一样处理新的TCP 连接
所以 此时想要通过TCP打洞,建立可靠的TCP连接,这时候就要搬出 TCP 状态迁移图  了同时打开  的效果
所以我想到的一个方法就是:
当然啦,如果这里的 S 是公网IP,则 A 向 S 建立的连接都可以连上这里主要是想实现的是 S 向 A 主动建立起的连接 
四、结语 最后再说两句吧… 后来才知道,原来这就是叫 p2p…..好吧是我浅薄了
然后关于UDP打洞的部分,我给我的小伙伴讲,给他讲完了之后
然后TCP部分,我确实没能在两个大NAT网上打通,所以我也好奇和请教一下大佬们,有什么好的解决方案实现TCP打洞和建立连接的呢
或者像酷狗、迅雷这种利用客户端的,又是怎么实现的呢
如果可以的话,能不能有个 node.js 的 demo,C语言真的好难顶…
另外:NAT打洞  同步更新一篇吧
完
参考文章