SO_REUSEADDR和SO_REUSEPORT
前言
基本概念:
SO_REUSEADDR套接字選項(xiàng)能起到以下4個不同的功用:
(1)SO_REUSEADDR允許啟動一個監(jiān)聽服務(wù)器并捆綁眾所周知端口,即使以前建立的該端口用作它們的本地端口的連接仍存在。
這個條件通常是這樣碰到的:
a)啟動一個監(jiān)聽服務(wù)器;
b)連接請求到的,派生一個子進(jìn)程來處理這個客戶;
c)監(jiān)聽服務(wù)器終止,但子進(jìn)程繼續(xù)為現(xiàn)有的連接上的客戶提供服務(wù);
d)重啟監(jiān)聽服務(wù)器。
默認(rèn)情況下,當(dāng)監(jiān)聽服務(wù)器在步驟d調(diào)用socket、bind和listen重新啟動時,由于它試圖捆綁一個現(xiàn)有連接上的端口,從而bind調(diào)用會失敗。但是如果該服務(wù)器在socket和bind兩個調(diào)用之間設(shè)置了SO_REUSEADDR套接字選項(xiàng),那么bind將成功。所有TCP服務(wù)器都應(yīng)該指定本套接字選項(xiàng),以允許服務(wù)器在這種情形下被重新啟動。
(2)SO_REUSEADDR允許在同一端口上啟動同一服務(wù)器的多個實(shí)例,只要每個實(shí)例捆綁一個不同的本地IP地址即可。
(3)SO_REUSEADDR允許單個進(jìn)程捆綁同一個端口到多個套接字上,只要每次捆綁指定不同的本地IP地址即可。
(4)SO_REUSEADDR允許完全重復(fù)的捆綁:當(dāng)一個IP地址和端口已綁定到某個套接字上時。如果傳輸協(xié)議支持,同樣的IP地址和端口還可以捆綁到另一個套接字上。一般來說,本特性僅支持UDP套接字。
?
?
SO_REUSEPORT套接字選項(xiàng)能起到以下2個不同的功用:
(1)本選項(xiàng)允許完全重復(fù)的捆綁,不過只有在想要捆綁同一IP地址和端口的每個套接字都指定了本套接字選項(xiàng)才行。
(2)如果被捆綁的IP地址是一個多播地址,那么SO_REUSEADDR和SO_REUSEPORT被認(rèn)為是等效的。
?
這里我們的重點(diǎn)討論內(nèi)容是:
SO_REUSEADDR的第(1)個功用(藍(lán)色字體)
SO_REUSEPORT的第(1)個功用(藍(lán)色字體)
?
應(yīng)用場景:nginx平滑升級就應(yīng)用到了相關(guān)知識。
?
例子1:
我們需要知道,
a) 如果不對TCP的套接字選項(xiàng)進(jìn)行任何限制時,如果啟動兩個進(jìn)程,第二個進(jìn)程就會在調(diào)用bind函數(shù)的時候出錯(Address already in use)。
b) 如果在調(diào)用bind之前我們設(shè)置了SO_REUSEADDR,但是不在第二個進(jìn)程啟動前close這個套接字,那么第二個進(jìn)程仍然會在調(diào)用bind函數(shù)的時候出錯(Address already in use)。
c)如果在調(diào)用bind之前我們設(shè)置了SO_REUSEADDR,并接收了一個客戶端連接,并且在第二個進(jìn)程啟動前關(guān)閉了bind的套接字,這個時候第一個進(jìn)程只擁有一個套接字(與客戶端的連接),那么第二個進(jìn)程則可以bind成功,符合預(yù)期。
代碼:
?
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdlib.h>
void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //TCP
int backlog = 100;
short port = 9527; //端口
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET; //IPv4
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //表示由內(nèi)核去選擇IP地址
servaddr.sin_port = htons(port);
int flag = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
Perror("setsockopt fail");
}
int res = bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));
if (0 == res)
printf("server bind success, 0.0.0.0:%d\n", port);
else {
Perror("bind fail");
}
if (-1 == listen(sockfd, backlog)) {
Perror("listen fail");
}
//等待連接
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int connfd = accept(sockfd, (sockaddr *)&cliaddr, &len);
if (-1 == connfd) {
Perror("accept fail");
}
//解析客戶端地址
char buff[INET_ADDRSTRLEN + 1] = {0};
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);
uint16_t cli_port = ntohs(cliaddr.sin_port);
printf("connection from %s, port %d\n", buff, cli_port);
//關(guān)閉bind的sockfd
close(sockfd);
//
sleep(1200);
return 0;
}
?
?
編譯:
g++ server.cpp -o s1
g++ server.cpp -o s2
運(yùn)行結(jié)果:
?
例子2,
SO_REUSEPORT可能在比較舊的內(nèi)核版本上不支持,我的測試環(huán)境的內(nèi)核版本是3.10,相對SO_REUSEADDR來說,SO_REUSEPORT沒有那么多的限制條件,允許兩個毫無血緣關(guān)系的進(jìn)程使用相同的IP地址同時監(jiān)聽同一個端口,并且不會出現(xiàn)驚群效應(yīng)。
代碼:
?
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdlib.h>
void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
int main()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //TCP
int backlog = 100;
short port = 9527; //端口
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET; //IPv4
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //表示由內(nèi)核去選擇IP地址
servaddr.sin_port = htons(port);
int flag = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag))) {
Perror("setsockopt fail");
}
int res = bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));
if (0 == res)
printf("server bind success, 0.0.0.0:%d\n", port);
else {
Perror("bind fail");
}
if (-1 == listen(sockfd, backlog)) {
Perror("listen fail");
}
//等待連接
while (1) {
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int connfd = accept(sockfd, (sockaddr *)&cliaddr, &len);
if (-1 == connfd) {
Perror("accept fail");
}
//解析客戶端地址
char buff[INET_ADDRSTRLEN + 1] = {0};
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);
uint16_t cli_port = ntohs(cliaddr.sin_port);
printf("connection from %s, port %d\n", buff, cli_port);
}
return 0;
}
?
?
編譯:
g++ server.cpp -o s1
?
g++ server.cpp -o s2
運(yùn)行結(jié)果:
?
另有:監(jiān)聽套接字與連接套接字
總結(jié)
以上是生活随笔為你收集整理的SO_REUSEADDR和SO_REUSEPORT的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。