linux下tcp socket的SO_REUSEPORT和SO_REUSEADDR
2016-01-28 10:42
1051 查看
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该端口,这样第三方程序就可以接收到部分连接数据并转发给服务程序。但问题是,第三方程序没法完全接收所有连接数据,这样就还是一个很受限制的转发了。
测试代码:
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 WIN32 WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(2, 2); WSAStartup(wVersionRequested, &wsaData); #endif } template <typename SOCKET_TYPE> static int CloseSocket(SOCKET_TYPE skt) { #ifdef WIN32 return closesocket(skt); #else return 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 WIN32 inet_pton(AF_INET, szIP, &addr.sin_addr); #else inet_aton(szIP, &addr.sin_addr); #endif return 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 WIN32 WSACleanup(); #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; }
相关文章推荐
- 能ping通网络,也正常连接,就是打不开网页,无法访问网络
- 关于Java网络编程
- 判断任意两台计算机的IP地址是否属于同一子网络(华为OJ)
- Python 3.x基于Xml数据的Http请求
- Servlet处理客户端HTTP请求
- Android使用HttpClient实现文件上传到PHP服务器,并监控进度条
- Android 扩展OkHttp支持请求优先级调度
- TCP长连接与短连接的区别
- 2016太原网络营销师郭文军分享所带学员在达内的成长?
- okhttp的简单介绍(二)之简单封装
- C语言中printf输出的奇怪错误 http://segmentfault.com/q/1010000002534752
- Android的网络应用
- TCP,IP,端口,地址(初步理解)
- php cURL模拟https请求报错
- 使用afnetworking和网络服务接口及soap发送并接收http/https请求:
- 【HTTP】Fiddler(三)- Fiddler命令行和HTTP断点调试
- 【HTTP】Fiddler(二) - 使用Fiddler做抓包分析
- 【HTTP】Fiddler(一) - Fiddler简介
- 网络流算法
- 理解并使用.NET 4.5中的HttpClient