Slide 1

Slide 1 text

Linux服务器端口的那些事 了解端口的用途和局限,以及如何突破端口限制 聂永@2014/06/26

Slide 2

Slide 2 text

目录 目录 IP和协议端口 网络四元组、二元组 突破端口限制 代码绑定IP和端口 外部程序绑定IP和端口 地址和TIME_WAIT 服务器地址重用和释放 演示和用途

Slide 3

Slide 3 text

数据进入栈封装过程

Slide 4

Slide 4 text

IP地址位置 IP报文 IP地址属于IP数据报,标识源主机和目标主机

Slide 5

Slide 5 text

协议端口在TCP数据包中位置  只有TCP数据包头部中包含端口  端口号和TCP服务器进程存在映射关系  端口,具有数据包路由作用 TCP传输层根据端口路由数据包到服务器程序所申请的缓存队列中

Slide 6

Slide 6 text

协议端口用途  一台服务器好比一座院子  IP地址比作院子大门;一座院子对应至少一个大门  每一个具体的服务程序进程好比一个房子  端口号好比进入房间的门  一个房间至少一个门 服务器程序可以决定由哪一个大门才能进入房间  以太网帧根据IP地址决定投递主机位置,端口决定数据报送达应用程 序映射  服务器内核接收的数据将根据端口进行路由传递到对应程序中

Slide 7

Slide 7 text

协议端口的一些常识  16位表示  范围:从0 到65535(2^16-1)  已知端口号[0,1023],这些端口由IANA分配和控制 Linux下需要root权限才能使用  [1024,65535],可自由使用端口数量不超过64512个 但不要和已有服务进行冲突

Slide 8

Slide 8 text

UNP对网络四元组的解释  《UNIX网络编程卷1:套接字联网API(第3版)》(简称UNP)解 释:  一个TCP连接的套接字对(socket pari)是一个定义该连接的两个端点 的四元组,即本地IP地址、本地TCP端口号、外地IP地址、外地TCP 端口号。套接字对唯一标识一个网络上的每个TCP连接。  ......  标识每个端点的两个值(IP地址和端口号)通常称为一个套接字。

Slide 9

Slide 9 text

四元组解释  仅针对TCP  IP用于标识通讯的机器,端口号用于标识通信的进程。  网络四元组,一个完整TCP连接参阅双方体现: {请求者本机IP地址,请求者本机端口,目标IP地址,目标端口}  一次TCP请求必须把自身IP、PORT以及目标IP、目标PORT都要携 带上

Slide 10

Slide 10 text

Linux系统端口范围  默认范围:1~65535  0-1023已知端口,已被分配,需要ROOT权限  可被自由使用端口范围:65535 - 1024 ≈ 64000  查看Linux系统默认可使用端口范围: sysctl -a | grep 'net.ipv4.ip_local_port_range' 32768 61000  修改其端口范围: sysctl -w net.ipv4.ip_local_port_range=1024 65535  保存修改: sysctl -p 持久化

Slide 11

Slide 11 text

如何突破端口数量限制  默认情况下,系统对外发出的请求数量受限于可用端口数量!  网络四元组中{本机IP,本机Port}两元组表示一个套接字  一个套接字表示系统可以向外请求的一个完整的TCP连接  套接字的数量表示系统可对外请求的完整TCP连接数量!  本机可用TCP套接字数量 = 本机IP数量 * 本机可用端口数量  绑定10个IP地址,自由可用端口约64000,那么可用端口数量 10 * 64000 = 640000,64万!

Slide 12

Slide 12 text

其它受限条件  打开文件句柄的限制,必须要大于可用端口数量  系统内存限制  每一个连接最少占用4K内存,连接数*4K = 需要的内存数,要求系 统内存要能够完全满足  线程/IO模型,非阻塞型:同步非阻塞/异步非阻塞,不用占满CPU  系统绑定的物理网卡数量受限

Slide 13

Slide 13 text

虚拟IP来帮忙  添加物理网卡,费时费劲,不太方便  可分配一个或多个IP地址给系统的虚拟IP,很方便!  Linux添加虚拟IP: ifconfig eth0:0 192.168.190.151 netmask 255.255.255.0 up 写入/etc/rc.local里可防止重启后丢失  试试Docker 已经释出Docker 1.0,可以尝试一下

Slide 14

Slide 14 text

代码指定IP和端口  C语言如何做 struct sockaddr_in clnt_addr; clnt_addr.sin_addr.s_addr = inet_addr(server_ip); clnt_addr.sin_port = htons(server_port);  Java如何做 java.net.InetSocketAddress 此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对 (主机名 + 端口号),在此情况下,将尝试解析主机名。 如果解析失败,则该地址将被视为未解析地址,但是其在某些情形下 仍然可以使用,比如通过代理连接。  服务器程序可以绑定所有可用IP地址  客户端请求者只能绑定一个具体IP(若非手动指定,系统默认选择可 用IP)

Slide 15

Slide 15 text

