<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          這種本機網(wǎng)絡(luò) IO 方法,性能可以翻倍!

          共 4854字,需瀏覽 10分鐘

           ·

          2022-02-26 15:20

          轉(zhuǎn)載自張彥非allen

          大家好,我是飛哥!

          很多讀者在看完《127.0.0.1 之本機網(wǎng)絡(luò)通信過程知多少 ?》這一篇后,讓我講講 Unix Domain Socket。好了,今天就安排!

          在本機網(wǎng)絡(luò) IO 中,我們講到過基于普通 socket 的本機網(wǎng)絡(luò)通信過程中,其實在內(nèi)核工作流上并沒有節(jié)約太多的開銷。該走的系統(tǒng)調(diào)用、協(xié)議棧、鄰居系統(tǒng)、設(shè)備驅(qū)動(雖然說對于本機網(wǎng)絡(luò) loopback 設(shè)備來說只是一個軟件虛擬的東東)全都走了一遍。其工作過程如下圖

          那么我們今天來看另外一種本機網(wǎng)絡(luò) IO 通信方式 -- Unix Domain Socket。看看這種方式在性能開銷上和基于 127.0.0.1 的本機網(wǎng)絡(luò) IO 有沒有啥差異呢。

          本文中,我們將分析 Unix Domain Socket 的內(nèi)部工作原理。你將理解為什么這種方式的性能比 127.0.0.1 要好很多。最后我們還給出了實際的性能測試對比數(shù)據(jù)。

          相信你已經(jīng)迫不及待了,別著急,讓我們一一展開細說!

          一、使用方法

          Unix Domain Socket(后面統(tǒng)一簡稱 UDS) 使用起來和傳統(tǒng)的 socket 非常的相似。區(qū)別點主要有兩個地方需要關(guān)注。

          第一,在創(chuàng)建 socket 的時候,普通的 socket 第一個參數(shù) family 為 AF_INET, 而 UDS 指定為 AF_UNIX 即可。

          第二,Server 的標識不再是 ip 和 端口,而是一個路徑,例如 /dev/shm/fpm-cgi.sock。

          其實在平時我們使用 UDS 并不一定需要去寫一段代碼,很多應(yīng)用程序都支持在本機網(wǎng)絡(luò) IO 的時候配置。例如在 Nginx 中,如果要訪問的本機 fastcgi 服務(wù)是以 UDS 方式提供服務(wù)的話,只需要在配置文件中配置這么一行就搞定了。

          fastcgi_pass?unix:/dev/shm/fpm-cgi.sock;

          如果 對于一個 UDS 的 server 來說,它的代碼示例大概結(jié)構(gòu)如下,大家簡單了解一下。只是個示例不一定可運行。

          int?main()
          {
          ?//?創(chuàng)建?unix?domain?socket
          ?int?fd?=?socket(AF_UNIX,?SOCK_STREAM,?0);

          ?//?綁定監(jiān)聽
          ?char?*socket_path?=?"./server.sock";
          ?strcpy(serun.sun_path,?socket_path);?
          ?bind(fd,?serun,?...);
          ?listen(fd,?128);

          ?while(1){
          ??//接收新連接
          ??conn?=?accept(fd,?...);

          ??//收發(fā)數(shù)據(jù)
          ??read(conn,?...);
          ??write(conn,?...);
          ?}
          }

          基于 UDS 的 client 也是和普通 socket 使用方式差不太多,創(chuàng)建一個 socket,然后 connect 即可。

          int?main(){
          ?sock?=?socket(AF_UNIX,?SOCK_STREAM,?0);
          ?connect(sockfd,?...)
          }

          二、連接過程

          總的來說,基于 UDS 的連接過程比 inet 的 socket 連接過程要簡單多了。客戶端先創(chuàng)建一個自己用的 socket,然后調(diào)用 connect 來和服務(wù)器建立連接。

          在 connect 的時候,會申請一個新 socket 給 server 端將來使用,和自己的 socket 建立好連接關(guān)系以后,就放到服務(wù)器正在監(jiān)聽的 socket 的接收隊列中。這個時候,服務(wù)器端通過 accept 就能獲取到和客戶端配好對的新 socket 了。

          總的 UDS 的連接建立流程如下圖。

          內(nèi)核源碼中最重要的邏輯在 connect 函數(shù)中,我們來簡單展開看一下。unix 協(xié)議族中定義了這類 socket 的所有方法,它位于 net/unix/af_unix.c 中。

          //file:?net/unix/af_unix.c
          static?const?struct?proto_ops?unix_stream_ops?=?{
          ?.family?=?PF_UNIX,
          ?.owner?=?THIS_MODULE,
          ?.bind?=??unix_bind,
          ?.connect?=?unix_stream_connect,
          ?.socketpair?=?unix_socketpair,
          ?.listen?=?unix_listen,
          ?...
          };

          我們找到 connect 函數(shù)的具體實現(xiàn),unix_stream_connect。

          //file:?net/unix/af_unix.c
          static?int?unix_stream_connect(struct?socket?*sock,?struct?sockaddr?*uaddr,
          ??????????int?addr_len,?int?flags)

          {
          ?struct?sockaddr_un?*sunaddr?=?(struct?sockaddr_un?*)uaddr;

          ?...

          ?//?1.?為服務(wù)器側(cè)申請一個新的?socket?對象
          ?newsk?=?unix_create1(sock_net(sk),?NULL);

          ?//?2.?申請一個?skb,并關(guān)聯(lián)上?newsk
          ?skb?=?sock_wmalloc(newsk,?1,?0,?GFP_KERNEL);
          ?...

          ?//?3.?建立兩個?sock?對象之間的連接
          ?unix_peer(newsk)?=?sk;
          ?newsk->sk_state??=?TCP_ESTABLISHED;
          ?newsk->sk_type??=?sk->sk_type;
          ?...
          ?sk->sk_state?=?TCP_ESTABLISHED;
          ?unix_peer(sk)?=?newsk;

          ?//?4.?把連接中的一頭(新?socket)放到服務(wù)器接收隊列中
          ?__skb_queue_tail(&other->sk_receive_queue,?skb);
          }

          主要的連接操作都是在這個函數(shù)中完成的。和我們平常所見的 TCP 連接建立過程,這個連接過程簡直是太簡單了。沒有三次握手,也沒有全連接隊列、半連接隊列,更沒有啥超時重傳。

          直接就是將兩個 socket 結(jié)構(gòu)體中的指針互相指向?qū)Ψ骄托辛?。就?unix_peer(newsk) = sk 和?unix_peer(sk)?= newsk?這兩句。

          //file:?net/unix/af_unix.c
          #define?unix_peer(sk)?(unix_sk(sk)->peer)

          當關(guān)聯(lián)關(guān)系建立好之后,通過 __skb_queue_tail 將 skb 放到服務(wù)器的接收隊列中。注意這里的 skb 里保存著新 socket 的指針,因為服務(wù)進程通過 accept 取出這個 skb 的時候,就能獲取到和客戶進程中 socket 建立好連接關(guān)系的另一個 socket。

          怎么樣,UDS 的連接建立過程是不是很簡單!?

          三、發(fā)送過程

          看完了連接建立過程,我們再來看看基于 UDS 的數(shù)據(jù)的收發(fā)。這個收發(fā)過程一樣也是非常的簡單。發(fā)送方是直接將數(shù)據(jù)寫到接收方的接收隊列里的。

          我們從 send 函數(shù)來看起。send 系統(tǒng)調(diào)用的源碼位于文件 net/socket.c 中。在這個系統(tǒng)調(diào)用里,內(nèi)部其實真正使用的是 sendto 系統(tǒng)調(diào)用。它只干了兩件簡單的事情,

          第一是在內(nèi)核中把真正的 socket 找出來,在這個對象里記錄著各種協(xié)議棧的函數(shù)地址。第二是構(gòu)造一個 struct msghdr 對象,把用戶傳入的數(shù)據(jù),比如 buffer地址、數(shù)據(jù)長度啥的,統(tǒng)統(tǒng)都裝進去. 剩下的事情就交給下一層,協(xié)議棧里的函數(shù) inet_sendmsg 了,其中 inet_sendmsg 函數(shù)的地址是通過 socket 內(nèi)核對象里的 ops 成員找到的。大致流程如圖。

          在進入到協(xié)議棧 inet_sendmsg 以后,內(nèi)核接著會找到 socket 上的具體協(xié)議發(fā)送函數(shù)。對于 Unix Domain Socket 來說,那就是 unix_stream_sendmsg。我們來看一下這個函數(shù)
          //file:
          static?int?unix_stream_sendmsg(struct?kiocb?*kiocb,?struct?socket?*sock,
          ??????????struct?msghdr?*msg,?size_t?len)

          {
          ?//?1.申請一塊緩存區(qū)
          ?skb?=?sock_alloc_send_skb(sk,?size,?msg->msg_flags&MSG_DONTWAIT,
          ??????&err);

          ?//?2.拷貝用戶數(shù)據(jù)到內(nèi)核緩存區(qū)
          ?err?=?memcpy_fromiovec(skb_put(skb,?size),?msg->msg_iov,?size);

          ?//?3.?查找socket?peer
          ?struct?sock?*other?=?NULL;
          ?other?=?unix_peer(sk);

          ?//?4.直接把?skb放到對端的接收隊列中
          ?skb_queue_tail(&other->sk_receive_queue,?skb);

          ?//?5.發(fā)送完畢回調(diào)
          ?other->sk_data_ready(other,?size);
          }

          和復(fù)雜的 TCP 發(fā)送接收過程相比,這里的發(fā)送邏輯簡單簡單到令人發(fā)指。申請一塊內(nèi)存(skb),把數(shù)據(jù)拷貝進去。根據(jù) socket 對象找到另一端,直接把 skb 給放到對端的接收隊列里了

          接收函數(shù)主題是 unix_stream_recvmsg,這個函數(shù)中只需要訪問它自己的接收隊列就行了,源碼就不展示了。所以在本機網(wǎng)絡(luò) IO 場景里,基于 Unix Domain Socket 的服務(wù)性能上肯定要好一些的。

          四、性能對比

          為了驗證 Unix Domain Socket 到底比基于 127.0.0.1 的性能好多少,我做了一個性能測試。在網(wǎng)絡(luò)性能對比測試,最重要的兩個指標是延遲和吞吐。我從 Github 上找了個好用的測試源碼:https://github.com/rigtorp/ipc-bench。我的測試環(huán)境是一臺 4 核 CPU,8G 內(nèi)存的 KVM 虛機。

          在延遲指標上,對比結(jié)果如下圖。

          可見在小包(100 字節(jié))的情況下,UDS 方法的“網(wǎng)絡(luò)” IO 平均延遲只有 2707 納秒,而基于 TCP(訪問 127.0.0.1)的方式下延遲高達 5690 納秒。耗時整整是前者的兩倍。

          在包體達到 100 KB 以后,UDS 方法延遲 24 微秒左右(1 微秒等于 1000 納秒),TCP 是 32 微秒,仍然高一截。這里低于 2 倍的關(guān)系了,是因為當包足夠大的時候,網(wǎng)絡(luò)協(xié)議棧上的開銷就顯得沒那么明顯了。

          再來看看吞吐效果對比。

          在小包的情況下,帶寬指標可以達到 854 M,而基于 TCP 的 IO 方式下只有 386 M。數(shù)據(jù)就解讀到這里。

          五、總結(jié)

          本文分析了基于 Unix Domain Socket 的連接創(chuàng)建、以及數(shù)據(jù)收發(fā)過程。其中數(shù)據(jù)收發(fā)的工作過程如下圖。

          相對比本機網(wǎng)絡(luò) IO 通信過程上,它的工作過程要清爽許多。其中 127.0.0.1 工作過程如下圖。

          我們也對比了 UDP 和 TCP 兩種方式下的延遲和性能指標。在包體不大于 1KB 的時候,UDS 的性能大約是 TCP 的兩倍多。所以,在本機網(wǎng)絡(luò) IO 的場景下,如果對性能敏感,飛哥建議你使用 Unix Domain Socket。



          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜日B网 | 国内精品偷拍 | 日本人日逼视频 | 最好看的2019中文大全在线观看 | 欧美女操逼视频 |