linux下tcp socket的SO_REUSEPORT和SO_REUSEADDR

来源:互联网 发布:linux 更改权限 编辑:程序博客网 时间:2024/06/05 20:38

SO_REUSEADDR

linux下多个tcp socket不能同时bind到一个ip:port上,但是可以bind到不同的ip相同的port上,前提是都要设置SO_REUSEADDR选项为true。否则会返回address already in use。

比如bind(127.0.0.1:80) bind(192.168.0.10:80) bind(10.0.0.12:80)这取决于有多少个网络接口,客户端connect的时候选择不同的ip和端口连接就可以了。

如果试图bind到同一个ip:port上,也必须是在前一个socket关闭,连接处于time_wait状态的时候。这种情况下大都是为了服务程序能够快速的重新启动,避免等待。


实际测试结果,假设我们本地是有2个ip地址127.0.0.1和10.0.0.1,服务端口为80,INADDR_ANY=0.0.0.0

如果设置了SO_REUSEADDR为1

1,先bind(0.0.0.0)成功,如果再bind(127.0.0.1)和bind(10.0.0.1)都会失败

2,先bind(127.0.0.1)成功,如果再bind(0.0.0.0)会失败,但bind(10.0.0.1)会成功

3,先bind(10.0.0.1)成功,如果再bind(0.0.0.0)会失败,但bind(127.0.0.1)会成功

4,先bind(x.x.x.x)成功,如果再bind(x.x.x.x)肯定失败



SO_REUSEPORT

linux下多个tcp socket可以同时bind到一个ip:port上,前提是都要设置SO_REUSEPORT选项为true。否则会返回address already in use。

当客户端connect这个ip:port的时候,实际上只有一个bind的socket是有效的,其他bind的socket是无法收到connect数据的。这个socket貌似也是系统随机挑选的,没有严格顺序。

实际测试结果,假设我们本地是有2个ip地址127.0.0.1和10.0.0.1,服务端口为80,INADDR_ANY=0.0.0.0

如果设置了SO_REUSEPORT为1

1,先bind(x.x.x.x)成功,如果再bind不同的ip都会成功

只是在数据的流向上会优先流向显示指定ip地址的socket。

比如bind(0.0.0.0)和bind(10.0.0.1)之后,

如果客户端connect(10.0.0.1),那就是第二个socket来接收数据。如果第二个socket关闭了,那则改为第一个socket来接收连接。

如果客户端connect(127.0.0.1),呢就是第一个socket接收数据。

2,如果bind两个相同的ip和port,那系统会根据srcip srcport dstip dstport做一个哈希,分配到指定的cpu核心和一个指定的socket来处理。


总结:SO_REUSEADDR是一个排他性的标记,一旦某个地址和某个socket绑定,并设置了该标志,那其他任意的socket都不能和该地址bind,包括其子地址。

一旦某个地址被bind成功了,使用该标记再bind该地址及其子地址也不会成功。唯一的例外是time_wait状态下,可以用同一地址成功bind。

SO_REUSEPORT则是一个包容性的标记,只要打上该标记,就可以随意的将socket和相同的或者不同的地址进行bind。然后系统会随机分配连接到这些socket。


那linux下想利用端口复用实现转发以及穿越防火墙的实现场景就和windows下不太一样了。

如果应用服务程序,bind的是INADDR_ANY,切设置了SO_REUSEADDR标记,那基本上在socket层面没法再bind该端口并接收数据了。即使先stop掉服务程序,自己先bind该端口,再启动服务程序,服务程序也会bind失败,进而转发没法实现。除非能把服务bind到localhost上,然后第三方程序bind到对外接口上,实现转发。

如果应用服务程序,设置了SO_REUSEPORT标记,那第三方程序也可以bind该端口,这样第三方程序就可以接收到部分连接数据并转发给服务程序。但问题是,第三方程序没法完全接收所有连接数据,这样就还是一个很受限制的转发了。




测试代码:

