首先需要了解 Socket 的一些基本知識, 然后看了一下官方的 API.
這次由于要構(gòu)建一個調(diào)試工具, 先選用的是 robbiehanson/CocoaAsyncSocket 這個框架, 先對而言上手很快, 且使用簡單.
要實(shí)現(xiàn)的需求是要在手機(jī)端搭建一個 Socket 接收端, 同時也需要發(fā)送數(shù)據(jù).
但是第一個坎就沒有過去, 當(dāng)在手機(jī)上(iOS 11.2.2)實(shí)現(xiàn)服務(wù)端時, 無法接收外界傳入的數(shù)據(jù)(使用 UDP 情況下, TCP 下亦然).
為了驗(yàn)證, 故切換為 TCP, 然后用 MAC 實(shí)現(xiàn)一個服務(wù)端, 手機(jī)連接進(jìn)去一切正常, 情況如下:
把服務(wù)端放到 MAC 上, 完全相同的 Socket 服務(wù)端代碼條件下, 指定了如下圖的 APP 功能, 則可以正常連接(TCP):
這里是手機(jī)端的輸出:
嘗試連接
已連接到: 192.168.199.127:9002
這里是 MAC 電腦上的輸出:
已啟動監(jiān)聽
已監(jiān)聽到連接并建立連接.
newSocket's localHost: 192.168.199.127
newSocket's connectedHost: 192.168.199.116
如果上面的勾勾不打的話, 會提示綁定端口錯誤.
但是在 iOS 工程上, 真沒有找到和這個勾勾類似的東西...
再去參考一下 swisspol/GCDWebServer, 為什么它可以讓服務(wù)器在手機(jī)上運(yùn)行起來呢, 而且可以指定端口? 證明并非是 IP 的關(guān)系. 所以這里就進(jìn)入到它的源碼里面去看看.
發(fā)現(xiàn)兩個的實(shí)現(xiàn)原理都不一樣, GCDWebServer 是基于 bonjour 的.
莫非是 iOS 直接就禁止了監(jiān)聽外界的 Socket 請求? 找了網(wǎng)上很久也沒找到可以在 iOS 上構(gòu)建 Socket 服務(wù)端到底可行否? 且完全沒有找到任何地方有在 iOS 上建立 Socket 服務(wù)端的案例, 所以最好的辦法還是從頭開始仔細(xì)看看官方文檔...
好吧, 網(wǎng)上有說用 multi peer connectivity framework 來實(shí)現(xiàn)的, 下面就來看看. 有時候一條路走不通, 就換種思路繼續(xù)做唄...
先看了一下這篇文章, 里面說到它是Bonjour 的繼承者, 然后看了一下內(nèi)容, 貌似還是只有蘋果平臺可用...
文末提到 Websocket, 可用嘗試一下...
但最終發(fā)現(xiàn)全部的例子中, 如果用 TCP 的情況下, 都是把手機(jī)作為客戶端的. 如果用 UDP 的話手機(jī)和其他地方就是兩個對等的點(diǎn)了.
故繼續(xù)看官方文檔, 先看 Networking Overview 啟發(fā)一下思路, 然后是 Networking Programming Topics...
參考官文里面的一句:
There are only two APIs that provide the ability to listen for incoming network connections: the Core Foundation socket API and the POSIX (BSD) socket API. Higher-level APIs cannot be used for accepting incoming connections.
即目前只有兩套 API 提供了監(jiān)聽網(wǎng)絡(luò)連接請求的能力, 一套是 Core Foundation socket API, 另外一套是 POSIX 的 Socket API. 高層的 API 無法被用來 accepting 進(jìn)入的連接.
use NSStream for remote connections and CFSocket for listening
使用 NSStream 來建立遠(yuǎn)程連接, 而用 CFSocket 來監(jiān)聽及 accepting 進(jìn)入的連接.(詳見上述Networking Programming Topics 中的 Do Not Use NSSocketPort (OS X) or NSFileHandle for General Socket Communication)
之前直接建立了一個可工作的客戶端, 下面就來跟著官文建立一個可監(jiān)聽外界連接請求的服務(wù)端.
文檔中需要的 <sys/socket.h>
在 swift 4 中默認(rèn)就已經(jīng)引入進(jìn)去了的, 貌似 <netinet/in.h>
也是一樣.
在 swift 官網(wǎng)上有一句說的是:
BSD sockets are a common pain point for Swift interoperability. Swift 3.0 exposes the difficultly in doing this correcly. Fortunately, Quinn “The Eskimo!” has provided these helpful wrappers.
下面提供了一個 Socket 擴(kuò)展, 詳見這個鏈接, 便于使用.
但為何兩個 iPhone 之間的 Socket 通信這么蛋疼? 在蘋果官方論壇找到了這樣一個問答: iOS 10.2 socket issue, 這里說到了兩個 iPhone 之間的 Socket 通信受阻并非是由系統(tǒng)造成的, 可能是由于 wifi 等外部原因造成的(比如本地的 wifi 阻止了 iOS 設(shè)備間的 STA TO STA 通信, STA 即 station, 它相當(dāng)于 wifi 中的客戶端設(shè)備), 在里面推薦了一篇關(guān)于入門文章Wi-Fi Fundamentals.
由于要徹底解決 wifi 導(dǎo)致的通信受阻問題, 就需要首先了解 wifi 中的一些東西, 根據(jù)上面的文章, 慢慢看.
術(shù)語表:
- STA (station) : wifi 中的客戶端設(shè)備
- AP (access point): 指的是運(yùn)行 Wi-Fi 網(wǎng)絡(luò)的硬件設(shè)備. 關(guān)于這里定義的 Wifi 網(wǎng)絡(luò)的詳細(xì)信息在后面會講.
- SSID (Service Set Identifier): It’s the user-visible network identifier string that you see throughout the system.
- BSSID (Basic Service Set Identifier): This defines a single Wi-Fi network at the Wi-Fi level. It’s identified by the MAC address of the AP, something that’s generally not user visible.
wifi 實(shí)現(xiàn)了如下兩類廣播:
- Unicasts
- Broadcasts(Multicasts)
不過最后最主要的還是在文章末尾提到一個檢測 STA--->AP, AP--->STA 的包傳輸跟蹤方法, 詳見Getting a Packet Trace.
這里是一些 MAC 上可用的網(wǎng)絡(luò)調(diào)試工具列表.
先把 iPhone 的整個網(wǎng)絡(luò)棧映射到 MAC 上, 然后通過網(wǎng)絡(luò)調(diào)試工具來查看通信內(nèi)容:
The RVI represents the entire networking stack of the iOS device
具體操作:
- 用 USB 連接手機(jī)和 MAC 電腦.
- 為設(shè)備創(chuàng)建一個 RVI(remote virtual interface, 遠(yuǎn)程虛擬接口, iOS 5 以上版本才有) , 通過 RVI, 可以在 MAC 上使用抓包工具來追蹤 iPhone 上的網(wǎng)絡(luò)通信.
# 使用如下命令可以查看當(dāng)前所有的網(wǎng)絡(luò)接口: ifconfig -l # 這個命令執(zhí)行后顯示如下 lo0 gif0 stf0 en0 en1 p2p0 fw0 ppp0 utun0 # 獲取設(shè)備的 UUID(在 itunes 里就看得到), 然后用如下命令建立 RVI, 命令參數(shù)即UUID: rvictl -s 74bd53c647548234ddcef0ee3abee616005051ed # 此時再用 ifconfig -l 查看的話, 會發(fā)現(xiàn)多了一個網(wǎng)絡(luò)接口, 多出來的 rvi0 就是新建的: ifconfig -l lo0 gif0 stf0 en0 en1 p2p0 fw0 ppp0 utun0 rvi0
- 有了上述的接口名稱 rvi0, 就可以通過抓包工具對該接口進(jìn)行抓包(下面只是演示):
sudo tcpdump -i rvi0 -A
- 如果是想移除 RVI, 則執(zhí)行如下命令:
# 其中參數(shù)值是設(shè)備的 UUID rvictl -x 74bd53c647548234ddcef0ee3abee616005051ed
另外有一個 CPA 工具, 來分析看包到底到了手機(jī)端沒有.
tcpdump
也是個好東西, 可以學(xué)學(xué)它的用法, 具體可以參考這篇官文.
下面的測試使用的是如下命令來執(zhí)行的 tcpdump:
# ip 地址為想要監(jiān)聽的host, 從該 host 發(fā)送的包, 或發(fā)向該 host 的包都會被抓取.
sudo tcpdump host 192.168.199.116 -i rvi1
但在測試的時候有一個問題:
兩臺手機(jī), 一臺 iPhone6P, 一臺 iPhone 6, 然后在 Xcode 中寫了兩個示例工程, 一個工程只是通過
UDP 協(xié)議利用 Socket 發(fā)送數(shù)據(jù), 另外一個工程的作用是接收通過 UDP 協(xié)議發(fā)送的過來的數(shù)據(jù).
- 當(dāng)使用 6 來向 6P 發(fā)送數(shù)據(jù), 無法發(fā)送.
- 當(dāng)使用 6P 來向 6 發(fā)送數(shù)據(jù), 一切正常.
- 使用 tcpdump 查看, 實(shí)際上每次發(fā)送的時候數(shù)據(jù)包都是過去了的(抓取接收方的包是接收到了的, 發(fā)送方也是發(fā)送了的).
數(shù)據(jù)包在兩種情況下都是順利被發(fā)送和接收到了的, 但為什么一個手機(jī)在上層無法接收, 另外一個可以接收呢?
蘋果在 10.2 之后可能對網(wǎng)絡(luò)功能進(jìn)行了修改? 但兩個手機(jī)的系統(tǒng)都是 11.2.2 , 為什么同樣的發(fā)送和接收代碼, 在兩臺 iPhone 上面還會有不同的反應(yīng)?
上面遇到的問題就無法解答了, 望看到的朋友能夠給予幫助, 萬分感謝!