代码示范  服务器端示范程序,绑定IP和端口 https://gist.github.com/yongboy/37875ad2476b0a8cab90  客户端示范程序,可绑定所有可用IP和端口,对外发出海量请求 https://gist.github.com/yongboy/5476214 后面会演示

Slide 16

Slide 16 text

指定已有程序绑定IP/端口?  已有程序,如何修改其绑定的IP地址和端口 ? 修改配置文件  试试linux系统的bindp小程序吧!  https://github.com/yongboy/bindp  可在运行之前硬绑定的指定IP和端口  原理: LD_PRELOAD,允许定义在程序运行前优先加载的动态链接库  如何使用: BIND_ADDR="your ip" BIND_PORT="your port" LD_PRELOAD=/your_path/bindp.so the real command ...

Slide 17

Slide 17 text

bindp + nginx + curl的示范1  nginx默认80端口, 设置nginx以8888端口进行启动 BIND_PORT="8888" LD_PRELOAD=/home/yongboy/bindp/bindp.so /usr/sbin/nginx -c /etc/nginx/nginx.conf  测试端准备 绑定特定IP和端口: BIND_ADDR="192.168.192.141" BIND_PORT="16888" LD_PRELOAD=/home/yongboy/bindp/bindp.so curl http://192.168.192.130  nginx日志输出:

Slide 18

Slide 18 text

bindp + nginx + curl的示范2  测试端第一次请求 BIND_ADDR="192.168.192.141" BIND_PORT="16888" LD_PRELOAD=/home/yongboy/bindp/bindp.so curl http://192.168.192.130  测试端第二次请求(间隔很短,不超过10秒钟) BIND_ADDR="192.168.192.141" BIND_PORT="16888" LD_PRELOAD=/home/yongboy/bindp/bindp.so curl http://192.168.192.130  持续观察Nginx日志输出

Slide 19

Slide 19 text

端口释放以及TIME_WAIT阶段  端口释放发生在四次挥手期间  主动关闭方产生TIME_WAIT  主动方最后一次发送挥手时计算  等待真正关闭

Slide 20

Slide 20 text

占用端口何时释放?  测试端连接虽已关闭,但被占用端口系统不会立即释放  主动关闭方产生TIME_WAIT  TIME_WAIT需要等待分组彻底消失,2倍MSL(Maximum Segment Life), 1MSL = 30s(Linux系统)  sysctl -a | grep net.ipv4.tcp_fin_timeout net.ipv4.tcp_fin_timeout = 60  原因: 1)TIME_WAIT可确保系统有足够时间让对端收到Fin ACK,如果 对方没有收到Fin Ack,会触发重发Fin ACK,2个MSL时间周期 2)系统有足够的时间让已关闭的连接不会跟后面的连接混淆(避免 连接被重用时,可能路由器层面缓存IP数据包,导致混淆) 3)TIME_WAIT下一站是真正的关闭

Slide 21

Slide 21 text

服务器程序地址重用  服务器程序启动时需要显式标注自身需要地址重用  C语言版: int reuseaddr_on = 1; if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on))) err(1, "setsockopt failed");  Java&Netty 版本  Java: ServerSocket socket = new ServerSocket(); socket.setReuseAddress(true);  Netty 3.x bootstrap.setOption("reuseAddress", true);  Netty 4.x/5.x ServerBootstrap server = new ServerBootstrap(); server.group(bossGroup, workerGroup) .option(ChannelOption.SO_REUSEADDR, true);  演示实际效果 ?

Slide 22

Slide 22 text

服务器程序地址重用2  不指定地址重用,在短时间内(60S内)再次重启: server: bind failed: Address already in use  地址重用  可以快速重启  同一端口可以被多次使用,但需要绑定到不同IP上  不同IP绑定同一端口  绑定不存在的IP地址: bind failed: Cannot assign requested address  不指定具体IP,意味着继续使用默认IP,其它进程绑定端口 bind failed: Address already in use  已绑定的指定IP和端口,其它进程再次绑定: bind failed: Address already in use

Slide 23

Slide 23 text

服务器如何快速回收端口?  Linux系统默认值:  net.ipv4.tcp_tw_reuse = 0  net.ipv4.tcp_tw_recycle = 0  默认都是关闭  tcp_tw_recycle,比较激进,若开启需同时设置 tcp_timestamps=1  不建议修改  修改可能会产生意想不到的问题  服务器端尽量不要主动关闭连接

Slide 24

Slide 24 text

Linux一些小工具  通过端口查看进程  lsof -i:port  进程查看端口  netstat –nltp|grep 进程号  SS命令

Slide 25

Slide 25 text

突破端口限制的示范/用途  若有时间,演示从测试机发出超过64000个对外请求  只要机器性能够好,可以发出超过100万个并发长连接请求  绑定IP和端口,突破端口数量限制,用途:  访问代理服务器  内网/外网百万长连接压力测试  ......

Slide 26

Slide 26 text

谢谢聆听!