成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

多進程可以監聽同一端口嗎

安全 應用安全
算法雖然我們看不懂,但通過其注釋我們可以知道,它返回的值的區間是[0, ep_ro),再結合上面的reuseport_select_sock方法我們可以確定,返回的就是所有listen socket的數組下標索引。

[[376523]]

當然可以,只要你使用 SO_REUSEPORT 這個參數。

還是先來看下man文檔中是怎么說的:

  1. SO_REUSEPORT (since Linux 3.9) 
  2.       Permits multiple AF_INET or AF_INET6 sockets to be bound to an 
  3.       identical socket address.  This option must be set on each 
  4.       socket (including the first socket) prior to calling bind(2) 
  5.       on the socket.  To prevent port hijacking, all of the pro‐ 
  6.       cesses binding to the same address must have the same effec‐ 
  7.       tive UID.  This option can be employed with both TCP and UDP 
  8.       sockets. 
  9.  
  10.       For TCP sockets, this option allows accept(2) load distribu‐ 
  11.       tion in a multi-threaded server to be improved by using a dis‐ 
  12.       tinct listener socket for each thread.  This provides improved 
  13.       load distribution as compared to traditional techniques such 
  14.       using a single accept(2)ing thread that distributes connec‐ 
  15.       tions, or having multiple threads that compete to accept(2) 
  16.       from the same socket. 
  17.  
  18.       For UDP sockets, the use of this option can provide better 
  19.       distribution of incoming datagrams to multiple processes (or 
  20.       threads) as compared to the traditional technique of having 
  21.       multiple processes compete to receive datagrams on the same 
  22.       socket. 

從文檔中可以看到,該參數允許多個socket綁定到同一本地地址,即使socket是處于listen狀態的。

當多個listen狀態的socket綁定到同一地址時,各個socket的accept操作都能接受到新的tcp連接。

