linux下tcp socket的SO_REUSEPORT和SO_REUSEADDR

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

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 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;
}

Java socket小例子 该队列为SYN 队列,长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) ,linux机器的tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog下配置。2.SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。1.SO_REUSEADDR允许启动一个监听服务器并捆绑其端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。 阅读详情

相关推荐

socket 端口地址复用

参考: https://blog.csdn.net/weibo1230123/article/details/79978745 https://blog.csdn.net/weixin_42157432/article/details/115560824 在linux socket网络编程中,大规模并发TCP或UDP连接时,经常会用到端口复用: int opt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *) &

hanhan的博客 5354

SO_REUSEPORT socket选项介绍以及在nginx上的配置

SO_REUSEPORTsocket的一个选项: 1)开启内核的网络链接分配负载均衡:允许多个进程/线程 bind/listen相同的IP/PORT,提升了新链接的分配性能。 2)在内核级解决了惊群问题:每个进程可以 bind/listen相同的IP/PORT,相当于每个进程拥有独立的 listen socket 的完全队列,避免了共享 listen socket 的资源争抢,提升了并发的吞吐。内核通过哈希算法,将新链接相对均衡地分配到各个开启了 reuseport 属性的的进程。

赶路人儿 3882

哈密顿-雅可比不等式:连接最优控制与优化算法的自然物理原理

在控制理论与优化算法之间,存在一个深刻而实用的桥梁。最优控制理论旨在通过设计控制输入,使动态系统在时间维度上达到性能最优,其核心数学工具如庞特里亚金极大值原理哈密顿-雅可比-贝尔曼方程,为解决连续时间系统的优化问题提供了优雅框架。然而,这些方法在高维非凸问题中面临求解难题。哈密顿-雅可比不等式作为一种松弛方法,通过构造李雅普诺夫函数并验证一个不等式条件,转而寻求性能有保障且易于实现的次优解,其本质是保证系统能量衰减性能上界。这一原理与耗散性理论紧密相连,体现了自然物理系统(如阻尼振动)中能量耗散的普遍规

weixin_30582943的博客 280

socket中的reuse addrreuse port

SO_REUSEADDR (1)首先对于一个server服务进程来说,它的创建流程是 socket->bind ->listen->accept 创建监听套接字,bind一个指定端口,listen监听端口,为每个连接提供服务(一般会创建子进程)。当服务进程重启时,那么会重新执行一遍上面的流程,如果在创建socket后没有使用SO_REUSEADDR选项进行设置,再次进行bind就会失败。那么原因是什么? 首先需要了解一下TCP协议的4次挥手动作,如文章开头的图片所示,当一个连接在关闭时.

程序猿Ricky的日常干货 7008

网络编程中的SO_REUSEADDRSO_REUSEPORT参数详解

在BSD中,SO_REUSEADDR选项有两个用户:setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));目的:当服务端出现timewait状态的链接时,确保server能够重启成功。注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用。举个time_wait的连接通过so_reuseaddr选项

赶路人儿 6304

