<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在具體分析之前,我們先看下socket(7)的man文檔對這個參數是怎么介紹的: ~~~ SO_REUSEADDR Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses.For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address. When the listening socket is bound to INADDR_ANY with a spe‐ cific port then it is not possible to bind to this port for any local address.Argument is an integer boolean flag. ~~~ ### 從這段文檔中我們可以知道三個事: 1. 使用這個參數后,bind操作是可以重復使用local address的,注意,這里說的是local address,即ip加端口組成的本地地址,也就是說,兩個本地地址,如果有任意ip或端口部分不一樣,它們本身就是可以共存的,不需要使用這個參數。 2. 當local address被一個處于listen狀態的socket使用時,加上該參數也不能重用這個地址。 3. 當處于listen狀態的socket監聽的本地地址的ip部分是INADDR\_ANY,即表示監聽本地的所有ip,即使使用這個參數,也不能再bind包含這個端口的任意本地地址,這個和 2 中描述的其實是一樣的。 好,接下來我們看幾個例子。 上文 1 中說,只要本地地址不一樣(ip或端口不一樣),即使沒有這個參數,兩個地址也是可以同時使用的,我們來看下是不是這樣。 下面是客戶端的測試代碼: ~~~ #include <arpa/inet.h> #include <assert.h> #include <netinet/in.h> #include <stdio.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> static int tcp_connect(char *ip, int port) { int sfd, err; struct sockaddr_in addr; sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(sfd != -1); // 先bind本地地址 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr)); assert(!err); // 再連接目標服務器 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(7777); err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr)); assert(!err); return sfd; } int main(int argc, char *argv[]) { // bind本地地址:127.0.0.1:8888 tcp_connect("127.0.0.1", 8888); // bind本地地址:192.168.3.187:8888 tcp_connect("192.168.3.187", 8888); printf("兩個連接同時建立成功\n"); sleep(100); return 0; } ~~~ 該段代碼中,會先綁定本地地址,再連接目標服務器,由上可見,兩次連接bind的本地地址中,ip部分是不同的,所以這兩個bind操作應該是成功的。 我們用以下ncat命令模擬服務端: ~~~ $?ncat?-lk4?7777 ~~~ 用ss命令查看有關7777端口的所有socket狀態: ~~~ $?ss?-antp?|?grep?7777 LISTEN 0 10 0.0.0.0:7777 0.0.0.0:\* users:(("ncat",pid=19208,fd=3)) ~~~ 由上可見,此時只有ncat服務端在監聽7777端口,沒有任何其他連接。 我們執行上面的程序,然后再次查看7777端口所有socket狀態: ~~~ $?ss?-antp?|?grep?7777 LISTEN 0 10 0.0.0.0:7777 0.0.0.0 : *users : (("ncat", pid = 19208, fd = 3)) ESTAB 0 0 127.0.0.1 : 7777 192.168.3.187 : 8888 users : (("ncat", pid = 19208, fd = 5)) ESTAB 0 0 127.0.0.1 : 7777 127.0.0.1 : 8888 users : (("ncat", pid = 19208, fd = 4)) ESTAB 0 0 192.168.3.187 : 8888 127.0.0.1 : 7777 users : (("a.out", pid = 19340, fd = 4)) ESTAB 0 0 127.0.0.1 : 8888 127.0.0.1 : 7777 users : (("a.out", pid = 19340, fd = 3)) ~~~ 由上可以看到,這兩個連接的確是建立成功了。 上面命令輸出中,有4個ESTAB狀態的連接,這是正常的,因為這分別是從服務端角度和客戶端的角度得到的輸出。 前三行是從服務器角度來看的,后兩行是從客戶端角度來看的,這個從后面的進程名也可以看出。 對客戶端來說,在connect之前可以bind不同本地地址,然后連同一目標,對服務端來說也是可以的,在listen之前,完全可以bind不同的本地地址,不需要SO\_REUSEADDR參數也可以成功,由于程序代碼差不多,這里我們就不演示了。 我們下面再來看下connect之前,bind相同地址的情況,下面是測試代碼: ~~~ #include <arpa/inet.h> #include <assert.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> static int tcp_connect(char *ip, int port) { int sfd, err; char buf[1024]; struct sockaddr_in addr; sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(sfd != -1); // 先bind本地地址 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(8888); err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr)); if (err) { sprintf(buf, "bind(%s:%d)", ip, port); perror(buf); exit(-1); } // 再連接目標服務器 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr)); assert(!err); return sfd; } int main(int argc, char *argv[]) { // 連接的目標地址:127.0.0.1:7777 tcp_connect("127.0.0.1", 7777); // 連接的目標地址:127.0.0.1:7778 tcp_connect("127.0.0.1", 7778); printf("兩個連接同時建立成功\n"); sleep(100); return 0; } ~~~ 該程序會在connect之前,bind本地地址到127.0.0.1:8888,然后再連接目標地址,兩次目標地址分別是127.0.0.1:7777和127.0.0.1:7778。 還是用ncat模擬服務端,只是這次要開兩個。 服務端7777: ~~~ $ ncat -lk4 7777 ~~~ 服務端7778: ~~~ $?ncat?-lk4?7778 ~~~ 運行客戶端代碼: ~~~ $?gcc?client.c?&&?./a.out bind(127.0.0.1:7778): Address already in use ~~~ 由上可見,第二次連接是失敗了的,因為127.0.0.1:8888本地地址已經被第一次connect使用過了。 此時,加上SO\_REUSEADDR參數應該是可以解決這個問題的。 ~~~ #include <arpa/inet.h> #include <assert.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> static int tcp_connect(char *ip, int port) { int sfd, opt, err; char buf[1024]; struct sockaddr_in addr; sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(sfd != -1); // 先設置SO_REUSEADDR opt = 1; err = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); assert(!err); // 再bind本地地址 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(8888); err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr)); if (err) { sprintf(buf, "bind(%s:%d)", ip, port); perror(buf); exit(-1); } // 然后連接目標服務器 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr)); assert(!err); return sfd; } int main(int argc, char *argv[]) { // 連接的目標地址:127.0.0.1:7777 tcp_connect("127.0.0.1", 7777); // 連接的目標地址:127.0.0.1:7778 tcp_connect("127.0.0.1", 7778); printf("兩個連接同時建立成功\n"); sleep(100); return 0; } ~~~ 再次編譯后執行: ~~~ $ gcc client.c && ./a.out 兩個連接同時建立成功 ~~~ 由上可以看到,這兩次連接都成功了,SO\_REUSEADDR允許我們重復bind相同的本地地址。 **細心的同學可能會發現,為什么兩次連接的目標地址是不同的呢?** 我們來把它改成相同的試下: ~~~ #include <arpa/inet.h> #include <assert.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> static int tcp_connect(char *ip, int port) { int sfd, opt, err; char buf[1024]; struct sockaddr_in addr; sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(sfd != -1); // 先設置SO_REUSEADDR opt = 1; err = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); assert(!err); // 再bind本地地址 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(8888); err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr)); if (err) { sprintf(buf, "bind(%s:%d)", ip, port); perror(buf); exit(-1); } // 然后連接目標服務器 bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr)); if (err) { sprintf(buf, "connect(%s:%d)", ip, port); perror(buf); exit(-1); } return sfd; } int main(int argc, char *argv[]) { // 連接的目標地址:127.0.0.1:7777 tcp_connect("127.0.0.1", 7777); // 連接的目標地址:127.0.0.1:7777 tcp_connect("127.0.0.1", 7777); printf("兩個連接同時建立成功\n"); sleep(100); return 0; } ~~~ 此時,執行該程序,命令行會有如下輸出: ~~~ $?gcc?client.c?&&?./a.out connect(127.0.0.1:7777): Cannot assign requested address ~~~ 為什么呢?因為這兩次連接都是從127.0.0.1:8888 到 127.0.0.1:7777的,**這個在tcp層面是不允許的,即使加了SO\_REUSEADDR參數也不行。** 本地地址和目標地址組成的元組唯一確定一個tcp連接,上面程中的兩次連接本地地址和目標地址都一樣,已經違背了唯一的原則。 對應內核相應檢查代碼如下: ~~~ // net/ipv4/inet_hashtables.c static int __inet_check_established(struct inet_timewait_death_row *death_row, struct sock *sk, __u16 lport, struct inet_timewait_sock **twp) { struct inet_hashinfo *hinfo = death_row->hashinfo; struct inet_sock *inet = inet_sk(sk); __be32 daddr = inet->inet_rcv_saddr; __be32 saddr = inet->inet_daddr; ... const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport); unsigned int hash = inet_ehashfn(net, daddr, lport, saddr, inet->inet_dport); struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash); ... struct sock *sk2; ... sk_nulls_for_each(sk2, node, &head->chain) { ... if (likely(INET_MATCH(sk2, net, acookie, saddr, daddr, ports, dif, sdif))) { ... goto not_unique; } } ... not_unique: ... return -EADDRNOTAVAIL; } ~~~ 如果本地地址和目標地址組成的元組之前已經存在了,則返回錯誤碼EADDRNOTAVAIL,這個錯誤碼對應的解釋為: ~~~ // include/uapi/asm-generic/errno.h #define EADDRNOTAVAIL 99 /\* Cannot assign requested address \*/ ~~~ 正好和上面執行程序輸出的錯誤信息一樣。 我們再回到對SO\_REUSEADDR參數的討論。 上面代碼中,兩個connect使用相同的本地地址,只要加上SO\_REUSEADDR參數是可以的,那兩個listen行嗎? **看代碼: ** ~~~ #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> static int tcp_listen(char *ip, int port) { int lfd, opt, err; char buf[1024]; struct sockaddr_in addr; lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(lfd != -1); opt = 1; err = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); assert(!err); bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); err = bind(lfd, (struct sockaddr *)&addr, sizeof(addr)); if (err) { sprintf(buf, "bind(%s:%d)", ip, port); perror(buf); exit(-1); } err = listen(lfd, 8); assert(!err); return lfd; } int main(int argc, char *argv[]) { tcp_listen("127.0.0.1", 7777); tcp_listen("127.0.0.1", 7777); return 0; } ~~~ 該代碼執行之后輸出如下: ~~~ $ gcc server.c && ./a.out bind(127.0.0.1:7777): Address already in use ~~~ 由上可見,即使加上SO\_REUSEADDR參數,兩個listen也是不行的。 其實,這個在最開始的man文檔中已經說過了,只要有listen占了一個本地地址,其他任何操作都不能再使用這個地址了。 我們對應看下內核源碼: ~~~ // net/ipv4/inet_connection_sock.c static int inet_csk_bind_conflict(const struct sock *sk, const struct inet_bind_bucket *tb, bool relax, bool reuseport_ok) { struct sock *sk2; bool reuse = sk->sk_reuse; ... sk_for_each_bound(sk2, &tb->owners) { if (sk != sk2 && ...) { if ((!reuse || !sk2->sk_reuse || sk2->sk_state == TCP_LISTEN) && ...) { if (inet_rcv_saddr_equal(sk, sk2, true)) break; } ... } } return sk2 != NULL; } ~~~ 該方法就是用來判斷本地地址是否可以重復使用的代碼。 如果該方法最終sk2不為null,則最終會返回錯誤碼EADDRINUSE給用戶,即我們上面程序執行之后的錯誤輸出。 我們來看下sk2什么時候不為null。 在我們的新socket和sk2本地地址相同時,如果新socket沒有設置SO\_REUSEADDR參數,或者sk2沒設置SO\_REUSEADDR參數,或者sk2為listen狀態,sk2最終都會不為null,也就是說,新socket的本地地址在這些情況下都不可重復使用。 和man文檔中說的基本是一樣的。 那我們在平時寫服務器時,為什么要加上這個參數呢?我們都是先關閉服務器,再開的啊,以前那個listen的socket,以及所有當時正在連接的socket,應該都已經關閉了啊?應該不會存在相同的本地地址了啊? ### 為什么呢? 這要再說起tcp的TIME\_WAIT狀態。 我們知道,在tcp連接中,主動發起關閉請求的那一端會最終進入TIME\_WAIT狀態,被動關閉連接的那一端會直接進入CLOSE狀態,即socket和它占用的資源會直接銷毀。 ***** 假設,在我們關閉服務器之前,先把客戶端都關閉掉,再關閉服務器,此時服務器的所有socket都直接進入CLOSE狀態了,它們占用的本地地址等也都立即可用,此時如果我們馬上開服務器,是不會出現 Address already in use 這個錯誤的。 但當我們在有客戶端連接的情況下,直接關閉服務器,也就是說,對所有現有的tcp連接,服務端都主動發起了關閉請求,此時,這些連接就會進入TIME\_WAIT狀態,一直占用服務器使用的本地地址,不讓后續操作使用。 這種情況下,你再開服務器,就會出現上面那個?Address already?in?use 錯誤,這也是我們寫服務器時經常會遇到的錯誤。 ***** **解決這個問題的方法就是設置SO\_REUSEADDR參數。** 由上面的inet\_csk\_bind\_conflict方法可以看到,如果設置了SO\_REUSEADDR參數,新socket和舊socket的reuse值都會為true,而舊socket此時處于TIME\_WAIT狀態,所以后續不會調用inet\_rcv\_saddr\_equal方法,判斷兩個地址是否相同。 這樣最終sk2也會為null,也就是說,內核允許新socket使用這個地址。 用代碼驗證下: ~~~ #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> static int tcp_listen(char *ip, int port) { int lfd, opt, err; char buf[1024]; struct sockaddr_in addr; lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(lfd != -1); opt = 1; err = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); assert(!err); bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); err = bind(lfd, (struct sockaddr *)&addr, sizeof(addr)); if (err) { sprintf(buf, "bind(%s:%d)", ip, port); perror(buf); exit(-1); } err = listen(lfd, 8); assert(!err); return lfd; } int main(int argc, char *argv[]) { int lfd, cfd; lfd = tcp_listen("127.0.0.1", 7777); printf("5秒鐘之后將關閉第一次listen的socket,請于此期間發起一次tcp連接\n"); sleep(5); cfd = accept(lfd, NULL, NULL); assert(cfd != -1); close(cfd); close(lfd); tcp_listen("127.0.0.1", 7777); printf("第二次listen操作成功\n"); return 0; } ~~~ 按照程序提示,對服務端發起tcp連接,最終服務端輸出如下: ~~~ $ gcc server.c && ./a.out 5秒鐘之后將關閉第一次listen的socket,請于此期間發起一次tcp連接 ~~~ 可見,有了SO\_REUSEADDR參數,即使我們先關閉的tcp連接,也是可以再次listen的。 有興趣的朋友可以把設置SO\_REUSEADDR參數的代碼去掉,然后再執行看下,理論上來說是會報錯的。 到此為止,所有有關SO\_REUSEADDR參數內容都講完了,希望對大家有所幫助。 完。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看