很神奇對吧,寫段代碼測試下:

  1. #include <arpa/inet.h> 
  2. #include <assert.h> 
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. #include <strings.h> 
  6. #include <sys/socket.h> 
  7. #include <sys/types.h> 
  8. #include <unistd.h> 
  9.  
  10. static int tcp_listen(char *ip, int port) { 
  11.   int lfd, opt, err; 
  12.   struct sockaddr_in addr; 
  13.  
  14.   lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
  15.   assert(lfd != -1); 
  16.  
  17.   opt = 1; 
  18.   err = setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); 
  19.   assert(!err); 
  20.  
  21.   bzero(&addr, sizeof(addr)); 
  22.   addr.sin_family = AF_INET; 
  23.   addr.sin_addr.s_addr = inet_addr(ip); 
  24.   addr.sin_port = htons(port); 
  25.  
  26.   err = bind(lfd, (struct sockaddr *)&addr, sizeof(addr)); 
  27.   assert(!err); 
  28.  
  29.   err = listen(lfd, 8); 
  30.   assert(!err); 
  31.  
  32.   return lfd; 
  33.  
  34. int main(int argc, char *argv[]) { 
  35.   int lfd, sfd; 
  36.  
  37.   lfd = tcp_listen("127.0.0.1", 8888); 
  38.   while (1) { 
  39.     sfd = accept(lfd, NULLNULL); 
  40.     close(sfd); 
  41.     printf("接收到tcp連接:%d\n", sfd); 
  42.   } 
  43.  
  44.   return 0; 

編譯并執行該程序:

  1. $ gcc server.c && ./a.out 

看下當前8888端口的所有socket的狀態:

  1. $ ss -antp | grep 8888 
  2. LISTEN       0        8              127.0.0.1:8888              0.0.0.0:*       users:(("a.out",pid=32505,fd=3)) 

和我們預想的一樣,只有一個socket處于listen狀態。

我們再執行一次該程序:

  1. $ gcc server.c && ./a.out 

再次查看8888端口socket的狀態:

  1. $ ss -antp | grep 8888 
  2. LISTEN     0        8               127.0.0.1:8888               0.0.0.0:*       users:(("a.out",pid=32607,fd=3)) 
  3. LISTEN     0        8               127.0.0.1:8888               0.0.0.0:*       users:(("a.out",pid=32505,fd=3)) 

此時已經出現兩個socket在監聽8888端口(注意它們的ip地址也是一樣的),而這兩個socket分別屬于兩個進程。

我們現在再用ncat模擬客戶端,連接8888端口:

  1. $ ncat localhost 8888 

重復該操作,建立n個到8888端口的tcp連接,此時兩個服務端終端的輸出如下。

服務端1:

  1. $ gcc server.c && ./a.out 
  2. 接收到tcp連接:4 
  3. 接收到tcp連接:4 
  4. 接收到tcp連接:4 

服務端2:

  1. $ gcc server.c && ./a.out 
  2. 接收到tcp連接:4 
  3. 接收到tcp連接:4 

可以看到,tcp連接基本上算是均勻分布到兩個服務器上,神奇。

下面我們來看到對應的linux內核代碼,看看它是如何實現的。

  1. // net/ipv4/inet_connection_sock.c 
  2. int inet_csk_get_port(struct sock *sk, unsigned short snum) 
  3.         ... 
  4.         struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo; 
  5.         int ret = 1, port = snum; 
  6.         struct inet_bind_hashbucket *head; 
  7.         ... 
  8.         struct inet_bind_bucket *tb = NULL
  9.         ... 
  10.         head = &hinfo->bhash[inet_bhashfn(net, port, 
  11.                                           hinfo->bhash_size)]; 
  12.         ... 
  13.         inet_bind_bucket_for_each(tb, &head->chain) 
  14.                 if (net_eq(ib_net(tb), net) && tb->l3mdev == l3mdev && 
  15.                     tb->port == port) 
  16.                         goto tb_found; 
  17. tb_not_found: 
  18.         tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, 
  19.                                      net, head, port, l3mdev); 
  20.         ... 
  21. tb_found: 
  22.         if (!hlist_empty(&tb->owners)) { 
  23.                 ... 
  24.                 if (... || sk_reuseport_match(tb, sk)) 
  25.                         goto success; 
  26.                 ... 
  27.         } 
  28. success: 
  29.         if (hlist_empty(&tb->owners)) { 
  30.                 ... 
  31.                 if (sk->sk_reuseport) { 
  32.                         tb->fastreuseport = FASTREUSEPORT_ANY; 
  33.                         ... 
  34.                 } else { 
  35.                         tb->fastreuseport = 0; 
  36.                 } 
  37.         } else { 
  38.                 ... 
  39.         } 
  40.         ... 
  41. EXPORT_SYMBOL_GPL(inet_csk_get_port); 

當我們做bind等操作時,就會調用這個方法,參數snum就是我們要bind的端口。

該方法中,類型struct inet_bind_bucket代表端口bind的具體信息,比如:哪個socket在bind這個端口。

hinfo->bhash是用于存放struct inet_bind_bucket實例的hashmap。

該方法先從hinfo->bhash這個hashmap中找,該端口是否已經被bind過,如果沒有,則新創建一個tb,比如我們第一次listen操作時,該端口就沒有被使用,所以會新創建一個tb。

新創建的tb,它的tb->owners是empty,此時,如果我們設置了SO_REUSEPORT參數,那sk->sk_reuseport字段值就會大于0,也就是說,第一次listen操作之后,tb->fastreuseport的值被設置為FASTREUSEPORT_ANY(大于0)。

當我們第二次做listen操作時,又會進入到這個方法,此時hinfo->bhash的map中存在相同端口的tb,所以會goto到tb_found部分。

因為之前的listen操作會把其對應的socket放入到tb->owners中,所以第二次的listen操作,tb->owners不為empty。

進而,邏輯處理會進入到sk_reuseport_match方法,如果此方法返回true,則內核會允許第二次listen操作使用該本地地址。