socket(7):详解 setsockopt (SO_REUSEPORT

通俗易懂说socket(5)详解 setsockopt()

哈尼的博客 3084

不可不知的socketTCP连接过程

本文主要说明的是TCP连接过程中,各个阶段对套接字的操作,希望能对没有网络编程基础的人理解套接字是什么、扮演的角色有所帮助。如发现错误,敬请指出 1. 背景 1.完整的套接字格式{protocol,src_addr,src_port,dest_addr,dest_port}。 这常被称为套接字的五元组。其中protocol指定了是TCP还是UDP连接,其余的分别指定了源地址、源端口、目标...

weixin_34416754的博客 459

linux unix域socket_浅谈LinuxSocket选项设置

图为好奇号火星车 代表了网络通信的最高水准-星际通信。科普性文章 多有参考 力争原创更多文章请关注微信公众号:后端技术指南针0.前言TCP/IP协议栈是Linux内核的重要组成部分网络编程的基石,虽然LinuxBSD有很大的联系,但是对于某些Socket选项内核操作仍然存在差异,因此文中适用场景均为仅Linux。《UNIX网络编程》是已故UNIX网络专家W. Richard Stevens博...

weixin_39897267的博客 336

socket 编程的端口地址复用

linux socket网络编程中,大规模并发TCP或UDP连接时,经常会用到端口复用: int opt = 1; if(setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, (const void *) &opt, sizeof(opt))){ perror("setsockopt")...

weixin_33790053的博客 301

TCP相关

文章目录1.Linux端口复用参数(套接字中SO_REUSEPORTSO_REUSEADDR的区别) 1.Linux端口复用参数(套接字中SO_REUSEPORTSO_REUSEADDR的区别) 参考:https://www.jb51.net/article/139134.htm SO_REUSEADDR: 如果在一个socket绑定到某一地址端口之前设置了其SO_REUSEADDR的属性,那么除非本socket与产生了尝试与另一个socket绑定到完全相同的源地址源端口组合的冲突,否则的话这个so

rootlh的博客 240

深入解析SO_REUSEADDR:解决TCP端口占用的关键技术

在网络编程中,TCP协议的TIME_WAIT状态常导致端口占用问题,影响服务重启效率。SO_REUSEADDR作为socket选项,通过放宽内核的端口绑定规则,允许重用处于TIME_WAIT状态的地址,解决了服务器快速重启的核心痛点。该技术与TCP_NODELAY等参数配合,能显著提升网络应用的可用性开发效率,广泛应用于Web服务器、负载均衡等高并发场景。理解其与SO_REUSEPORT的区别及跨平台特性,是构建稳定网络服务的关键基础。

weixin_34112181的博客 453

TCP/IP socket

(3)如果待发送数据的长度大于s的内核发送缓冲区的长度时,会先将s(发送端)的内核发送缓冲区填满,然后发送端会将内核发送缓冲区的数据发送到接收端socket的内核接收缓冲区,所以s(发送端)的内核发送缓冲区又会慢慢腾出空间,send又会将待发送数据往s(发送端)的内核发送缓冲区中copy。在阻塞模式下,send函数的过程是将应用程序请求发送的数据拷贝到内核发送缓存中,待发送数据完全被拷贝到内核发送缓存区中才返回,当然如果内核发送缓存区一直没有空间能容纳待发送的数据,则一直阻塞。

Melo琦 993

深入操作系统 Socket 底层:Socket 系统调用详解

深入操作系统 Socket 底层:Socket 系统调用、setsockopt / fcntl 详解:端口复用、缓冲区、IO选项、Unix域套接字、IPv6双栈

可达鸭的笔记簿 511

SO_REUSEADDRSO_REUSEPORT平台差异性与测试

SO_REUSEADDRSO_REUSEPORT平台差异性与测试 目录 平台差异性 测试 MACLinuxWindows总结references  正文  前些天,与另外一个项目组的同事聊天的时候,谈到他遇到的一个有意思的BUG。在window上启动服务器,然后客户端连接的时候收到一些奇怪的消息,查证了,原来是他自己的另一个工具也在相同的...

cool grass's blog 801

muduo库 --socket的封装

Linux高性能网络编程:Socket封装与muduo网络库核心思想 摘要 本文介绍了Linux高性能网络编程中Socket的基础封装方法,为学习muduo网络库奠定基础。主要内容包括: Socket通信的基本概念工作原理 关键API的封装实现: 创建套接字(socket) 绑定地址(bind) 监听连接(listen) 连接管理(connect/accept) 数据收发(recv/send) 非阻塞套接字复用设置 客户端服务器的快速创建方法 文章通过代码示例详细展示了每个核心功能的实现逻辑,包括错误

2301_80127108的博客 215

bind函数返回值-1_从Linux源码看Socket(TCP)的bind

Linux源码看Socket(TCP)的bind前言笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。今天笔者就来从Linux源码的角度看下Server端的Socket在进行bind的时候到底做了哪些事情(基于Linux 3.10内核)。一个最简单的Server端例子众所周知,一个Server端Socket的建立,需要socket、bind、li...

weixin_42164534的博客 4272

Linux源码看Socket(TCP)的bind

Linux源码看Socket(TCP)的bind 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。 今天笔者就来从Linux源码的角度看下Server端的Socket在进行bind的时候到底做了哪些事情(基于Linux 3.10内核)。 一个最简单的Server端例子 众所周知,一个Server端Socket的建立,需要socket、bind、listen、accept四个步骤。 代码如下: void start_server(){ // serv

alchemystar的专栏 567

SO_REUSEADDR SO_REUSEPORT

大部分内容来自stackoverflow上的回答:Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ? Do they mean the same across all major operating systems? 由于现有的操作系统上的socket都来自BSD socket,且每种操作...

weixin_30617797的博客 68

Socket options SO_REUSEADDR and SO_REUSEPORT

为什么80%的码农都做不了架构师?>>> ...

weixin_33982670的博客 205

如何从Linux源码看Socket(TCP)的bind

Linux源码看Socket(TCP)的bind 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。 今天笔者就来从Linux源码的角度看下Server端的Socket在进行bind的时候到底做了哪些事情(基于Linux 3.10内核)。 一个最简单的Server端例子 众所周知,一个Server端Socket的建立,需要socket、bind、listen、accept四个步骤。 代码如下: void start_server(){ /.

weixin_45127309的博客 278
上一篇: 从观察者模式的实现,看c/c++的语言特性
下一篇: linux下查看tcp连接的几种方式
winnuke
博客等级 码龄22年 18粉丝 9原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值