LocalSocket作為安卓提供的一種IPC機制,可能應(yīng)用層的同學比較陌生,我實際也是在這段時間做項目使用到才注意到它并去了解的。不過實際上framework層里面被頻繁使用到了,例如我很久前寫的博客從源碼看安卓應(yīng)用的啟動過程里面提到其他進程和Zygote進程之間的通信使用的是LocalSocket。
那么LocalSocket和Socket到底有什么不同呢?官方文檔里面其實提到了它其實是基于UNIX-domain socket的:
Non-standard class for creating an inbound UNIX-domain socket in the Linux abstract namespace.
Socket本來是用來做不同主機間的網(wǎng)絡(luò)通信的,如果有人想拿來做本機的IPC通信就會發(fā)現(xiàn)它的性能堪憂(例如實現(xiàn)binder機制做不到的傳輸大文件),因為它需要走網(wǎng)絡(luò)協(xié)議棧、打包拆包、計算校驗等,如果是TCP還需要走三次握手和應(yīng)答。
于是后面就發(fā)展出了UNIX-domain socket (LocalSocket),它的api和socket的基本一致,但是本質(zhì)上只是一種IPC通信,不可和外部主機通信,但是因為IPC通信是可靠通信,直接將數(shù)據(jù)拷貝到目標進程內(nèi)存即可,所以沒有之前說的那些耗時的操作。
使用方式
我們先來看看它的使用方式:
private void demoClient() throws IOException {
LocalSocket client = new LocalSocket();
client.connect(new LocalSocketAddress("me.linjw.localsocket"));
client.getOutputStream().write(123);
int read = client.getInputStream().read();
Log.d(TAG, "response from server : " + read);
client.close();
}
private void demoServer() throws IOException {
LocalServerSocket server = new LocalServerSocket("me.linjw.localsocket");
LocalSocket client = server.accept();
int read = client.getInputStream().read();
Log.d(TAG, "request from client :" + read);
client.getOutputStream().write(read + 1);
client.getOutputStream().flush();
client.close();
}
// 打印
// request from client :123
// response from server : 124
沒錯,看起來和普通Socket的用法很類似了。
性能
性能是評判一種ipc進制好壞的重要指標,例如我們常用的Binder機制就是用了mmap機制實現(xiàn)了數(shù)據(jù)的一次拷貝提高了傳輸速度。
于是我寫了一個測試程序來對比AIDL、LocalSocket和TCP Socket的傳輸速度。測試的邏輯大概是:
- 每次傳輸讀或者寫1024 byte數(shù)據(jù)
- 計算3000次讀或者寫的耗時(也就是計算讀3000k或者寫3000k數(shù)據(jù)的總耗時)
- LocalSocket和TCP Socket每次傳輸完數(shù)據(jù)都斷開連接,下次需要重新連接
在我們的產(chǎn)品設(shè)備上得到的實際數(shù)據(jù)如下:
| 方式 | 方向 | 第一次3000k | 第二次3000k | 第三次3000k | 第四次3000k | 平均時間 |
|---|---|---|---|---|---|---|
| AIDL | 讀 | 1.711s | 1.195s | 1.25s | 1.169s | 1.33125s |
| LocalSocket | 讀 | 1.674s | 1.286s | 1.185s | 1.219s | 1.341s |
| TCP Socket | 讀 | 10.188s | 8.926s | 8.865s | 8.803s | 9.1955s |
| AIDL | 寫 | 1.261s | 1.212s | 1.175s | 1.23s | 1.2195s |
| LocalSocket | 寫 | 1.387s | 1.323s | 1.23s | 1.35s | 1.3225s |
| TCP Socket | 寫 | 8.284s | 8.242s | 8.324s | 8.285s | 8.28375s |
從上面的數(shù)據(jù)可以看出來LocalSocket雖然會比AIDL慢但是也差的不多,而tcp的耗時就比較多了。雖然我沒有具體看過LocalSocket的底層原理,但是想來既然它在framework層被頻繁使用,那么谷歌應(yīng)該也應(yīng)該會考慮到性能這一點。
優(yōu)缺點
優(yōu)點:
- 可以進行數(shù)據(jù)流讀寫,沒有大小限制
- 比TCP Socket會更加安全,因為不能通過抓包監(jiān)聽傳輸?shù)臄?shù)據(jù)
- 不會開啟線程池(Zygote之所以使用它而不是Binder也是因為Binder機制默認會啟動線程池,而fork在多線程下只會fork出當前線程)
缺點:
- 比Binder的速度還是會稍微慢那么一點點
- 沒有像AIDL這樣的高層封裝,需要自己實現(xiàn)
- 和TCP Socket對比起來不能跨主機通信
需要注意的地方
- 雖然不是真正的網(wǎng)絡(luò)傳輸,但是也需要聲明android.permission.INTERNET權(quán)限,要不然同樣會報java.net.SocketException: Permission denied異常
- 雖然可以通過LocalSocket和framework層直接通信,但是如果系統(tǒng)打開了SeLinux就會出現(xiàn)Permission denied異常
- 在模擬器上LocalSocket的flush用多了耗時有時候會比較嚴重(Tcp沒有問題,實機測試LocalSocket也沒有出現(xiàn)問題,猜測和系統(tǒng)相關(guān))