目录
【2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
UDP:无连接,不可靠,速度快
TCP:面向连接,可靠,速度慢
一、TCP/IP模型和OSI模型比较
1.1 相同点
两者都是以协议栈的概念为基础
协议栈中的协议彼此相互独立
下层对上层提供服务
1.2 不同点
OSI是先有模型;TCP/IP是先有协议,后有模型
OSI适用于各种协议栈;TCP/IP只适用于TCP/IP网络
层次数量不同
二、传输层
IP层:提供点到点的连接
传输层的作用:提供端到端的连接
不同的应用程序监听不同的端口,通过不同的端口号来区别不同的应用进程
网络通信四元组:源ip、目的ip、源端口、目的端口(五元组:+协议)
2.1 常见的应用进程对应的端口号
nginx : 80
mysql : 3306
ssh : 22
dns : 53
QQ:8000
2.2 TCP
Transmission Control Protocol 传输控制协议
可靠的、面向连接的、基于字节流的传输层通信协议
密文传输
传输效率低
类似:打电话
三次握手,四次断开(或挥手):体现的是面向连接的特点
传输效率低:面向连接,总是需要确认
协议 | 端口 | 说明 |
telnet | 23 | 远程控制的,如果明文传输,不安全 |
ssh | 22 | |
http | 80 | |
ftp | 21 | |
dns | 53 | dns的主从服务器之间的数据传输 |
SMTP | 25 | 发送邮件 |
pop3 | 110 | 收取邮件的 |
mysql | 3306 |
查看各种服务的默认端口:cat /etc/services
2.3 UDP
User Datagram Protocol 用户数据报协议
不可靠的(尽最大努力交付)、无连接、面向报文的服务
明文传输
传输效率高
类似:发短信
三、TCP
3.1 TCP的封装格式
端口号的范围:0 ~ 65535。分给服务器端:0~1023;登记端口号:1024~49151;客户端暂时使用的:49152~65535。(2^16=65536,因为端口号占16位)
序列号:数据段标记,用于到目的端对到达包的重组
- 序列号为什么不从1开始?
> 安全性:采用随机产生的初始化序列号进行请求,这样做主要是出于网络安全的因素着想。如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号,并且伪造序列号进行攻击,这已经成为一种很常见的网络攻击手段。
- 序列号回绕?
> 描述:当序列号始终递增增长,序列号有范围(最高为2^32),当到达尽头时,序列号会从0开始 => 无法判断哪个是以前的旧包、哪个是新包。
> 解决:在选项中加入时间戳timestamp,以确认数据包的先后顺序,也能用来计算往返时间
确认号:对发送端的确认信息,告诉发送端这个序号之前的数据段都收到了
16位窗口大小:滑动窗口的大小,指明本地可接收数据的字节数==》TCP通过滑动窗口的大小来做流量控制。
3.1.2 6个控制位
URG(urgent):紧急指针有效位,与16位紧急指针配合使用。URG=1时,表示这是一个紧急数据,需要尽快发出
ACK(acknowledgement):ACK=1时,确认号字段才有效,表明该数据包包含确认信息。建立连接后,ACK一直为1。
PSH(push):通知接收端立即将数据提交给用户进程,不在缓存中停留,等待更多数据
RST(reset):RST=1时,请求重新建立TCP连接
SYN(sync):SYN=1时,请求建立连接
FIN(finish):FIN=1时,数据发送完毕,请求断开连接
3.2 TCP的连接—三次握手
最后一次的回包不占用序列号:一般不需要回复的包基本不占用序列号
(带seq,两次的seq相同<不占用seq>:ACK包不携带数据的情况)
telnet:测试tcp连接的工具
telnet 14.215.177.38 80 和百度建立tcp连接
tcpdump -i ens33 port 800 -v
必须三次,两次不行,两次就不可靠了 ==> 三次握手才能确定两方都已经准备好了,两次握手只是客户端确定服务端收到了数据包,客户端有可能没有收到服务端发送到SYN+ACK包,但服务端又已经发送了,那么服务端会认为我发了,客户端认为服务端没有发送,最后一直等着形成死锁。
三次握手封装数据段里是否有源端口和目的端口?==> 有
3.2.1 简单介绍一下三次握手是个怎样过程
假设现在有客户机A和web服务器B,客户机A会封装封装好数据,因为web服务在传输层是采用的TCP封装格式,所以A在封装数据时,会有一个序列号,现在假设这个序列号seq=x,又因为现在是发送的第一个请求报文,所以控制位SYN=1,表示客户机请求和web服务器建立连接,源端口为客户机A的浏览器随机产生的端口,目的端口为80。
web服务器收到请求报文后,解封装,发现目的IP符合且目的端口开放,决定回复。web服务器进行封装数据,假设序列号seq=y,同时要表示已经收到了客户机A发来的数据包,确认号ack=x+1,表示收到了101以前的数据。此外,web服务器B也要和客户机A建立连接,所以SYN=1,且这是一个含有确认号的报文,所以控制位ACK=1。
客户机A收到后,解封装,看到确认号,知道之前的数据web服务器B已收到,同时客户机A要给web服务器B回复,确认号ack=y+1,并令控制位ACK=1。
web服务器B收到后,A和B成功建立连接。
3.2.2 当服务器未收到最后一个ack包
超时重传计时器:ack包迟迟未收到,服务器端检测到超时
SYN+ACK包超时重传的次数:默认5次
[root@nginx-kafka01 ~]# cd /proc/sys/net/ipv4
# syn包
[root@nginx-kafka01 ipv4]# cat tcp_syn_retries
6
# SYN+ACK包
[root@nginx-kafka01 ipv4]# cat tcp_synack_retries
5
修改内核参数(永久生效):SYN+ACK包重传次数
查看当前内核参数sysctl -a
[root@nginx-kafka01 ipv4]# vim /etc/sysctl.conf
# 添加net.ipv4.tcp_synack_retries=7
# 将SYN+ACK宝的超时重传次数修改为7
# net.ipv4.tcp_synack_retries由proc后面的地址而定
[root@nginx-kafka01 ipv4]# cd /proc/sys/net/ipv4
[root@nginx-kafka01 ipv4]# cat tcp_synack_retries
5
[root@nginx-kafka01 ipv4]# sysctl -p
net.ipv4.tcp_synack_retries = 7
[root@nginx-kafka01 ipv4]# cat tcp_synack_retries
7
3.2.3 syn flood
>> 半连接队列满了
服务器启动的时候会初始化两个队列:
- 半连接队列 syn queue
服务器将连接状态为syn_recvd的连接信息放到半连接队列
- 全连接队列 accept queue
服务进入establish状态
/var/log/dmesg日志可以查看到syn flood攻击
出现这个日志的原因
- 半连接队列设置过小
- 遭受到ddos攻击
syn cookie 可以解决这种问题
[root@nginx-kafka01 ipv4]# cat tcp_max_syn_backlog
128
> 修改内核参数(永久生效):半连接队列大小
[root@nginx-kafka01 ipv4]# vim /etc/sysctl.conf
# 添加net.ipv4.tcp_max_syn_backlog=256
# 具体大小根据各自的情况定,此处只是展示修改方法
> 启用syn cookies:/etc/proc/type -tcp_suncookies:
3.2.5 MTU、MSS
MTU
最大传输单元:1500字节
MSS
TCP最大报文长度 -- 传输层一次最大可以传输多少数据(不包括头部字段)
因为有MSS,tcp包一般在网络层是不需要分片的
1500-ip头(20字节)-tcp头(>=20字节)
3.3 查看网络连接状态
可以用ss或者netstat,来查看套接字、网络栈、网络接口以及路由表的信息。推荐使用ss来查询网络的连接信息,因为它比netstat提供了更好的性能(速度更快)
3.3.1 netstat:查看本机开放了哪些端口
[root@localhost ~]# netstat -anplut
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1051/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 1215/master
tcp 0 0 192.168.254.129:52362 210.28.130.3:80 TIME_WAIT -
tcp 0 36 192.168.254.129:22 192.168.254.1:61382 ESTABLISHED 1334/sshd: root@pts
udp 0 0 0.0.0.0:68 0.0.0.0:* 861/dhclient
udp6 0 0 ::1:323 :::* 670/chronyd
-a ==》 all
-n ==》 number 以数字的形式显示端口号,无需进行域名解析,速度更快
[root@localhost ~]# netstat -aplut
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN 1051/sshd
tcp 0 0 localhost:smtp 0.0.0.0:* LISTEN 1215/master
-p ==》 program 程序的名字
-l ==》listening
-t ==》 TCP
-u ==》UDP
[root@localhost ~]# less /etc/services
记录哪些服务使用哪些端口:熟知的端口和登记的端口
如果没接n就会到这个文件里查找端口对应的名字,接了n就不会查,所以速度更快。
【各字段的意思】
Proto 协议
Local Address 本机IP地址和端口
Foreign Address 外部连接的IP地址和端口
State 当前的状态:ESTABLISHED==》已连接;LISTEN==》未连接
PID/Program name 访问的进程号和进程名字
Recv-Q 接收到的队列 Q:queue
Send-Q 发送的队列
Recv-Q
Established: The count of bytes not copied by the user program connected to this socket.
连接到此套接字的用户程序未复制的字节数。==》内核空间里的socket队列里还有多少数据没有被用户空间里的进程复制(取)走
如果数值大,就说明应用程序非常忙,处理不过来了
Listening: Since Kernel 2.6.18 this column contains the current syn backlog.
Kernel 2.6.18这个队列里包含当前积压的同步包。
如果数值大,就说明内核非常忙,处理不过来了
syn backlog 是 TCP 协议栈中的半连接队列长度
Send-Q
Established: The count of bytes not acknowledged by the remote host.
远端主机未确认的字节数
Listening: Since Kernel 2.6.18 this column contains the maximum size of the syn backlog.
Kernel 2.6.18这个队列里包含同步包积压的最大大小。
注意
接收队列(Recv-Q)和发送队列(Send-Q)通常应该是0,当不为0时,说明有网络包的堆积发生。
socket 套接字:ip+port 如:192.168.0.1:80 ==》一个程序占用一个ip地址的端口号
访问这个套接字就是访问一个程序
3.3.2 ss
ss命令查看全连接队列的大小和当前等待accept的连接个数
[root@nginx-kafka01 ipv4]# ss -lnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:80 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 *:2299 *:*
LISTEN 0 128 [::]:80 [::]:*
LISTEN 0 100 [::1]:25 [::]:*
LISTEN 0 128 [::]:2299 [::]:*
对于LISTEN状态的套接字,Recv-Q表示accept队列排队的连接个数,Send-Q表示全连接队列(也就是accept队列)的总大小
如以上代码: Send-Q的值是100的行,当行对应listen的端口上的全连接队列最大为100,第一列Reccv-Q为全连接队列当前使用了多少。全连接队列的大小取决于:min(backlog,somaxconn)。backlog是在socket创建的时候传入的参数;somaxconn是一个os级别的系统参数。
3.3.3 nc
扫描嗅探其他的机器开放了哪个端口
yum install nc -y
3.3.4 nmap
探测一个机器或者整个局域网里机器开放了哪些端口
yum install nmap -y
3.4 TCP的四次挥手
(1)假设HostA请求和HostB断开连接。HostA将发送数据包,序列号seq=x,确认号ack=y,控制位FIN=1,ACK=1。
(2)HostB收到后进行回复,发送数据包,seq=y,ack=x+1,控制位ACK=1。
(3)把余下的数据传输完毕后。HostB会发一个数据包,seq=v(,ack=x+1,控制位ACK=1,FIN=1。
(4)HostA收到两个数据包后,会进行确认,回复一个数据包,seq=x+1,ack=y+1,ACK=1。两台主机顺利断开
【网络状态】
TIME-WAIT理论上最长4分钟
3.4.1 常见问题
【为什么要等待2MSL】
MSL:最长报文寿命
当网络不稳定,假设A传出的最后的确认包丢失,那服务器B会重传包,若等待时间太短,A直接关闭,就无法给B回复,服务器会重传多次,需要等待很久。若等待时间为2MSL,那A就还可以给B回复,减少服务器的等待时间。
【服务器TIME-WAIT特别多】
只有客户端会出现time-wait过多的情况;nginx作为反向代理,及时客户端又是服务器,就有可能出现time-wait过多的情况
(1)访问量大
(2)说明有大量连接需要断开==》用户没有发起新的请求:用户没有对网站进行操纵,或已经离开
如何尽量处理TIMEWAIT过多的情况?--》如何timewait等待的这段时间发挥更加大的作用,不然就白白的在等待,浪费了时间和资源
- 加机器
- 防火墙 nginx limit限制
- 修改内核参数:见下
【内核参数调优】
编辑内核文件/etc/sysctl.conf,加入以下内容(内核参数调优):
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
然后执行 /sbin/sysctl -p 让参数生效.
/etc/sysctl.conf是一个允许改变正在运行中的Linux系统的接口,它包含一些TCP/IP堆栈和虚拟内存系统的高级选项,修改内核参数永久生效。
简单来说,就是打开系统的TIMEWAIT重用和快速回收。
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系默认的 TIMEOUT 时间
【阿里云用到的】
root@iZwz9hqlrezjnsz3968bz2Z:~# sysctl -p ==》从配置文件“/etc/sysctl.conf”加载内核参数设置
vm.swappiness = 0
kernel.sysrq = 1
net.ipv4.neigh.default.gc_stale_time = 120
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
#arp协议的调优
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.default.arp_announce = 2
参考资料:https://www.cnblogs.com/chia/p/7799071.html
对网络接口上本地IP地址发出的ARP报文作出相应级别的限制。
0:本机所有IP地址都向任何一个接口通告ARP报文。
1:尽量仅向该网卡回应与该网段匹配的ARP报文。
2:只向该网卡回应与该网段匹配的ARP报文。
===
#tcp的三次握手和四次断开的调优
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_slow_start_after_idle = 0
root@iZwz9hqlrezjnsz3968bz2Z:~#
【自己用到的】
[root@slave-mysql ~]# sysctl -p
net.ipv4.ip_forward = 1
net.ipv4.tcp_tw_reuse = 1
vm.swappiness = 0 尽量使用物理内存,当物理内存使用完了,再使用交换分区
[root@slave-mysql ~]#
【服务器LAST-ACK多】
可能是有人恶意攻击服务器,让服务器给他发包,消耗服务器资源
3.5 TCP流控机制—拥塞控制
Cwnd:拥塞窗口
中间链路带宽比较窄,传输时窗口大小改变为拥塞窗口的大小
拥塞窗口值是用来给滑动窗口作为参考值的
发送方窗口的大小 = min(拥塞窗口,接受方发送过来的win值)
3.5.1 ⭕拥塞控制四大算法
1)慢启动;
2)拥塞避免:经过一个RTT时间就增加1
3)拥塞发生(快重传):A发送给B的2号包丢失,A不知道,继续发送3、4、5号包,B一直没有收到2号包,收到3/4/5号包时,均会发送ack2包,这时A就收到了3个重复的ACK包,就会执行快重传。
4)快速恢复
3.6 差错控制:可靠性
超时——计时器
3.7 计时器
重传计时器:
为了控制丢失的数据段
如果重传计时器超时了,还未收到确认,那就会重传数据,并让重传计时器重新计时
坚持计时器:
为了防止零窗口死锁
当主机B传给主机A的包里提示滑动窗口=0,主机A就不会给B发送数据,开启坚持计时器,在超时之前B可能会发送新的包过来,提醒A又可以传输数据了。如果这个传来的数据包丢失了,坚持计时器超时,A会发动探测数据段,探测B是否可以重新接收数据。
如果没有坚持计时器,和探测数据段,当B后来传来的数据丢失时,双方就会进入等待死锁状态,A在等待B的确认包,B在等待A之后的数据。
保活计时器:
防止两个TCP之间的连接长时间空闲
每隔一段时间就会发送探测数据段,如果接连几次(一般是10次)对方没有响应就断开连接
时间等待计时器:
连接终止期间使用的
在发送了最后一个ACK后,不立即关闭连接,而是等待一段时间,保证如果传来的FIN数据段丢失,能接收到重传的FIN数据段。
3.8 滑动窗口
3.8.1 引入
- 停止等待协议:
发送完一个分组就停止发送,等待对方确认,在收到确认后再发送下一个分组.
- 连续ARQ协议:
发送方维持着一个一定大小的发送窗口,位于发送窗口内的所有分组都可连续发送出去,而中途不需要等待对方的确认。这样信道的利用率就提高了。而发送方每收到一个确认就把发送窗口向前滑动一个分组的位置。-- 累计确认
如果发送方发送了前5个分组,而中间的第3个分组丢失了。这时接收方只是对前两个分组发出确认。发送方无法知道后面三个分组的下落,而只好把后面的三个分组都再重传一次。这就叫做Go-back-N(回退N),表示需要再退回来重传已发送过的N个分组。
包含回退n帧协议(网上观点很多,还有一种说法,认为连续ARQ协议包括了退回n帧协议和选择重传协议,此处我们认为只包含回退N帧协议)
- 选择重传协议:
tcp_ack=1,表示开启了选择重传协议:会记录有哪些包到了哪些没到,不需要重传丢失包及其之后的所有包。
[root@nginx-kafka01 ipv4]# cat tcp_sack
1
[root@nginx-kafka01 ipv4]# pwd
/proc/sys/net/ipv4
====举例
假设A→B,A发送了1-5共5个数据包,1,、2、4、5顺利到达B,3丢失
连续 ARQ协议:A重传3、4、5
选择重传协议:A重传3
为什么会引入滑动窗口呢?
如果不存在发送窗口的话,TCP发送一个数据包后会等待ACK包,因为必须要保存对应的数据包,数据包很有可能需要重新发送。
这样的话发送效率会很慢。大部分时间都在等待
【接受数据的过程】
【发送数据的过程】
3.8.2 详解
tcp基于连续arp协议和滑动窗口协议
发送窗口存在于操作系统中开辟的一块缓冲区,用来存放当前需要发送的数据。本质是一个循环数组的实现。利用三个指针来维护相关的区域。
指针1: 指向第一个已发送但是未接收到ack的字节
指针2: 指向第一个允许发送但是还未发送的字节
指针3: 发送窗口的大小
接收窗口也是存在于操作系统中开辟的一块缓冲区,用于接收数据。缓冲区本质是一个循环数组的实现。利用两个指针来维护相关的区域。
滑动窗口都是成对存在的:发送方的窗口和接收方的窗口
tcp通过滑动窗口进行流量控制
四、UDP
UDP首部只占8个字节。
使用UDP的常见协议及其端口:
端口 | 协议 | 说明 |
69 | TETP | 简单文件传输协议 |
53 | DNS | 域名服务 |
123 | NTP | 网络时间协议 |
111 | RPC | 远程过程调用 |
8000 | |
UDP没有流控机制、拥塞控制
UDP只有校验和来提供差错控制==》需要上层协议来提供差错控制:如TFTP协议
五、TCP/IP协议栈
六、常见问题
【1】为什么连接的时候是三次握手,关闭的时候却是四次挥手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。 现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
【5】TCP怎么保证可靠?
- 校验和
- 确认应答序列号:接收方收到数据包后,会给发送方发送一个确认包(序列号:保证数据包不会乱序,也可以保证收到每一个包)
- 各种计时器:如,超时重传
- 连接管理:三次握手、四次挥手
- 流量控制
- 拥塞控制