REUSEPORT 场景
TCP REUSEPORT 选项通过每个监听线程使用不同的 listen fd
来改进accpet的负载分配。相比于在一个线程中进行 accept
或者多个线程在同一个 listen fd 上进行 accept,有助于系统的负载均衡。
REUSEPORT 解决的问题是:两个线程同时 bind 在相同的 IP:Port
上,当两者同时进入 TCP_LISTEN
状态,内核如何将一个客户端连接分发给正确的线程。
bind
内核 bind 过程:
- 根据 IP 和 Port 等计算 hash key ;
- 在 bind_hash_bucket 中找到对应的 bind_bucket 链表,链表由 sock
结构体组成;
- bind 将新的 sock 结构体添加到对应链表;
- 添加时,由 inet_csk_get_port 检查当前使用的 port 的合法性;
- 检查 fastreuse 标志位是否为真,或者 fastreuseport
标志位是否为真,且新连接的 UID
是否和当前进程相同。标志位快速判断成功时,直接返回可用;
- 若快速判断不成功,将遍历 sock 链表,判断是否由冲突;
REUSEPORT 选项开启后,两个线程的连接冲突的条件为,sock 当前不是
TCP_TIME_WAIT 状态且连接 UID 不同与线程
UID。其他情况下,不会存在连接冲突的情况。
测试
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| #include <arpa/inet.h> #include <errno.h> #include <iostream> #include <net/ethernet.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <string> #include <sys/socket.h> #include <unistd.h>
using namespace std;
int main(int argc, char *argv[]) { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { std::cout << "socket error" << std::endl; return -1; }
int port = 30001; std::string addr_str = "127.0.0.1"; std::cout << "[tcp server_test1] addr:" << addr_str << ":" << port << " fd=" << listenfd << std::endl;
sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(addr_str.c_str());
#ifdef RU_PORT int val = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) < 0) { std::cout << "set SO_REUSEPORT error" << std::endl; } std::cout << "set SO_REUSEPORT succ" << std::endl; #endif
#ifdef RU_ADDR int val = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { std::cout << "set SO_REUSEADDR error" << std::endl; } std::cout << "set SO_REUSEADDR succ" << std::endl; #endif
int ret = bind(listenfd, (sockaddr *)&addr, sizeof(addr));
if (ret < 0) { std::cout << "bind error! errno=" << errno << ", err = " << strerror(errno) << std::endl; return -1; } std::cout << "bind success" << std::endl;
#ifndef NO_LISTEN ret = listen(listenfd, 5); if (ret < 0) { std::cout << "listen error! errno=" << errno << ", err = " << strerror(errno) << std::endl; return -1; } std::cout << "listen success" << std::endl; #endif
while (1) { #ifndef NO_LISTEN sockaddr cli_addr; socklen_t len;
std::cout << "accepting " << std::endl; int connfd = accept(listenfd, &cli_addr, &len); if (connfd < 0) { std::cout << "accept error, error=" << strerror(errno) << std::endl; } std::cout << "accept success, connfd=" << connfd << std::endl;
std::cout << "INFO: input 1 to close connection, go to timewait" << std::endl; int close_flag; std::cin >> close_flag; if (close_flag == 1) { shutdown(connfd, SHUT_WR); } std::cout << "success call shutdown, now exit" << std::endl;
break; #endif }
close(listenfd); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include <arpa/inet.h> #include <errno.h> #include <iostream> #include <net/ethernet.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <string> #include <sys/socket.h> #include <unistd.h>
int main(int argc, char *argv[]) { int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { std::cout << "socket error" << std::endl; return -1; }
int port = 30001; std::string addr_str = "127.0.0.1"; std::cout << "[tcp server_test2] addr:" << addr_str << ":" << port << " fd=" << listenfd << std::endl; sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(addr_str.c_str()); addr.sin_port = htons(port);
#ifdef RU_PORT int val = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) < 0) { std::cout << "set SO_REUSEPORT error" << std::endl; } std::cout << "set SO_REUSEPORT success" << std::endl; #endif
#ifdef RU_ADDR int val = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { std::cout << "set SO_REUSEADDR error" << std::endl; } std::cout << "set SO_REUSEADDR success" << std::endl; #endif
int ret = bind(listenfd, (sockaddr *)&addr, sizeof(addr)); if (ret < 0) { std::cout << "bind error! errno=" << errno << ", err = " << strerror(errno) << std::endl; return -1; } std::cout << "bind success" << std::endl;
#ifndef NO_LISTEN ret = listen(listenfd, 5); if (ret < 0) { std::cout << "listen error! errno=" << errno << ", err = " << strerror(errno) << std::endl; return -1; } std::cout << "listen success" << std::endl; #endif while (1) {}
close(listenfd); return 0; }
|
Case 1: 不开启 REUSEADDR 和 REUSEPORT,使用相同 IP 和 PORT
Case 2: 开启 REUSEADDR ,但是不调用 listen,使用相同 IP 和 PORT
Case 3: 开启 REUSEADDR ,调用 listen,使用相同 IP 和 PORT
Case 4: 开启 REUSEPORT ,调用 listen,使用相同 IP 和 PORT