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

多進(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 這個參數(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ù)允許多個socket綁定到同一本地地址,即使socket是處于listen狀態(tài)的。

當(dāng)多個listen狀態(tài)的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; 

編譯并執(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ù)想的一樣,只有一個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)) 

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

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

  1. $ ncat localhost 8888 

重復(fù)該操作,建立n個到8888端口的tcp連接,此時兩個服務(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連接基本上算是均勻分布到兩個服務(wù)器上,神奇。

下面我們來看到對應(yīng)的linux內(nèi)核代碼,看看它是如何實現(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等操作時,就會調(diào)用這個方法,參數(shù)snum就是我們要bind的端口。

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

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

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

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

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

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

進(jìn)而,邏輯處理會進(jìn)入到sk_reuseport_match方法,如果此方法返回true,則內(nèi)核會允許第二次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方法最終會調(diào)用上面的方法,在該方法中,sk代表第二次listen操作的socket,sk2代表第一次listen操作的socket。

該方法的大致邏輯為:

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

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

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

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

到現(xiàn)在為止,reuseport是如何實現(xiàn)的基本就明朗了,當(dāng)有新的tcp連接來時,只要我們找到監(jiān)聽該端口的一個listen的socket,就等于拿到了所有設(shè)置了SO_REUSEPORT參數(shù),并監(jiān)聽同樣端口的其他socket,我們只需隨機(jī)挑一個socket,然后讓它完成之后的tcp連接建立過程,這樣我們就可以實現(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方法,計算被選中的listen socket的索引,最后返回這個listen socket繼續(xù)處理tcp連接請求。

看下reciprocal_scale方法是如何實現(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ù),那這個參數(shù)和SO_REUSEADDR又有什么區(qū)別呢?

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

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

好,就到這里吧。

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

 

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

2022-07-26 00:00:02

TCPUDPMAC

2012-11-21 20:11:12

交換機(jī)MACIP

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

2025-03-20 08:40:00

TCPUDP端口

2010-07-15 12:51:17

Perl多進(jìn)程

2012-08-08 09:32:26

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

2016-01-11 10:29:36

Docker容器容器技術(shù)

2021-10-12 09:52:30

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

2024-03-29 06:44:55

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

2020-11-17 10:50:37

Python

2024-03-18 08:21:06

TCPUDP協(xié)議

2010-07-22 12:48:49

Telnet 1433

2024-08-26 08:39:26

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

2019-02-26 11:15:25

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

2009-04-21 09:12:45

Java多進(jìn)程運行

2021-02-25 11:19:37

谷歌Android開發(fā)者

2022-03-09 17:01:32

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

2020-11-18 09:06:04

Python
點贊
收藏

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

主站蜘蛛池模板: 91在线精品视频 | 国产视频久久 | 国产精品久久久久一区二区 | 成人国产毛片 | 国产精品a久久久久 | 中文字幕免费视频 | 日韩av视屏 | 日本手机看片 | 久久精品国产一区二区电影 | 精国产品一区二区三区 | 久久久婷| 亚洲一区二区 | 亚洲欧美在线视频 | 国产午夜精品视频 | 欧美高清成人 | 999www视频免费观看 | 日韩中文一区二区三区 | 中文字幕乱码一区二区三区 | 中文字幕日韩欧美一区二区三区 | 欧美福利| 婷婷开心激情综合五月天 | 香蕉婷婷 | 91久久北条麻妃一区二区三区 | 精品综合久久久 | 亚洲在线一区二区 | 色欧美综合 | 久日精品 | 欧美大片一区二区 | 久草色视频| 伊人性伊人情综合网 | 欧美精品tv | 免费h在线 | av天空| 日韩精品久久一区二区三区 | 欧美日韩精品一区二区三区四区 | 午夜影院网站 | 欧美日韩91 | 成人一区二区在线 | 一级看片| 日韩av一区二区在线 | 国产免费播放视频 |