自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

多進(jìn)程可以監(jiān)聽同一端口嗎

安全 應(yīng)用安全
算法雖然我們看不懂,但通過其注釋我們可以知道,它返回的值的區(qū)間是[0, ep_ro),再結(jié)合上面的reuseport_select_sock方法我們可以確定,返回的就是所有l(wèi)isten socket的數(shù)組下標(biāo)索引。

[[376523]]

當(dāng)然可以,只要你使用 SO_REUSEPORT 這個(gè)參數(shù)。

還是先來看下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. 

從文檔中可以看到,該參數(shù)允許多個(gè)socket綁定到同一本地地址,即使socket是處于listen狀態(tài)的。

當(dāng)多個(gè)listen狀態(tài)的socket綁定到同一地址時(shí),各個(gè)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; 

編譯并執(zhí)行該程序:

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

看下當(dāng)前8888端口的所有socket的狀態(tài):

  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)) 

和我們預(yù)想的一樣,只有一個(gè)socket處于listen狀態(tài)。

我們再執(zhí)行一次該程序:

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

再次查看8888端口socket的狀態(tài):

  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)) 

此時(shí)已經(jīng)出現(xiàn)兩個(gè)socket在監(jiān)聽8888端口(注意它們的ip地址也是一樣的),而這兩個(gè)socket分別屬于兩個(gè)進(jìn)程。

我們現(xiàn)在再用ncat模擬客戶端,連接8888端口:

  1. $ ncat localhost 8888 

重復(fù)該操作,建立n個(gè)到8888端口的tcp連接,此時(shí)兩個(gè)服務(wù)端終端的輸出如下。

服務(wù)端1:

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

服務(wù)端2:

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

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

下面我們來看到對應(yīng)的linux內(nèi)核代碼,看看它是如何實(shí)現(xiàn)的。

  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); 

當(dāng)我們做bind等操作時(shí),就會(huì)調(diào)用這個(gè)方法,參數(shù)snum就是我們要bind的端口。

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

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

該方法先從hinfo->bhash這個(gè)hashmap中找,該端口是否已經(jīng)被bind過,如果沒有,則新創(chuàng)建一個(gè)tb,比如我們第一次listen操作時(shí),該端口就沒有被使用,所以會(huì)新創(chuàng)建一個(gè)tb。

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

當(dāng)我們第二次做listen操作時(shí),又會(huì)進(jìn)入到這個(gè)方法,此時(shí)hinfo->bhash的map中存在相同端口的tb,所以會(huì)goto到tb_found部分。

因?yàn)橹暗膌isten操作會(huì)把其對應(yīng)的socket放入到tb->owners中,所以第二次的listen操作,tb->owners不為empty。

進(jìn)而,邏輯處理會(huì)進(jìn)入到sk_reuseport_match方法,如果此方法返回true,則內(nèi)核會(huì)允許第二次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被設(shè)置為FASTREUSEPORT_ANY,而此次listen操作的socket,又設(shè)置了SO_REUSEPORT參數(shù),即sk->sk_reuseport值大于0,所以,該方法最終返回true。

由上可見,設(shè)置了SO_REUSEPORT參數(shù)之后,第二次listen中的bind操作是沒用問題的,我們再看下對應(yīng)的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方法最終會(huì)調(diào)用上面的方法,在該方法中,sk代表第二次listen操作的socket,sk2代表第一次listen操作的socket。

該方法的大致邏輯為:

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

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

3. 將sk的sk_reuseport_cb字段也指向這個(gè)數(shù)組。

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

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

看下相應(yīng)代碼:

  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方法,計(jì)算被選中的listen socket的索引,最后返回這個(gè)listen socket繼續(xù)處理tcp連接請求。

看下reciprocal_scale方法是如何實(shí)現(xiàn)的:

  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); 

算法雖然我們看不懂,但通過其注釋我們可以知道,它返回的值的區(qū)間是[0, ep_ro),再結(jié)合上面的reuseport_select_sock方法我們可以確定,返回的就是所有l(wèi)isten socket的數(shù)組下標(biāo)索引。

至此,有關(guān)SO_REUSEPORT參數(shù)的內(nèi)容我們就講完了。

上篇文章 socket的SO_REUSEADDR參數(shù)全面分析 中,我們分析了SO_REUSEADDR參數(shù),那這個(gè)參數(shù)和SO_REUSEADDR又有什么區(qū)別呢?

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

其實(shí),該參數(shù)在我上次寫的socks5代理那個(gè)項(xiàng)目就有用到(是的,我又用rust實(shí)現(xiàn)了一版socks5代理),通過使用該參數(shù),我可以開多個(gè)進(jìn)程同時(shí)處理socks5代理請求,現(xiàn)在使用下來的感受是,真的非??欤肎oogle什么的完全不是問題。

好,就到這里吧。

本文轉(zhuǎn)載自微信公眾號「卯時(shí)卯刻」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系卯時(shí)卯刻公眾號。

 

責(zé)任編輯:武曉燕 來源: 卯時(shí)卯刻
相關(guān)推薦

2012-11-21 20:11:12

交換機(jī)MACIP

2022-07-26 00:00:02

TCPUDPMAC

2024-03-05 10:07:22

TCPUDP協(xié)議

2020-11-10 07:13:44

端口號進(jìn)程

2017-06-30 10:12:46

Python多進(jìn)程

2010-07-15 12:51:17

Perl多進(jìn)程

2025-03-20 08:40:00

TCPUDP端口

2024-03-29 06:44:55

Python多進(jìn)程模塊工具

2016-01-11 10:29:36

Docker容器容器技術(shù)

2021-10-12 09:52:30

Webpack 前端多進(jìn)程打包

2012-08-08 09:32:26

C++多進(jìn)程并發(fā)框架

2020-11-17 10:50:37

Python

2024-08-26 08:39:26

PHP孤兒進(jìn)程僵尸進(jìn)程

2024-03-18 08:21:06

TCPUDP協(xié)議

2019-02-26 11:15:25

進(jìn)程多線程多進(jìn)程

2022-03-09 17:01:32

Python多線程多進(jìn)程

2009-04-21 09:12:45

Java多進(jìn)程運(yùn)行

2021-02-25 11:19:37

谷歌Android開發(fā)者

2010-07-22 12:48:49

Telnet 1433

2020-11-18 09:06:04

Python
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號