Linux socket跨局域网聊天和文件传输
一直想寫一個(gè)跨局域網(wǎng)聊天和文件傳輸,以及視頻聊天的軟件,這兩天剛好閑著沒啥事就把代碼寫完了,代碼已經(jīng)上傳至github:https://github.com/vinllen/chat
其實(shí)之前想法P2P模式,P2P的話必須穿透NAT,現(xiàn)在的NAT有4種模式:
- 1.Full cone NAT,亦即著名的一對(duì)一(one-to-one)NAT
一旦一個(gè)內(nèi)部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發(fā)自iAddr:port1的包都經(jīng)由eAddr:port2向外發(fā)送。任意外部主機(jī)都能通過給eAddr:port2發(fā)包到達(dá)iAddr:port1
- 2.Address-Restricted cone NAT
一旦一個(gè)內(nèi)部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發(fā)自iAddr:port1的包都經(jīng)由eAddr:port2向外發(fā)送。任意外部主機(jī)(hostAddr:any)都能通過給eAddr:port2發(fā)包到達(dá)iAddr:port1的前提是:iAddr:port1之前發(fā)送過包到hostAddr:any. "any"也就是說端口不受限制
- 3.Port-Restricted cone NAT
一旦一個(gè)內(nèi)部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發(fā)自iAddr:port1的包都經(jīng)由eAddr:port2向外發(fā)送。一個(gè)外部主機(jī)(hostAddr:port3)能夠發(fā)包到達(dá)iAddr:port1的前提是:iAddr:port1之前發(fā)送過包到hostAddr:port3.
- 4.Symmetric NAT(對(duì)稱NAT)
只有曾經(jīng)收到過內(nèi)部主機(jī)封包的外部主機(jī),才能夠把封包發(fā)回
對(duì)于第1種特別簡(jiǎn)單,因?yàn)槎丝诖嬖谟成?#xff0c;只要把包發(fā)網(wǎng)出口路由的端口即可,路由會(huì)幫你轉(zhuǎn)發(fā)
對(duì)于第2,3種情況,可以采用如下辦法(內(nèi)容來自該博客:點(diǎn)擊打開鏈接):
假設(shè)網(wǎng)絡(luò)模型如下:
限制性錐NAT 和端口限制性錐NAT (簡(jiǎn)稱限制性NAT ),穿透限制性錐NAT 會(huì)丟棄它未知的源地址發(fā)向內(nèi)部主機(jī)的數(shù)據(jù)包。所以如果現(xiàn)在ClientA-1 直接發(fā)送UDP 數(shù)據(jù)包到ClientB-1 ,那么數(shù)據(jù)包將會(huì)被NAT-B 無情的丟棄。所以采用下面的方法來建立ClientA-1 和ClientB-1 之間的通信。
- 1 .ClientA-1 (202.103.142.29:5000 )發(fā)送數(shù)據(jù)包給Server ,請(qǐng)求和ClientB-1 (221.10.145.84:6000 )通信。
- 2. Server 將ClientA-1 的地址和端口(202.103.142.29:5000 )發(fā)送給ClientB-1 ,告訴ClientB-1 ,ClientA-1 想和它通信。
- 3. ClientB-1 向ClientA-1 (202.103.142.29:5000 )發(fā)送UDP 數(shù)據(jù)包,當(dāng)然這個(gè)包在到達(dá)NAT-A 的時(shí)候,還是會(huì)被丟棄,這并不是關(guān)鍵的,因?yàn)榘l(fā)送這個(gè)UDP 包只是為了讓NAT-B 記住這次通信的目的地址:端口號(hào),當(dāng)下次以這個(gè)地址和端口為源的數(shù)據(jù)到達(dá)的時(shí)候就不會(huì)被NAT-B 丟棄,這樣就在NAT-B 上打了一個(gè)從ClientB-1 到ClientA-1 的孔。
- 4. 為了讓ClientA-1 知道什么時(shí)候才可以向ClientB-1 發(fā)送數(shù)據(jù),所以ClientB-1 在向ClientA-1 (202.103.142.29:5000 )打孔之后還要向Server 發(fā)送一個(gè)消息,告訴Server 它已經(jīng)準(zhǔn)備好了。
- 5. Server 發(fā)送一個(gè)消息給ClientA-1 ,內(nèi)容為:ClientB-1 已經(jīng)準(zhǔn)備好了,你可以向ClientB-1 發(fā)送消息了。
- 6. ClientA-1 向ClientB-1 發(fā)送UDP 數(shù)據(jù)包。這個(gè)數(shù)據(jù)包不會(huì)被NAT-B 丟棄,以后ClientB-1 向ClientA-1 發(fā)送的數(shù)據(jù)包也不會(huì)被ClientA-1 丟棄,因?yàn)镹AT-A 已經(jīng)知道是ClientA-1 首先發(fā)起的通信。至此,ClientA-1 和ClientB-1 就可以進(jìn)行通信了。
1.同時(shí)開放TCP ( Simultaneous TCP open )策略
如果一個(gè) 對(duì)稱 NAT 接收到一個(gè)來自 本地 私有網(wǎng) 絡(luò) 外面的 TCP SYN 包, 這 個(gè)包想 發(fā) 起一個(gè) “ 引入” 的 TCP 連 接,一般來 說 , NAT 會(huì)拒 絕這 個(gè) 連 接 請(qǐng) 求并扔掉 這 個(gè) SYN 包,或者回送一個(gè)TCP RST (connection reset ,重建 連 接)包 給請(qǐng) 求方。但是,有一 種 情況 卻會(huì)接受這個(gè)“引入”連接。
RFC 規(guī)定:對(duì)于對(duì)稱NAT , 當(dāng) 這 個(gè)接收到的 SYN 包中的源IP 地址 : 端口、目 標(biāo) IP 地址 : 端口都與NAT 登 記 的一個(gè)已 經(jīng) 激活的 TCP 會(huì) 話 中的地址信息相符 時(shí) , NAT 將會(huì)放行 這 個(gè) SYN 包。 需要 特 別 指出 的是:怎樣才是一個(gè)已經(jīng)激活的TCP 連接?除了真正已經(jīng)建立完成的TCP 連接外,RFC 規(guī)范指出: 如果 NAT 恰好看到一個(gè) 剛剛發(fā) 送出去的一個(gè) SYN 包和 隨之 接收到的SYN 包中的地址 :端口 信息相符合的 話 ,那 么 NAT 將會(huì) 認(rèn)為這 個(gè) TCP 連 接已 經(jīng) 被激活,并將允 許這 個(gè)方向的 SYN 包 進(jìn) 入 NAT 內(nèi)部。 同時(shí)開放TCP 策略就是利用這個(gè)時(shí)機(jī)來建立連接的。
如果 Client A -1 和 Client B -1 能 夠 彼此正確的 預(yù) 知 對(duì) 方的 NAT 將會(huì) 給 下一個(gè) TCP 連 接分配的公網(wǎng) TCP 端口,并且兩個(gè)客 戶 端能 夠 同 時(shí) 地 發(fā) 起一 個(gè)面向?qū)Ψ降?“ 外出 ” 的 TCP 連 接 請(qǐng)求 ,并在 對(duì) 方的 SYN 包到達(dá)之前,自己 剛發(fā) 送出去的 SYN 包都能 順 利的穿 過 自己的 NAT 的 話 ,一條端 對(duì) 端的 TCP 連 接就 能 成功地建立了 。
2.UDP 端口猜測(cè)策略
同時(shí)開放TCP 策略非常依賴于猜測(cè)對(duì)方的下一個(gè)端口,而且強(qiáng)烈依賴于發(fā)送連接請(qǐng)求的時(shí)機(jī),而且還有網(wǎng)絡(luò)的不確定性,所以能夠建立的機(jī)會(huì)很小,即使Server 充當(dāng)同步時(shí)鐘的角色。下面是一種通過UDP 穿透的方法,由于UDP 不需要建立連接,所以也就不需要考慮“同時(shí)開放”的問題。
為了介紹ClientB-1 的詭計(jì),先介紹一下STUN 協(xié)議。STUN (Simple Traversal of UDP Through NATs )協(xié)議是一個(gè)輕量級(jí)協(xié)議,用來探測(cè)被NAT 映射后的地址:端口。STUN 采用C/S 結(jié)構(gòu),需要探測(cè)自己被NAT 轉(zhuǎn)換后的地址:端口的Client 向Server 發(fā)送請(qǐng)求,Server 返回Client 轉(zhuǎn)換后的地址:端口。
參考4.2 節(jié)中穿透NAT 的步驟2 ,當(dāng)ClientB-1 收到Server 發(fā)送給它的消息后,ClientB-1 即打開3 個(gè)socket 。socket-0 向STUN Server 發(fā)送請(qǐng)求,收到回復(fù)后,假設(shè)得知它被轉(zhuǎn)換后的地址:端口( 221.10.145.84:600 5 ),socket-1 向ClientA-1 發(fā)送一個(gè)UDP 包,socket-2 再次向另一個(gè)STUN Server 發(fā)送請(qǐng)求,假設(shè)得到它被轉(zhuǎn)換后的地址:端口( 221.10.145.84:60 20 )。通常,對(duì)稱NAT 分配端口有兩種策略,一種是按順序增加,一種是隨機(jī)分配。如果這里對(duì)稱NAT 使用順序增加策略,那么,ClientB-1 將兩次收到的地址:端口發(fā)送給Server 后,Server 就可以通知ClientA-1 在這個(gè)端口范圍內(nèi)猜測(cè)剛才ClientB-1 發(fā)送給它的socket-1 中被NAT 映射后的地址:端口,ClientA-1 很有可能在孔有效期內(nèi)成功猜測(cè)到端口號(hào),從而和ClientB-1 成功通信。
/************************************華麗分割線**********************************************************/ 以上內(nèi)容大部分參考維基百科和chengweiv5的博客,我自己也寫了代碼,嘗試用第二種方式去穿透我們學(xué)校所在的NAT,結(jié)果悲劇了,找了好幾天的問題也沒找出來到底是代碼的原因呢還是NAT限制為第4種的原因呢。 But項(xiàng)目不能這么擱淺了。我采用了另外一條曲線救國(guó)的路線,就跟現(xiàn)在QQ模式一樣,用中轉(zhuǎn)服務(wù)器進(jìn)行消息轉(zhuǎn)發(fā)實(shí)現(xiàn)。 1.首先搞了一臺(tái)具有公網(wǎng)ip的服務(wù)器 2.配置好環(huán)境后(比如iptables等),把服務(wù)器代碼放置服務(wù)器,運(yùn)行 3.客戶端連接后就可以發(fā)送文件和聊天了 That's all,就這么簡(jiǎn)單。 唯一需要說明的就是發(fā)送文件時(shí)我調(diào)用的是sendfile的系統(tǒng)調(diào)用,接收文件調(diào)用了Pat Patterson的包裹函數(shù):利用splice接收文件,理論上這兩個(gè)函數(shù)是直接在內(nèi)核態(tài)進(jìn)行文件傳遞,而不用在內(nèi)核態(tài)和普通態(tài)進(jìn)行數(shù)據(jù)的拷貝,減少時(shí)間。我測(cè)試了一下,對(duì)幾M大小的文件都沒問題,不過貌似文件大了貌似傳不動(dòng)了,目測(cè)sendfile有大小限制。。。這個(gè)就之后再改改了。 最后一點(diǎn),本來還要實(shí)現(xiàn)視頻傳輸?shù)?#xff0c;我的想法就是用opencv調(diào)用本地?cái)z像頭,然后把圖片按每秒固定幀數(shù)截下來,按文件方法傳輸,再在對(duì)端對(duì)圖片進(jìn)行拼接成視頻。后來感覺有點(diǎn)麻煩,最后上網(wǎng)一查,果然我做的部分就是重造輪子的活,現(xiàn)在直接都有現(xiàn)成的文件傳輸協(xié)議。這部分也等以后有時(shí)間再去完善吧。 Finally,?如果大家有誰有想法,可以和我交流交流,一起學(xué)習(xí)進(jìn)步
總結(jié)
以上是生活随笔為你收集整理的Linux socket跨局域网聊天和文件传输的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 已经出狱的李一男和即将出狱的王欣,还能赶
- 下一篇: 尚硅谷韩顺平Linux教程学习笔记