#ifdef WIN32#include <WinSock2.h>#include <WS2tcpip.h>#define SO_REUSEPORT 0  // SO_REUSEPORT is not impl under windows#pragma comment(lib, "ws2_32.lib")#else#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>// g++ this.cpp -std=c++11 -lpthread -o test#endif#include <string.h>#include <iostream>#include <thread>#include <string>#include <sstream>struct SocketTool{SocketTool(){#ifdef WIN32WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(2, 2);WSAStartup(wVersionRequested, &wsaData);#endif}template <typename SOCKET_TYPE>static int CloseSocket(SOCKET_TYPE skt){#ifdef WIN32return closesocket(skt);#elsereturn close(skt);#endif}static sockaddr_in GenInAddr(const char *szIP, unsigned uPort){sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(uPort);#ifdef WIN32inet_pton(AF_INET, szIP, &addr.sin_addr);#elseinet_aton(szIP, &addr.sin_addr);#endifreturn addr;}static std::string PrintInAddr(const sockaddr_in& addr){char buf[128] = { 0 };inet_ntop(AF_INET, (void*)&addr.sin_addr, buf, sizeof(buf));auto port = ntohs(addr.sin_port);std::stringstream ss;ss << buf << ":" << port;return ss.str();}~SocketTool(){#ifdef WIN32WSACleanup();#endif}};void TestClient(const char *ip, unsigned port){SocketTool sktTool;bool bCotinue = true;auto thread = [&bCotinue, ip, port](){auto s = socket(AF_INET, SOCK_STREAM, 0);auto addr = SocketTool::GenInAddr(ip, port);auto ret = connect(s, (sockaddr*)&addr, sizeof(addr));if (ret != 0){std::stringstream ss;ss << "connect " << ip << ":" << port << "failed";std::cout << ss.str() << std::endl;}std::stringstream ss;ss << "this is send from thread " << std::this_thread::get_id();std::string strSend = ss.str();do{auto r = send(s, strSend.c_str(), strSend.length(), 0);if (r <= 0){std::stringstream ss;ss << "send " << ip << ":" << port << "failed";std::cout << ss.str() << std::endl;break;}std::this_thread::sleep_for(std::chrono::seconds(3));std::cout << strSend << std::endl;} while (bCotinue);SocketTool::CloseSocket(s);};for (auto i = 0; i < 5; ++i){std::thread t(thread);t.detach();}std::thread t(thread);t.join();}void TestServ(const char *ip, unsigned port, unsigned reuseflag){SocketTool sktTool;auto serv = socket(AF_INET, SOCK_STREAM, 0);if (reuseflag != 0){int reuse = 1;if (setsockopt(serv, SOL_SOCKET, reuseflag, (char *)&reuse, sizeof(reuse)) == -1){std::cout << "setsockopt " << reuseflag << "failed" << std::endl;return;}}auto addr = SocketTool::GenInAddr(ip, port);if (bind(serv, (sockaddr*)&addr, sizeof(sockaddr)) == -1){std::cout << "bind failed" << std::endl;return;}if (listen(serv, 5) == -1){std::cout << "listen failed" << std::endl;return;}while (true){sockaddr_in client_addr;socklen_t length = sizeof(client_addr);auto con = accept(serv, (struct sockaddr*)&client_addr, &length);if (con < 0){std::cout << "accept failed!\n";}else{auto strClient = SocketTool::PrintInAddr(client_addr);std::cout << "accept from " << strClient << std::endl;std::thread procThread([con, strClient](){char buf[1024] = { 0 };do{int ret = recv(con, buf, 1024, 0);if (ret <= 0){std::cout << strClient << " closed!" << std::endl;SocketTool::CloseSocket(con);break;}else{buf[ret] = 0;std::cout << strClient << ":" << buf << std::endl;}} while (true);});procThread.detach();}}SocketTool::CloseSocket(serv);}int main(int argc, char **argv){if (argc != 4){std::cout << "usage: socket c|p|a ip port\n" << "c=client(connect(ip:port))\np=SO_REUSEPORT(not impl in windows)\na=SO_REUSEPORT" << std::endl;return 0;}char *ip = argv[2];int port = atoi(argv[3]);if (argv[1][0] == 'c'){TestClient(ip, port);}else if (argv[1][0] == 'p'){TestServ(ip, port, SO_REUSEPORT);}else if (argv[1][0] == 'a'){TestServ(ip, port, SO_REUSEADDR);}else{TestServ(ip, port, 0);}return 0;}

0 0