我們看下sk_reuseport_match方法:

  1. // net/ipv4/inet_connection_sock.c 
  2. static inline int sk_reuseport_match(struct inet_bind_bucket *tb, 
  3.                                      struct sock *sk) 
  4.         ... 
  5.         if (tb->fastreuseport <= 0) 
  6.                 return 0; 
  7.         if (!sk->sk_reuseport) 
  8.                 return 0; 
  9.         ... 
  10.         if (tb->fastreuseport == FASTREUSEPORT_ANY) 
  11.                 return 1; 
  12.         ... 

由于上一次listen操作,tb->fastreuseport被設置為FASTREUSEPORT_ANY,而此次listen操作的socket,又設置了SO_REUSEPORT參數,即sk->sk_reuseport值大于0,所以,該方法最終返回true。

由上可見,設置了SO_REUSEPORT參數之后,第二次listen中的bind操作是沒用問題的,我們再看下對應的listen操作:

  1. // net/core/sock_reuseport.c 
  2. int reuseport_add_sock(struct sock *sk, struct sock *sk2, bool bind_inany) 
  3.         struct sock_reuseport *old_reuse, *reuse; 
  4.         ... 
  5.         reuse = rcu_dereference_protected(sk2->sk_reuseport_cb, 
  6.                                           lockdep_is_held(&reuseport_lock)); 
  7.         ... 
  8.         reuse->socks[reuse->num_socks] = sk; 
  9.         ... 
  10.         reuse->num_socks++; 
  11.         rcu_assign_pointer(sk->sk_reuseport_cb, reuse); 
  12.         ... 
  13. EXPORT_SYMBOL(reuseport_add_sock); 

listen方法最終會調用上面的方法,在該方法中,sk代表第二次listen操作的socket,sk2代表第一次listen操作的socket。

該方法的大致邏輯為:

1. 將sk2->sk_reuseport_cb字段值賦值給reuse。

2. 將sk放入到reuse->socks字段代表的數組中。

3. 將sk的sk_reuseport_cb字段也指向這個數組。

也就是說,該方法會將所有第二次及其以后的listen操作的socket放入到reuse->socks字段代表的數組中(第一次listen操作的socket在創建struct sock_reuseport實例時就已經被放入到該數組中了),同時,將所有listen的socket的sk->sk_reuseport_cb字段,都指向reuse,這樣,我們就可以通過listen的socket的sk_reuseport_cb字段,拿到struct sock_reuseport實例,進而可以拿到所有其他的listen同一端口的socket。

到現在為止,reuseport是如何實現的基本就明朗了,當有新的tcp連接來時,只要我們找到監聽該端口的一個listen的socket,就等于拿到了所有設置了SO_REUSEPORT參數,并監聽同樣端口的其他socket,我們只需隨機挑一個socket,然后讓它完成之后的tcp連接建立過程,這樣我們就可以實現tcp連接均勻負載到這些listen socket上了。

看下相應代碼:

  1. // net/core/sock_reuseport.c 
  2. struct sock *reuseport_select_sock(struct sock *sk, 
  3.                                    u32 hash, 
  4.                                    struct sk_buff *skb, 
  5.                                    int hdr_len) 
  6.         struct sock_reuseport *reuse; 
  7.         ... 
  8.         struct sock *sk2 = NULL
  9.         u16 socks; 
  10.         ... 
  11.         reuse = rcu_dereference(sk->sk_reuseport_cb); 
  12.         ... 
  13.         socks = READ_ONCE(reuse->num_socks); 
  14.         if (likely(socks)) { 
  15.                 ... 
  16.                 if (!sk2) 
  17.                         sk2 = reuse->socks[reciprocal_scale(hash, socks)]; 
  18.         } 
  19.         ... 
  20.         return sk2; 
  21. EXPORT_SYMBOL(reuseport_select_sock); 

看到了吧,該方法中,最后使用了reciprocal_scale方法,計算被選中的listen socket的索引,最后返回這個listen socket繼續處理tcp連接請求。

