在寫完object 672後,軟體的乙個致命問題暴露出來,如果伺服器和客戶端都在內網環境下,即雙方都通過nat來接觸外網,那麼此時客戶端是無法直接和伺服器交流的。
解決方案可以是:
1:把伺服器部署在不存在nat的公網環境下。
2:使用常見的nat穿透方法比如udp打洞,或者stun協議,但是這些方法都需要另乙個已知的部署在公網環境下的伺服器。
3:就是這篇文章主要討論的方案,即不需要部署任何公網環境下的伺服器,通過路由器支援的upnp協議來把內網的介面繫結到公網介面上。
upnp的一大優勢就是不會像udp打洞那樣,內網介面不需要先向外部介面傳送udp包來把繫結的公網介面告訴nat,而且對於對稱nat,udp打洞是無效的。而upnp一旦設定成功後,內網介面完全以繫結的公網介面暴露在公網中。
演示程式的執行是這樣的:
具體過程:
1. 輸出使用者host name和內網ip位址。在.net環境下使用windows的upnp元件需要現在工程中引用:natupnp 1.0 type library,這是乙個com類庫。2. 通過upnp把內網ip位址,內部埠號繫結到乙個外部埠號上。
4. 在內網中建立tcp socket伺服器。
5. 建立另乙個tcp socket客戶端,然後嘗試連線上面獲取的公網ip和upnp繫結的外部埠。
6. 如果一切沒有問題的話,此時會成功連線到伺服器,並收到回應!
下面開始逐句分析源**,源**均擬使用者已加入下列命名空間:
using
system
.net;
using
system
.net
.sockets;
using
system
.text
.regularexpressions;
//提取ip時的正則
using
system
.threading
.tasks;
//task
using
system
.io;
//讀取伺服器資訊用到streamreader
using
natupnplib;
//windows upnp com元件
首先輸出本機(也就是內網介面資訊),這個很簡單了:
//獲取host name
varname
=dns
.gethostname();
console
.writeline(
"使用者:"
+name);
//從當前host name解析ip位址,篩選ipv4位址是本機的內網ip位址。
varipv4
=dns
.gethostentry(name)
.addresslist
.where(i
=>i.
addressfamily
==addressfamily
.internetwork)
.firstordefault();
console
.writeline(
"內網ip:"
+ipv4);
**如下:
console
.writeline(
"設定upnp");
//upnp繫結資訊
vareport
=8733;
variport
=8733;
vardescription
="mgen測試";
//建立com型別
varupnpnat
=new
upnpnat
();
var=upnpnat.
//錯誤判斷
if==null)
//新增之前的ipv4變數(內網ip),內部埠,和外部埠 .
add(eport,
"tcp"
, iport, ipv4
.tostring(),
true
, description);
console
.writeline(
"外部埠:"
, eport);
console
.writeline(
"內部埠:"
, iport);
如果成功後,你應該可以在路由器的upnp選項中看到這些資料:
設定好upnp後,開始獲取外網ip位址,可以通過這個**(此時只需要傳送乙個http get請求,然後把返回的html中的ip位址提取出來就可以了,我們用正則來提取ip位址。
**如下:
//外網ip變數
string
eip;
//正則
varregex
=@"\b\d\.\d\.\d\.\d\b";
using
(var
webclient
=new
webclient
())
console
.writeline(
"外網ip:"
+eip);
ok,這個時候(如果一切順利的話),一切準備工作都做好了。我們有了:內網ip,內部埠,外網ip,外部埠。那麼就可以做乙個tcp連線做測試了。
直接建立乙個tcp服務端,代表在nat下的伺服器,注意埠號要繫結到upnp設定時的內部埠。
**://在nat下的伺服器
varsocket
=new
socket
(addressfamily
.internetwork,
sockettype
.stream,
protocoltype
.tcp);
//繫結內網ip和內部埠
socket
.bind(
newipendpoint
(ipv4, iport));
socket
.listen(1);
//在另乙個執行緒中執行客戶端socket
task
.run(()
=>
);//成功連線
varclient
=socket
.accept();
//伺服器向客戶端傳送資訊
client
.send(
encoding
.unicode
.getbytes(
"=== 歡迎來到mgen的伺服器!==="
+environment
.newline));
console
.readkey(
false);
上面的clientsocket方法就是客戶端的socket連線執行,注意tcp協議是不保留資料邊界的,因此伺服器在傳送訊息時,後面加了個換行符(environment.newline),然後在客戶端接受資料時,使用socket –> networkstream –> streamreader的巢狀組合,最後由streamreader的readline讀取資料,這樣確保會讀到最後的換行符。
clientsocket方法的執行**:
//ip引數和port引數是公網的ip位址,和upnp中的外部埠
static
void
clientsocket(
string
ip,
intport)
}catch
(exception
ex)
}ok。
nat內網穿透 相關研究
就是乙個socket 同時進行listen和 connect 是否可行。答案是可以的。而且此socket還需要bind同乙個本機ip port,同時,需要進行設定 setsockopt listenfd,sol socket,so reuseaddr,void value,sizeof value ...
利用frp來建立內網穿透
將包放到外網的伺服器 以下稱為服務端 和內網的伺服器 以下稱為客戶端 並解壓 服務端 配置檔案 frps.ini common bind port 7000 啟動 nohup frps c frps.ini 客戶端配置檔案 frpc.ini common server addr 填寫服務端ip se...
內網穿透NatApp使用
2 註冊並登入 註冊成功之後,登入如下圖 然後需要進行實名認證和繫結郵箱 3 購買隧道 登入成功之後,需要購買通道,可以選擇免費的通道,或者付費的,根據自己的需要 4 購買成功之後,配置隧道 5 解壓軟體,進行操作 對應隧道的authtoken authtoken 6fdb788dce71d7e 對...