技术文档收录
ASCII
Tcpdump
IPV4保留地址段
深入理解以太网网线原理 - 三帛的世界
Linux
WireGuard 一键安装脚本 | 秋水逸冰
SSH Config 那些你所知道和不知道的事 | Deepzz's Blog
Linux 让终端走代理的几种方法
ubuntu 20.04 server 版设置静态 IP 地址 - 链滴
Linux 挂载 Windows 共享磁盘的方法 - 技术学堂
将 SMB/CIFS 网络硬盘永久的挂载到 Ubuntu 上 - 简书
linux 获取当前脚本的绝对路径 | aimuke
[Linux] Linux 使用 / dev/urandom 生成随机数 - piaohua's blog
Linux 生成随机数的多种方法 | Just Do It
Linux 的 Centos7 版本下忘记 root 或者普通用户密码怎么办?
Git 强制拉取覆盖本地
SSH 安全加固指南 - FreeBuf 网络安全行业门户
Linux 系统安全强化指南 - FreeBuf 网络安全行业门户
Linux 入侵排查 - FreeBuf 网络安全行业门户
sshd_config 配置详解 - 简书
SSH 权限详解 - SegmentFault 思否
CentOS 安装 node.js 环境 - SegmentFault 思否
如何在 CentOS 7 上安装 Node.js 和 npm | myfreax
几款 ping tcping 工具总结
OpenVpn 搭建教程 | Jesse's home
openvpn 一键安装脚本 - 那片云
OpenVPN 解决 每小时断线一次 - 爱开源
OpenVPN 路由设置 – 凤曦的小窝
OpenVPN 设置非全局代理 - 镜子的记录簿
TinyProxy 使用帮助 - 简书
Ubuntu 下使用 TinyProxy 搭建代理 HTTP 服务器_Linux_运维开发网_运维开发技术经验分享
Linux 软件包管理工具 Snap 常用命令 - 简书
linux systemd 参数详解
Systemd 入门教程:命令篇 - 阮一峰的网络日志
记一次 Linux 木马清除过程
rtty:在任何地方通过 Web 访问您的终端
02 . Ansible 高级用法 (运维开发篇)
终于搞懂了服务器为啥产生大量的 TIME_WAIT!
巧妙的 Linux 命令,再来 6 个!
77% 的 Linux 运维都不懂的内核问题,这篇全告诉你了
运维工程师必备:请收好 Linux 网络命令集锦
一份阿里员工的 Java 问题排查工具单
肝了 15000 字性能调优系列专题(JVM、MySQL、Nginx and Tomcat),看不完先收
作业调度算法(FCFS,SJF,优先级调度,时间片轮转,多级反馈队列) | The Blog Of WaiterXiaoYY
看了这篇还不会 Linux 性能分析和优化,你来打我
2019 运维技能风向标
更安全的 rm 命令,保护重要数据
求你了,别再纠结线程池大小了!
Linux sudo 详解 | 失落的乐章
重启大法好!线上常见问题排查手册
sudo 使用 - 笨鸟教程的博客 | BY BenderFly
shell 在手分析服务器日志不愁? - SegmentFault 思否
sudo 与 visudo 的超细用法说明_陈发哥 007 的技术博客_51CTO 博客
ESXI 下无损扩展 Linux 硬盘空间 | Naonao Blog
Linux 学习记录:su 和 sudo | Juntao Tan 的个人博客
使用者身份切换 | Linux 系统教程(笔记)
你会使用 Linux 编辑器 vim 吗?
在 Windows、Linux 和 Mac 上查看 Wi-Fi 密码
linux 隐藏你的 crontab 后门 - 简书
Linux 定时任务详解 - Tr0y's Blog
linux 的 TCP 连接数量最大不能超过 65535 个吗,那服务器是如何应对百万千万的并发的?_一口 Linux 的博客 - CSDN 博客_tcp 连接数多少正常
万字长文 + 28 张图,一次性说清楚 TCP,运维必藏
为什么 p2p 模式的 tunnel 底层通常用 udp 而不是 tcp?
记一次服务器被入侵挖矿 - tlanyan
shell 判断一个变量是否为空方法总结 - 腾讯云开发者社区 - 腾讯云
系统安装包管理工具 | Escape
编译代码时动态地链接库 - 51CTO.COM
甲骨文 Oracle Cloud 添加新端口开放的方法 - WirelessLink 社区
腾讯云 Ubuntu 添加 swap 分区的方法_弓弧名家_玄真君的博客 - CSDN 博客
Oracle 开放全部端口并关闭防火墙 - 清~ 幽殇
谁再说不熟悉 Linux 命令, 就把这个给他扔过去!
即插即用,运维工程师必会正则表达式大全
Shell脚本编写及常见面试题
Samba 文件共享服务器
到底一台服务器上最多能创建多少个 TCP 连接 | plantegg
SSH 密钥登录 - SSH 教程 - 网道
在 Bash 中进行 encodeURIComponent/decodeURIComponent | Harttle Land
使用 Shell 脚本来处理 JSON - Tom CzHen's Blog
Docker
「Docker」 - 保存镜像 - 知乎
终于可以像使用 Docker 一样丝滑地使用 Containerd 了!
私有镜像仓库选型:Harbor VS Quay - 乐金明的博客 | Robin Blog
exec 与 entrypoint 使用脚本 | Mr.Cheng
Dockerfile 中的 CMD 与 ENTRYPOINT
使用 Docker 配置 MySQL 主从数据库 - 墨天轮
Alpine vs Distroless vs Busybox – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang | 云原生
再见,Docker!
docker save 与 docker export 的区别 - jingsam
如何优雅的关闭容器
docker 储存之 tmpfs 、bind-mounts、volume | 陌小路的个人博客
Dockerfile 中 VOLUME 与 docker -v 的区别是什么 - 开发技术 - 亿速云
理解 docker 容器的退出码 | Vermouth | 博客 | docker | k8s | python | go | 开发
【Docker 那些事儿】容器监控系统,来自 Docker 的暴击_飞向星的客机的博客 - CSDN 博客
【云原生】Docker 镜像详细讲解_微枫 Micromaple 的博客 - CSDN 博客_registry-mirrors
【云原生】Helm 架构和基础语法详解
CMD 和 Entrypoint 命令使用变量的用法
实时查看容器日志 - 苏洋博客
Traefik 2 使用指南,愉悦的开发体验 - 苏洋博客
为你的 Python 应用选择一个最好的 Docker 映像 | 亚马逊 AWS 官方博客
【云原生】镜像构建实战操作(Dockerfile)
Docker Compose 中的 links 和 depends_on 的区别 - 编程知识 - 白鹭情
Python
Pipenv:新一代Python项目环境与依赖管理工具 - 知乎
Python list 列表实现栈和队列
Python 各种排序 | Lesley's blog
Python 中使用 dateutil 模块解析时间 - SegmentFault 思否
一个小破网站,居然比 Python 官网还牛逼
Python 打包 exe 的王炸 - Nuitka
Django - - 基础 - - Django ORM 常用查询语法及进阶
[Python] 小知識:== 和 is 的差異 - Clay-Technology World
Window
批处理中分割字符串 | 网络进行时
Windows 批处理基础命令学习 - 简书
在Windows上设置WireGuard
Windows LTSC、LTSB、Server 安装 Windows Store 应用商店
windows 重启 rdpclip.exe 的脚本
中间件
Nginx 中的 Rewrite 的重定向配置与实践
RabbitMQ 的监控
RabbitMq 最全的性能调优笔记 - SegmentFault 思否
为什么不建议生产用 Redis 主从模式?
高性能消息中间件——NATS
详解:Nginx 反代实现 Kibana 登录认证功能
分布式系统关注点:仅需这一篇,吃透 “负载均衡” 妥妥的
仅需这一篇,妥妥的吃透” 负载均衡”
基于 nginx 实现上游服务器动态自动上下线——不需 reload
Nginx 学习书单整理
最常见的日志收集架构(ELK Stack)
分布式之 elk 日志架构的演进
CAT 3.0 开源发布,支持多语言客户端及多项性能提升
Kafka 如何做到 1 秒处理 1500 万条消息?
Grafana 与 Kibana
ELK 日志系统之通用应用程序日志接入方案
ELK 简易 Nginx 日志系统搭建: ElasticSearch+Kibana+Filebeat
记一次 Redis 连接池问题引发的 RST
把 Redis 当作队列来用,你好大的胆子……
Redis 最佳实践:业务层面和运维层面优化
Redis 为什么变慢了?常见延迟问题定位与分析
好饭不怕晚,扒一下 Redis 配置文件的底 Ku
rabbitmq 集群搭建以及万级并发下的性能调优
别再问我 Redis 内存满了该怎么办了
Nginx 状态监控及日志分析
uWSGI 的安装及配置详解
uwsgi 异常服务器内存 cpu 爆满优化思路
Uwsgi 内存占用过多 - 简书
Nginx 的 limit 模块
Nginx 内置模块简介
Redis 忽然变慢了如何排查并解决?_redis_码哥字节_InfoQ 写作社区
领导:谁再用 redis 过期监听实现关闭订单,立马滚蛋!
Nginx 限制 IP 访问频率以及白名单配置_问轩博客
Nginx $remote_addr 和 $proxy_add_x_forwarded_for 变量详解
Caddy 部署实践
一文搞定 Nginx 限流
数据库
SqlServer 将数据库中的表复制到另一个数据库_MsSql_脚本之家
SQL Server 数据库同步,订阅、发布、复制、跨服务器
sql server 无法删除本地发布 | 辉克's Blog
SQLite全文检索
SQL 重复记录查询的几种方法 - 简书
SQL SERVER 使用订阅发布同步数据库(转)
Mysql 查看用户连接数配置及每个 IP 的请求情况 - 墨天轮
优化 SQL 的 21 条方案
SQL Server 连接时好时坏的奇怪问题
MS SQL 执行大脚本文件时,提示 “内存不足” 的解决办法 - 阿里云开发者社区
防火墙-iptables
iptables 常用规则:屏蔽 IP 地址、禁用 ping、协议设置、NAT 与转发、负载平衡、自定义链
防火墙 iptables 企业防火墙之 iptables
Linux 防火墙 ufw 简介
在 Ubuntu 中用 UFW 配置防火墙
在 Ubuntu20.04 上怎样使用 UFW 配置防火墙 - 技术库存网
监控类
开箱即用的 Prometheus 告警规则集
prometheus☞搭建 | zyh
docker 部署 Prometheus 监控服务器及容器并发送告警 | chris'wang
PromQL 常用命令 | LRF 成长记
prometheus 中使用 python 手写 webhook 完成告警
持续集成CI/CD
GitHub Actions 的应用场景 | 记录干杯
GithubActions · Mr.li's Blog
工具类
GitHub 中的开源网络广告杀手,十分钟快速提升网络性能
SSH-Auditor:一款 SHH 弱密码探测工具
别再找了,Github 热门开源富文本编辑器,最实用的都在这里了 - srcmini
我最喜欢的 CLI 工具
推荐几款 Redis 可视化工具
内网代理工具与检测方法研究
环境篇:数据同步工具 DataX
全能系统监控工具 dstat
常用 Web 安全扫描工具合集
给你一款利器!轻松生成 Nginx 配置文件
教程类
Centos7 搭建神器 openvpn | 运维随笔
搭建 umami 收集个人网站统计数据 | Reorx’s Forge
openvpn安装教程
基于 gitea+drone 完成小团队的 CI/CD - 德国粗茶淡饭
将颜色应用于交替行或列
VMware Workstation 全系列合集 精简安装注册版 支持 SLIC2.6、MSDM、OSX 更新 16.2.3_虚拟机讨论区_安全区 卡饭论坛 - 互助分享 - 大气谦和!
在 OpenVPN 上启用 AD+Google Authenticator 认证 | 运维烂笔头
Github 进行 fork 后如何与原仓库同步:重新 fork 很省事,但不如反复练习版本合并 · Issue #67 · selfteaching/the-craft-of-selfteaching
卧槽,VPN 又断开了!!- 阿里云开发者社区
Grafana Loki 学习之踩坑记
zerotier 的 planet 服务器(根服务器)的搭建踩坑记。无需 zerotier 官网账号。
阿里云 qcow2 镜像转 vmdk,导入 ESXi - 唐际忠的博客
Caddy 入门 – 又见杜梨树
【Caddy2】最新 Caddy2 配置文件解析 - Billyme 的博客
Web 服务器 Caddy 2 | Haven200
手把手教你打造高效的 Kubernetes 命令行终端
Keras 作者:给软件开发者的 33 条黄金法则
超详细的网络抓包神器 Tcpdump 使用指南
使用 fail2ban 和 FirewallD 黑名单保护你的系统
linux 下 mysql 数据库单向同步配置方法分享 (Mysql)
MySQL 快速删除大量数据(千万级别)的几种实践方案
GitHub 上的优质 Linux 开源项目,真滴牛逼!
WireGuard 教程:使用 Netmaker 来管理 WireGuard 的配置 – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang | 云原生
Tailscale 基础教程:Headscale 的部署方法和使用教程 – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang | 云原生
Nebula Graph 的 Ansible 实践
改进你的 Ansible 剧本的 4 行代码
Caddy 2 快速简单安装配置教程 – 高玩梁的博客
切换至 Caddy2 | 某不科学的博客
Caddy2 简明教程 - bleem
树莓派安装 OpenWrt 突破校园网限制 | Asttear's Blog
OpenVPN 路由设置 – 凤曦的小窝
个性化编译 LEDE 固件
盘点各种 Windows/Office 激活工具
[VirtualBox] 1、NAT 模式下端口映射
VirtualBox 虚拟机安装 openwrt 供本机使用
NUC 折腾笔记 - 安装 ESXi 7 - 苏洋博客
锐捷、赛尔认证 MentoHUST - Ubuntu 中文
How Do I Use A Client Certificate And Private Key From The IOS Keychain? | OpenVPN
比特记事簿: 笔记: 使用电信 TR069 内网架设 WireGuard 隧道异地组网
利用 GitHub API 获取最新 Releases 的版本号 | 这是只兔子
docsify - 生成文档网站简单使用教程 - SegmentFault 思否
【干货】Chrome 插件 (扩展) 开发全攻略 - 好记的博客
一看就会的 GitHub 骚操作,让你看上去像一位开源大佬
【计算机网络】了解内网、外网、宽带、带宽、流量、网速_墩墩分墩 - CSDN 博客
mac-ssh 配置 | Sail
如何科学管理你的密码
VirtualBox NAT 端口映射实现宿主机与虚拟机相互通信 | Shao Guoliang 的博客
CentOS7 配置网卡为静态 IP,如果你还学不会那真的没有办法了!
laisky-blog: 近期折腾 tailscale 的一些心得
使用 acme.sh 给 Nginx 安装 Let’ s Encrypt 提供的免费 SSL 证书 · Ruby China
acme 申请 Let’s Encrypt 泛域名 SSL 证书
从 nginx 迁移到 caddy
使用 Caddy 替代 Nginx,全站升级 https,配置更加简单 - Diamond-Blog
http.proxy - Caddy 中文文档
动手撸个 Caddy(二)| Caddy 命令行参数最全教程 | 飞雪无情的总结
Caddy | 学习笔记 - ijayer
Caddy 代理 SpringBoot Fatjar 应用上传静态资源
使用 graylog3.0 收集 open××× 日志进行审计_年轻人,少吐槽,多搬砖的技术博客_51CTO 博客
提高国内访问 github 速度的 9 种方法! - SegmentFault 思否
VM16 安装 macOS 全网最详细
2022 目前三种有效加速国内 Github
How to install MariaDB on Alpine Linux | LibreByte
局域网内电脑 - ipad 文件共享的三种方法 | 岚
多机共享键鼠软件横向测评 - 尚弟的小笔记
VLOG | ESXI 如何升级到最新版,无论是 6.5 还是 6.7 版本都可以顺滑升级。 – Vedio Talk - VLOG、科技、生活、乐分享
远程修改 ESXi 6.7 管理 IP 地址 - 腾讯云开发者社区 - 腾讯云
几乎不要钱自制远程 PLC 路由器方案
traefik 简易入门 | 个人服务器运维指南 | 山月行
更完善的 Docker + Traefik 使用方案 - 苏洋博客
MicroSD·TF 卡终极探秘 ·MLC 颗粒之谜 1 三星篇_microSD 存储卡_什么值得买
macOS 绕过公证和应用签名方法 - 走客
MiscSecNotes / 内网端口转发及穿透. md at master · JnuSimba/MiscSecNotes
我有特别的 DNS 配置和使用技巧 | Sukka's Blog
SEO:初学者完整指南
通过 OpenVPN 实现流量审计
OpenVPN-HOWTO
OpenVPN Server · Devops Roadmap
Linux 运维必备的 13 款实用工具, 拿好了~
linux 平台下 Tomcat 的安装与优化
Linux 运维跳槽必备的 40 道面试精华题
Bash 脚本进阶,经典用法及其案例 - alonghub - 博客园
推荐几个非常不错的富文本编辑器 - 走看看
在 JS 文件中加载 JS 文件的方法 - 月光博客
#JavaScript 根据需要动态加载脚本并设置自定义参数
笔记本电脑 BIOS 修改及刷写教程
跨平台加密 DNS 和广告过滤 personalDNSfilter · LinuxTOY
AdGuard Home 安装及使用指北
通过 Amazon S3 协议挂载 OSS
记一次云主机如何挂载对象存储
本文档发布于https://mrdoc.fun
-
+
首页
到底一台服务器上最多能创建多少个 TCP 连接 | plantegg
> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [plantegg.github.io](https://plantegg.github.io/2020/11/30/%E4%B8%80%E5%8F%B0%E6%9C%BA%E5%99%A8%E4%B8%8A%E6%9C%80%E5%A4%9A%E8%83%BD%E5%88%9B%E5%BB%BA%E5%A4%9A%E5%B0%91%E4%B8%AATCP%E8%BF%9E%E6%8E%A5/) 经常听到有同学说一台机器最多能创建 65535 个 TCP 连接,这其实是错误的理解,为什么会有这个错误的理解呢? [](#port-range "port range")port range -------------------------------------- 我们都知道 linux 下本地随机端口范围由参数控制,也就是 listen、connect 时候如果没有指定本地端口,那么就从下面的 port range 中随机取一个可用的 ``` # cat /proc/sys/net/ipv4/ip_local_port_range 2000 65535 ``` port range 的上限是 65535,所以也经常看到这个**误解**:一台机器上最多能创建 65535 个 TCP 连接 [](#到底一台机器上最多能创建多少个TCP连接 "到底一台机器上最多能创建多少个TCP连接")到底一台机器上最多能创建多少个 TCP 连接 ---------------------------------------------------------------------- 先说**结论**:在内存、文件句柄足够的话可以创建的连接是**没有限制**的(每个 TCP 连接至少要消耗一个文件句柄)。 那么 / proc/sys/net/ipv4/ip_local_port_range 指定的端口范围到底是什么意思呢? 核心规则:**一个 TCP 连接只要保证四元组 (src-ip src-port dest-ip dest-port) 唯一就可以了,而不是要求 src port 唯一** 后面所讲都遵循这个规则,所以在心里反复默念:**四元组唯一** 五个大字,就能分析出来到底能创建多少 TCP 连接了。 比如如下这个机器上的 TCP 连接实际状态: ``` # netstat -ant |grep 18089 tcp 0 0 192.168.1.79:18089 192.168.1.79:22 ESTABLISHED tcp 0 0 192.168.1.79:18089 192.168.1.79:18080 ESTABLISHED tcp 0 0 192.168.0.79:18089 192.168.0.79:22 TIME_WAIT tcp 0 0 192.168.1.79:22 192.168.1.79:18089 ESTABLISHED tcp 0 0 192.168.1.79:18080 192.168.1.79:18089 ESTABLISHED ``` 从前三行可以清楚地看到 18089 被用了三次,第一第二行 src-ip、dest-ip 也是重复的,但是 dest port 不一样,第三行的 src-port 还是 18089,但是 src-ip 变了。他们的四元组均不相同。 所以一台机器能创建的 TCP 连接是没有限制的,而 ip_local_port_range 是指没有 bind 的时候 OS 随机分配端口的范围,但是分配到的端口要同时满足五元组唯一,这样 ip_local_port_range 限制的是连同一个目标(dest-ip 和 dest-port 一样)的 port 的数量(请忽略本地多网卡的情况,因为 dest-ip 为以后 route 只会选用一个本地 ip)。 **那么为什么大家有这样的误解呢?**我总结了下,大概是以下两个原因让大家误解了: * 如果是 listen 服务,那么肯定端口不能重复使用,这样就跟我们的误解对应上了,一个服务器上最多能监听 65535 个端口。比如 nginx 监听了 80 端口,那么 tomcat 就没法再监听 80 端口了,这里的 80 端口只能监听一次(如果有个连接用了 80 连别人,这里 80 还是不能被 listen…… 想想)。 * 另外如果我们要连的 server 只有一个,比如:1.1.1.1:80 ,同时本机只有一个 ip 的话,那么这个时候即使直接调 connect 也只能创建出 65535 个连接,因为四元组中的三个是固定的了。 我们在创建连接前,经常会先调 bind,bind 后可以调 listen 当做服务端监听,也可以直接调 connect 当做 client 来连服务端。 bind(ip,port=0) 的时候是让系统绑定到某个网卡和自动分配的端口,此时系统没有办法确定接下来这个 socket 是要去 connect 还是 listen. 如果是 listen 的话,那么肯定是不能出现端口冲突的,如果是 connect 的话,只要满足 4 元组唯一即可。在这种情况下,系统只能尽可能满足更强的要求,就是先要求端口不能冲突,即使之后去 connect 的时候四元组是唯一的。 但如果我只是个 client 端,只需要连接 server 建立连接,也就不需要 bind,直接调 connect 就可以了,这个时候只要保证四元组唯一就行。 bind() 的时候内核是还不知道四元组的,只知道 src_ip、src_port,所以这个时候单网卡下 src_port 是没法重复的,但是 connect() 的时候已经知道了四元组的全部信息,所以只要保证四元组唯一就可以了,那么这里的 src_port 完全是可以重复使用的。 ![](https://plantegg.github.io/images/951413iMgBlog/640-20220224103024676.png) **是不是加上了 SO_REUSEADDR、SO_REUSEPORT 就能重用端口了呢?** [](#TCP-SO-REUSEADDR "TCP SO_REUSEADDR")TCP SO_REUSEADDR -------------------------------------------------------- 文档描述: SO_REUSEADDR Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses. For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address. When the listening socket is bound to INADDR_ANY with a specific port then it is not possible to bind to this port for any local address. Argument is an integer boolean flag. 从这段文档中我们可以知道三个事: 1. 使用这个参数后,bind 操作是可以重复使用 local address 的,注意,这里说的是 local address,即 ip 加端口组成的本地地址,也就是两个本地地址,如果有任意 ip 或端口部分不一样,它们本身就是可以共存的,不需要使用这个参数。 2. 当 local address 被一个处于 listen 状态的 socket 使用时,加上该参数也不能重用这个地址。 3. 当处于 listen 状态的 socket 监听的本地地址的 ip 部分是 INADDR_ANY,即表示监听本地的所有 ip,即使使用这个参数,也不能再 bind 包含这个端口的任意本地地址,这个和 2 中描述的其实是一样的。 ==SO_REUSEADDR 可以用本地相同的 (sip, sport) 去连 connect 远程的不同的(dip、dport)// 而 SO_REUSEPORT 主要是解决 Server 端的 port 重用 == [SO_REUSEADDR 还可以重用 TIME_WAIT 状态的 port](https://mp.weixin.qq.com/s/YWzuKBK3TMclejeN2ziAvQ), 在程序崩溃后之前的 TCP 连接会进入到 TIME_WAIT 状态,需要一段时间才能释放,如果立即重启就会抛出 Address Already in use 的错误导致启动失败。这时候可以通过在调用 bind 函数之前设置 SO_REUSEADDR 来解决。 What exactly does SO_REUSEADDR do? This socket option tells the kernel that even if this port is busy (in the TIME_WAIT state), go ahead and reuse it anyway. If it is busy, but with another state, you will still get an address already in use error. It is useful if your server has been shut down, and then restarted right away while sockets are still active on its port. You should be aware that if any unexpected data comes in, it may confuse your server, but while this is possible, it is not likely. It has been pointed out that “A socket is a 5 tuple (proto, local addr, local port, remote addr, remote port). SO_REUSEADDR just says that you can reuse local addresses. The 5 tuple still must be unique!” This is true, and this is why it is very unlikely that unexpected data will ever be seen by your server. The danger is that such a 5 tuple is still floating around on the net, and while it is bouncing around, a new connection from the same client, on the same system, happens to get the same remote port. By setting `SO_REUSEADDR` user informs the kernel of an intention to share the bound port with anyone else, but only if it doesn’t cause a conflict on the protocol layer. There are at least three situations when this flag is useful: 1. Normally after binding to a port and stopping a server it’s neccesary to wait for a socket to time out before another server can bind to the same port. With `SO_REUSEADDR` set it’s possible to rebind immediately, even if the socket is in a `TIME_WAIT` state. 2. When one server binds to `INADDR_ANY`, say `0.0.0.0:1234`, it’s impossible to have another server binding to a specific address like `192.168.1.21:1234`. With `SO_REUSEADDR` flag this behaviour is allowed. 3. When using the bind before connect trick only a single connection can use a single outgoing source port. With this flag, it’s possible for many connections to reuse the same source port, given that they connect to different destination addresses. [](#TCP-SO-REUSEPORT "TCP SO_REUSEPORT")TCP SO_REUSEPORT -------------------------------------------------------- SO_REUSEPORT 主要用来解决惊群、性能等问题。通过多个进程、线程来监听同一端口,进来的连接通过内核来 hash 分发做到负载均衡,避免惊群。 SO_REUSEPORT is also useful for eliminating the try-10-times-to-bind hack in ftpd’s data connection setup routine. Without SO_REUSEPORT, only one ftpd thread can bind to TCP (lhost, lport, INADDR_ANY, 0) in preparation for connecting back to the client. Under conditions of heavy load, there are more threads colliding here than the try-10-times hack can accomodate. With SO_REUSEPORT, things work nicely and the hack becomes unnecessary. SO_REUSEPORT 使用场景:linux kernel 3.9 引入了最新的 SO_REUSEPORT 选项,使得多进程或者多线程创建多个绑定同一个 ip:port 的监听 socket,提高服务器的接收链接的并发能力, 程序的扩展性更好;此时需要设置 SO_REUSEPORT(**注意所有进程都要设置才生效**)。 setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int)); 目的:每一个进程有一个独立的监听 socket,并且 bind 相同的 ip:port,独立的 listen() 和 accept();提高接收连接的能力。(例如 nginx 多进程同时监听同一个 ip:port) (a) on Linux SO_REUSEPORT is meant to be used _purely_ for load balancing multiple incoming UDP packets or incoming TCP connection requests across multiple sockets belonging to the same app. ie. it’s a work around for machines with a lot of cpus, handling heavy load, where a single listening socket becomes a bottleneck because of cross-thread contention on the in-kernel socket lock (and state). (b) set IP_BIND_ADDRESS_NO_PORT socket option for tcp sockets before binding to a specific source ip with port 0 if you’re going to use the socket for connect() rather then listen() this allows the kernel to delay allocating the source port until connect() time at which point it is much cheaper [](#The-Ephemeral-Port-Range "The Ephemeral Port Range")[The Ephemeral Port Range](http://www.ncftp.com/ncftpd/doc/misc/ephemeral_ports.html) --------------------------------------------------------------------------------------------------------------------------------------------- Ephemeral Port Range 就是我们前面所说的 Port Range(/proc/sys/net/ipv4/ip_local_port_range) A TCP/IPv4 connection consists of two endpoints, and each endpoint consists of an IP address and a port number. Therefore, when a client user connects to a server computer, an established connection can be thought of as the 4-tuple of (server IP, server port, client IP, client port). Usually three of the four are readily known – client machine uses its own IP address and when connecting to a remote service, the server machine’s IP address and service port number are required. What is not immediately evident is that when a connection is established that the client side of the connection uses a port number. Unless a client program explicitly requests a specific port number, the port number used is an ephemeral port number. Ephemeral ports are temporary ports assigned by a machine’s IP stack, and are assigned from a designated range of ports for this purpose. When the connection terminates, the ephemeral port is available for reuse, although most IP stacks won’t reuse that port number until the entire pool of ephemeral ports have been used. So, if the client program reconnects, it will be assigned a different ephemeral port number for its side of the new connection. [](#linux-如何选择Ephemeral-Port "linux 如何选择Ephemeral Port")linux 如何选择 Ephemeral Port --------------------------------------------------------------------------------- 有资料说是随机从 Port Range 选择 port,有的说是顺序选择,那么实际验证一下。 如下测试代码: ``` #include <stdio.h> // printf #include <stdlib.h> // atoi #include <unistd.h> // close #include <arpa/inet.h> // ntohs #include <sys/socket.h> // connect, socket void sample() { // Create socket int sockfd; if (sockfd = socket(AF_INET, SOCK_STREAM, 0), -1 == sockfd) { perror("socket"); } // Connect to remote. This does NOT actually send a packet. const struct sockaddr_in raddr = { .sin_family = AF_INET, .sin_port = htons(8080), // arbitrary remote port .sin_addr = htonl(INADDR_ANY) // arbitrary remote host }; if (-1 == connect(sockfd, (const struct sockaddr *)&raddr, sizeof(raddr))) { perror("connect"); } // Display selected ephemeral port const struct sockaddr_in laddr; socklen_t laddr_len = sizeof(laddr); if (-1 == getsockname(sockfd, (struct sockaddr *)&laddr, &laddr_len)) { perror("getsockname"); } printf("local port: %i\n", ntohs(laddr.sin_port)); // Close socket close(sockfd); } int main() { for (int i = 0; i < 5; i++) { sample(); } return 0; } ``` bind 逻辑测试代码 ``` #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <time.h> void test_bind(){ int listenfd = 0, connfd = 0; struct sockaddr_in serv_addr; char sendBuff[1025]; time_t ticks; socklen_t len; listenfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serv_addr, '0', sizeof(serv_addr)); memset(sendBuff, '0', sizeof(sendBuff)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(0); bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); len = sizeof(serv_addr); if (getsockname(listenfd, (struct sockaddr *)&serv_addr, &len) == -1) { perror("getsockname"); return; } printf("port number %d\n", ntohs(serv_addr.sin_port)); //只是挑选到了port,在系统层面保留,tcp连接还没有,netstat是看不到的 } int main(int argc, char *argv[]) { for (int i = 0; i < 5; i++) { test_bind(); } return 0; } ``` ### [](#3-10-0-327-ali2017-alios7-x86-64 "3.10.0-327.ali2017.alios7.x86_64")3.10.0-327.ali2017.alios7.x86_64 编译后,执行 (3.10.0-327.ali2017.alios7.x86_64): ``` #date; ./client && echo "+++++++" ; ./client && sleep 0.1 ; echo "-------" && ./client && sleep 10; date; ./client && echo "+++++++" ; ./client && sleep 0.1 && echo "******"; ./client; Fri Nov 27 10:52:52 CST 2020 local port: 17448 local port: 17449 local port: 17451 local port: 17452 local port: 17453 +++++++ local port: 17455 local port: 17456 local port: 17457 local port: 17458 local port: 17460 ------- local port: 17475 local port: 17476 local port: 17477 local port: 17478 local port: 17479 Fri Nov 27 10:53:02 CST 2020 local port: 17997 local port: 17998 local port: 17999 local port: 18000 local port: 18001 +++++++ local port: 18002 local port: 18003 local port: 18004 local port: 18005 local port: 18006 ****** local port: 18010 local port: 18011 local port: 18012 local port: 18013 local port: 18014 ``` 从测试看起来 linux 下端口选择跟时间有关系,起始端口肯定是顺序增加,起始端口应该是在 Ephemeral Port 范围内并且和时间戳绑定的某个值(也是递增的),即使没有使用任何端口,起始端口也会随时间增加而增加。 ### [](#4-19-91-19-1-al7-x86-64 "4.19.91-19.1.al7.x86_64")4.19.91-19.1.al7.x86_64 换个内核版本编译后,执行 (4.19.91-19.1.al7.x86_64): ``` $date; ./client && echo "+++++++" ; ./client && sleep 0.1 ; echo "-------" && ./client && sleep 10; date; ./client && echo "+++++++" ; ./client && sleep 0.1 && echo "******"; ./client; Fri Nov 27 14:10:47 CST 2020 local port: 7890 local port: 7892 local port: 7894 local port: 7896 local port: 7898 +++++++ local port: 7900 local port: 7902 local port: 7904 local port: 7906 local port: 7908 ------- local port: 7910 local port: 7912 local port: 7914 local port: 7916 local port: 7918 Fri Nov 27 14:10:57 CST 2020 local port: 7966 local port: 7968 local port: 7970 local port: 7972 local port: 7974 +++++++ local port: 7976 local port: 7978 local port: 7980 local port: 7982 local port: 7984 ****** local port: 7988 local port: 7990 local port: 7992 local port: 7994 local port: 7996 ``` 以上测试时的参数 ``` $cat /proc/sys/net/ipv4/ip_local_port_range 1024 65535 ``` 将 1024 改成 1025 后,分配出来的都是奇数端口了: ``` $cat /proc/sys/net/ipv4/ip_local_port_range 1025 1034 $./client local port: 1033 local port: 1025 local port: 1027 local port: 1029 local port: 1031 local port: 1033 local port: 1025 local port: 1027 local port: 1029 local port: 1031 local port: 1033 local port: 1025 local port: 1027 local port: 1029 local port: 1031 ``` 之所以都是偶数端口,是因为 port_range 从偶数开始, 每次从 ++ 变到 + 2 的[原因](https://github.com/plantegg/linux/commit/1580ab63fc9a03593072cc5656167a75c4f1d173),connect 挑选随机端口时都是在起始端口的基础上 + 2,而 bind 挑选随机端口的起始端口是系统 port_range 起始端口 + 1(这样和 connect 错开),然后每次仍然尝试 + 2,这样 connect 和 bind 基本一个用偶数另外一个就用奇数,一旦不够了再尝试使用另外一组 ``` $cat /proc/sys/net/ipv4/ip_local_port_range 1024 1047 $./bind & ---bind程序随机挑选5个端口 port number 1039 port number 1043 port number 1045 port number 1041 port number 1047 --用完所有奇数端口 $./bind & --继续挑选偶数端口 [8] 4170 port number 1044 port number 1042 port number 1046 port number 0 --实在没有了 port number 0 ``` 可见 4.19 内核下每次 port 是 + 2,在 3.10 内核版本中是 + 1. 并且都是递增的,同时即使 port 不使用,也会随着时间的变化这个起始 port 增大。 Port Range 有点像雷达转盘数字,时间就像是雷达上的扫描指针,这个指针不停地旋转,如果这个时候刚好有应用要申请 Port,那么就从指针正好指向的 Port 开始向后搜索可用 port [](#tcp-max-tw-buckets "tcp_max_tw_buckets")tcp_max_tw_buckets -------------------------------------------------------------- tcp_max_tw_buckets: 在 TIME_WAIT 数量等于 tcp_max_tw_buckets 时,新的连接断开不再进入 TIME_WAIT 阶段,而是直接断开,并打印 warnning. 实际测试发现 在 TIME_WAIT 数量等于 tcp_max_tw_buckets 时 新的连接仍然可以不断地创建和断开,这个参数大小不会影响性能,只是影响 TIME_WAIT 数量的展示(当然 TIME_WAIT 太多导致 local port 不够除外), 这个值设置小一点会避免出现端口不够的情况 tcp_max_tw_buckets - INTEGER Maximal number of timewait sockets held by system simultaneously.If this number is exceeded time-wait socket is immediately destroyed and warning is printed. This limit exists only to prevent simple DoS attacks, you _must_ not lower the limit artificially, but rather increase it (probably, after increasing installed memory), if network conditions require more than default value. [](#SO-LINGER "SO_LINGER")[SO_LINGER](https://notes.shichao.io/unp/ch7/) ------------------------------------------------------------------------ SO_LINGER 选项**用来设置延迟关闭的时间,等待套接字发送缓冲区中的数据发送完成**。 没有设置该选项时,在调用 close() 后,在发送完 FIN 后会立即进行一些清理工作并返回。 如果设置了 SO_LINGER 选项,并且等待时间为正值,则在清理之前会等待一段时间。 如果把延时设置为 0 时,Socket 就丢弃数据,并向对方发送一个 `RST` 来终止连接,因为走的是 RST 包,所以就不会有 `TIME_WAIT` 了。 This option specifies how the `close` function operates for a connection-oriented protocol (for TCP, but not for UDP). By default, `close` returns immediately, but ==if there is any data still remaining in the socket send buffer, the system will try to deliver the data to the peer==. SO_LINGER 有三种情况 1. l_onoff 为 false(0), 那么 l_linger 的值没有意义,socket 主动调用 close 时会立即返回,操作系统会将残留在缓冲区中的数据发送到对端,并按照正常流程关闭 (交换 FIN-ACK),最后连接进入`TIME_WAIT`状态。**这是默认情况** 2. l_onoff 为 true(非 0), l_linger 为 0,主动调用 close 的一方也是立刻返回,但是这时 TCP 会丢弃发送缓冲中的数据,而且不是按照正常流程关闭连接(不发送 FIN 包),直接发送`RST`,连接不会进入 time_wait 状态,对端会收到 `java.net.SocketException: Connection reset`异常 3. l_onoff 为 true(非 0), l_linger 也为非 0,这表示 `SO_LINGER`选项生效,并且超时时间大于零,这时调用 close 的线程被阻塞,TCP 会发送缓冲区中的残留数据,这时有两种可能的情况: * 数据发送完毕,收到对方的 ACK,然后进行连接的正常关闭(交换 FIN-ACK) * 超时,未发送完成的数据被丢弃,连接发送`RST`进行非正常关闭 ``` struct linger { int l_onoff; /* 0=off, nonzero=on */ int l_linger; /* linger time, POSIX specifies units as seconds */ }; ``` ### [](#NIO下设置-SO-LINGER-的错误案例 "NIO下设置 SO_LINGER 的错误案例")NIO 下设置 SO_LINGER 的错误案例 在使用 NIO 时,最好不设置`SO_LINGER`。比如 Tomcat 服务端接收到请求创建新连接时,做了这样的设置: ``` SocketChannel.setOption(SocketOption.SO_LINGER, 1000) ``` `SO_LINGER`的单位为`秒`!在网络环境比较好的时候,例如客户端、服务器都部署在同一个机房,close 虽然会被阻塞,但时间极短可以忽略。但当网络环境不那么好时,例如存在丢包、较长的网络延迟,buffer 中的数据一直无法发送成功,那么问题就出现了:`close会被阻塞较长的时间,从而直接或间接引起NIO的IO线程被阻塞`,服务器会不响应,不能处理 accept、read、write 等任何 IO 事件。也就是应用频繁出现挂起现象。解决方法就是删掉这个设置,close 时立即返回,由操作系统接手后面的工作。 这时会看到如下连接状态 ![](https://plantegg.github.io/images/951413iMgBlog/image-20220721100246598.png) 以及对应的堆栈 ![](https://plantegg.github.io/images/951413iMgBlog/image-20220721100421130.png) 查看其中一个 IO 线程等待的锁,发现锁是被 HTTP 线程持有。这个线程正在执行`preClose0`,就是在这里等待连接的关闭 ![](https://plantegg.github.io/images/951413iMgBlog/image-20220721100446521.png) 每次 HTTP 线程在关闭连接被阻塞时,同时持有了`SocketChannelImpl`的对象锁,而 IO 线程在把这个连接移除出它的 selector 管理队列时,也要获得同一个`SocketChannelImpl`的对象锁。IO 线程就这么一次次的被阻塞,悲剧的无以复加。有些 NIO 框架会让 IO 线程去做 close,这时候就更加悲剧了。 **总之这里的错误原因有两点:1)网络状态不好;2)错误理解了 l_linger 的单位,是秒,不是毫秒。 在这两个原因的共同作用下导致了数据迟迟不能发送完毕,l_linger 超时又需要很久,所以服务会出现一直阻塞的状态。** [](#为什么要有-time-wait-状态 "为什么要有 time_wait 状态")为什么要有 time_wait 状态 -------------------------------------------------------------- TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request. ![](https://plantegg.github.io/images/951413iMgBlog/image-20220721093116395.png) [](#短连接的开销 "短连接的开销")短连接的开销 -------------------------- 用 ab 通过短连接走 lo 网卡压本机 nginx,CPU0 是 ab 进程,CPU3/4 是 Nginx 服务,可以看到 si 非常高,QPS 2.2 万 ![](https://plantegg.github.io/images/951413iMgBlog/image-20220627154822263.png) 再将 ab 改用长连接来压,可以看到 si、sy 都有下降,并且 si 下降到短连接的 20%,QPS 还能提升到 5.2 万 ![](https://plantegg.github.io/images/951413iMgBlog/image-20220627154931495.png) [](#一条连接的开销 "一条连接的开销")[一条连接的开销](https://mp.weixin.qq.com/s/BwddYkVLSYlkKFNeA-NUVg) ---------------------------------------------------------------------------------- 主要是内存开销 (如图,来源见水印),另外就是每个连接都会占用一个文件句柄,可以通过参数来设置:fs.nr_open、nofile(其实 nofile 还分 soft 和 hard) 和 fs.file-max ![](https://plantegg.github.io/images/951413iMgBlog/640-20220413134252639) 从上图可以看到: * 没有收发数据的时候收发 buffer 不用提前分配,3K 多点的内存是指一个连接的元信息数据空间,不包含传输数据的内存 buffer * 客户端发送数据后,会根据数据大小分配 send buffer(一般不超过 wmem,默认 kernel 会根据系统内存压力来调整 send buffer 大小) * server 端 kernel 收到数据后存放在 rmem 中,应用读走后就会释放对应的 rmem * rmem 和 wmem 都不会重用,用时分配用完释放 可见,内核在 socket 内存开销优化上采取了不少方法: * 内核会尽量及时回收发送缓存区、接收缓存区,但高版本做的更好 * 发送接收缓存区最小并一定不是 rmem 内核参数里的最小值,实际大部分时间都是 0 * 其它状态下,例如对于 TIME_WAIT 还会回收非必要的 socket_alloc 等对象 [](#可用-local-port-不够导致对端time-wait-连接重用进而卡顿案例 "可用 local port 不够导致对端time_wait 连接重用进而卡顿案例")[可用 local port 不够导致对端 time_wait 连接重用进而卡顿案例](https://ata.alibaba-inc.com/articles/251853) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A 进程选择某个端口,并设置了 reuseaddr opt(表示其它进程还能继续用这个端口),这时 B 进程选了这个端口,并且 bind 了,如果 A 进程一直不释放这个端口对应的连接,那么这个端口会一直在内核中记录被 bind 用掉了(能 bind 的端口 是 65535 个,四元组不重复的连接你理解可以无限多),这样的端口越来越多后,剩下可供 A 进程发起连接的本地随机端口就越来越少了,这时会造成新建连接的时候这个四元组高概率重复,一般这个时候对端大概率还在 time_wait 状态,会忽略掉握手 syn 包并回复 ack ,进而造成建连接卡顿的现象 [](#结论 "结论")结论 -------------- * 在内存、文件句柄足够的话一台服务器上可以创建的 TCP 连接数量是没有限制的 * SO_REUSEADDR 主要用于快速重用 TIME_WAIT 状态的 TCP 端口,避免服务重启就会抛出 Address Already in use 的错误 * SO_REUSEPORT 主要用来解决惊群、性能等问题 * 全局范围可以用 net.ipv4.tcp_max_tw_buckets = 50000 来限制总 time_wait 数量,但是会掩盖问题 * local port 的选择是递增搜索的,搜索起始 port 随时间增加也变大 [](#参考资料 "参考资料")参考资料 -------------------- [https://segmentfault.com/a/1190000002396411](https://segmentfault.com/a/1190000002396411) [linux 中 TCP 的 socket、bind、listen、connect 和 accept 的实现](https://blog.csdn.net/a364572/article/details/40628171) [How Linux allows TCP introspection The inner workings of bind and listen on Linux.](https://ops.tips/blog/how-linux-tcp-introspection/) [https://idea.popcount.org/2014-04-03-bind-before-connect/](https://idea.popcount.org/2014-04-03-bind-before-connect/) [TCP 连接中客户端的端口号是如何确定的?](https://mp.weixin.qq.com/s/C-Eeoeh9GHxugF4J30fz1A) [对应 4.19 内核代码解析](https://github.com/plantegg/linux/commit/9b3312bf18f6873e67f1f51dab3364c95c9dc54c) [How to stop running out of ephemeral ports and start to love long-lived connections](https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections/)
Jonny
2022年12月4日 12:36
160
0 条评论
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
如遇文档失效,可评论告知,便后续更新!
【腾讯云】2核2G云服务器新老同享 99元/年,续费同价
【阿里云】2核2G云服务器新老同享 99元/年,续费同价(不要✓自动续费)
【腾讯云】2核2G云服务器新老同享 99元/年,续费同价
【阿里云】2核2G云服务器新老同享 99元/年,续费同价(不要✓自动续费)
Markdown文件
Word文件
PDF文档
PDF文档(打印)
分享
链接
类型
密码
更新密码
有效期