看下reciprocal_scale方法是如何實現的:

  1. // include/linux/kernel.h 
  2. /** 
  3.  * reciprocal_scale - "scale" a value into range [0, ep_ro) 
  4.  * ... 
  5.  */ 
  6. static inline u32 reciprocal_scale(u32 val, u32 ep_ro) 
  7.         return (u32)(((u64) val * ep_ro) >> 32); 

算法雖然我們看不懂,但通過其注釋我們可以知道,它返回的值的區間是[0, ep_ro),再結合上面的reuseport_select_sock方法我們可以確定,返回的就是所有listen socket的數組下標索引。

至此,有關SO_REUSEPORT參數的內容我們就講完了。

上篇文章 socket的SO_REUSEADDR參數全面分析 中,我們分析了SO_REUSEADDR參數,那這個參數和SO_REUSEADDR又有什么區別呢?

SO_REUSEPORT參數是SO_REUSEADDR參數的超集,兩個參數目的都是為了重復使用本地地址,但SO_REUSEADDR不允許處于listen狀態的地址重復使用,而SO_REUSEPORT允許,同時,SO_REUSEPORT參數還會把新來的tcp連接負載均衡到各個listen socket上,為我們tcp服務器編程,提供了一種新的模式。

其實,該參數在我上次寫的socks5代理那個項目就有用到(是的,我又用rust實現了一版socks5代理),通過使用該參數,我可以開多個進程同時處理socks5代理請求,現在使用下來的感受是,真的非常快,用Google什么的完全不是問題。

好,就到這里吧。

本文轉載自微信公眾號「卯時卯刻」,可以通過以下二維碼關注。轉載本文請聯系卯時卯刻公眾號。

 

責任編輯:武曉燕 來源: 卯時卯刻
相關推薦

2022-07-26 00:00:02

TCPUDPMAC

2012-11-21 20:11:12

交換機MACIP

2020-11-10 07:13:44

端口號進程

2024-03-05 10:07:22

TCPUDP協議

2017-06-30 10:12:46

Python多進程

2025-03-20 08:40:00

TCPUDP端口

2010-07-15 12:51:17

Perl多進程

2012-08-08 09:32:26

C++多進程并發框架

2024-03-29 06:44:55

Python多進程模塊工具

2021-10-12 09:52:30

Webpack 前端多進程打包

2016-01-11 10:29:36

Docker容器容器技術

2020-11-17 10:50:37

Python

2024-03-18 08:21:06

TCPUDP協議

2024-08-26 08:39:26

PHP孤兒進程僵尸進程

2019-02-26 11:15:25

進程多線程多進程

2009-04-21 09:12:45

Java多進程運行

2021-02-25 11:19:37

谷歌Android開發者

2010-07-22 12:48:49

Telnet 1433

2022-03-09 17:01:32

Python多線程多進程

2020-11-18 09:06:04

Python
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: www.国产.com | 亚洲 中文 欧美 日韩 在线观看 | 亚洲欧美精品 | 亚洲福利一区二区 | 日韩乱码在线 | 成人高清在线视频 | 91视频正在播放 | 国产欧美日韩在线 | 色偷偷噜噜噜亚洲男人 | 日韩成人精品视频 | 久久精品久久久久久 | 国产激情视频在线 | 亚洲国产精品久久 | 精品国产一区二区三区久久久蜜月 | 成人午夜网 | 久久久久久久国产精品视频 | 免费国产一区二区 | 欧美国产日韩在线 | 精品国产乱码一区二区三区 | 在线观看亚洲精品 | 成人午夜免费视频 | 韩国精品一区二区三区 | 亚洲在线免费 | 成人精品一区 | 日本成人中文字幕在线观看 | 日日操夜夜摸 | 亚洲中午字幕 | 亚洲成人久久久 | 亚洲成av人片在线观看 | 天堂在线www| 亚洲97| 国产视频黄色 | 国产一二三区免费视频 | 亚洲成人精品影院 | 激情一区二区三区 | 欧美一级二级视频 | 日韩免费毛片视频 | 国产精品日韩一区二区 | 久久精品一区二区 | 黄久久久 | 亚洲国产成人精品久久久国产成人一区 |