对于TCP协议栈的一些认识和思考
程序员文章站
2022-05-18 16:01:50
...
1.为什么使用TCP?
1.数据可靠,必达;
2.传输效率不低;
3.顺序传输数据.
2.TCP报文头图示
1.端口号:用来标识同一台计算机的不同的应用进程。
1)源端口:源端口和IP地址的作用是标识报文的返回地址。
2)目的端口:端口指明接收方计算机上的应用程序接口。
TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。
2.序号和确认号:是TCP可靠传输的关键部分。序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。
3.数据偏移/首部长度:4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。
4.保留:为将来定义新的用途保留,现在一般置0。
5.控制位:URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。
1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
5)SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
6.窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小是一个16bit字段,因而窗口大小最大为65535。
7.校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。
8.紧急指针:只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
9.选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。
10.数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。
3.状态图的转变
4.TCP协议栈的实现
我的实现版本:
struct tcphdr{
unsighed short sport;
unsighed short dport;
unsighed int seqnum;
unsighed int acknum;//
unsigned char hdrlen:4,
resv:4;
unsigned char cwr:1,
ece:1,
urg:1,//紧急位
ack:1,//
psh:1,
rst:1,
syn:1,
fin:1;
unsighed short cwnd;
unsighed short check;
unsighed short urg_pointer;
}
struct tcppkt{
struct ethhdr eh;
struct iphdr ip;
struct tcphdr tcp;
unsighed char body[0];
}
内核源码中的实现版本,所在文件include/Linux/tcp.h:
struct tcphdr {
__be16 source; //16位源端口号
__be16 dest; //16位目的端口号
//每个tcp段都包源和目的端口号,用于寻找发送端和接受端的应用进程。
//这两个端口号加上ip报头中的源ip和目的ip,来确定一个唯一的TCP连接。
__be32 seq; //此次发送的数据在整个报文段中的起始字节数。此序号用来标识从tcp发送端向tcp接受端发送的数据字节流,
//seq表示在这个报文段中的第一个数据字节。如果将字节流看做在两个应用程序间的单向流动,
//则tcp用序号对每个字节进行计数。32 bit的无符号数。为了安全起见,它的初始值是一个随机生成的数,
//它到达2的32次方-1后又从零开始。
__be32 ack_seq; //是下一个期望接收的字节,确认序号应当是上次已成功接收的序号+1,只有ack标志为1时确认序号字段才有效。
//一旦一个连接已经建立了,ack总是=1
#if defined(__LITTLE_ENDIAN_BITFIELD) //小端
__u16 res1:4, // 保留位
doff:4, //tcp头部长度,指明了在tcp头部中包含了多少个32位的字。由于options域的长度是可变的,
//所以整个tcp头部的长度也是变化的。4bit可表示最大值15,故15*32=480bit=60字节,所以tcp首部最长60字节
//然后,没有任选字段,正常的长度是20字节
fin:1, //发端完成发送任务
syn:1, //同步序号用来发起一个连接
rst:1, //重建连接
psh:1, //接收方应该尽快将这个报文段交给应用层
ack:1, //一旦一个连接已经建立了,ack总是=1
urg:1, //紧急指针有效
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window; //窗口大小,单位字节数,指接收端正期望接受的字节,16bit,故窗口大小最大为16bit=1111 1111 1111 1111(二进制)
//=65535(十进制)字节
__sum16 check; //校验和校验的是整个tcp报文段,包括tcp首部和tcp数据,这是一个强制性的字段,一定是由发端计算和存储,
//并由收端进行验证。
__be16 urg_ptr;
};
5.思考问题
1.服务器连接那么多客户端,如何做到将对应的包发给对应的客户端?
fd与五元组.
2.状态机?
状态迁移图.
3.一开始的时候窗口大小是多少?
默认是0,也可以赋值,但不论多少,传什么值是没有意义的.
4.如何实现三次握手?
(1)客户端给服务器端发第一次请求;
(2)然后服务端给客户段发确认和申请;
(3)客户端给客户端发确认.
5.为什么不是2次握手或4次握手?
为什么不是两次?服务器没法知道ACK的值;
为什么不是四次?没有必要浪费,三次已经可以了,是协议上被动完成.全双工.
6.半连接队列和全连接队列?
第一次请求连接的时候服务器段需要准备一个syn队列来接受想要连接的客户端信息;
完成三次握手之后,要将队列中的节点移动到accept队列.
syn队列 ---半连接队列
accept队列---全连接队列
7.clientfd = accept(listenfd,adddr);中的accpt做了啥?recv函数又做了啥?
这里的accept做了两件事:
(1)accept队列里面取出一个结点;
(2)分配一个fd,与节点一一对应.
accept的阻塞是通过条件变量来判断的,判断全连接队列中是否有数据,如果没有数据,就阻塞.
recv函数把内存从内核区拷贝到用户区.
8.结点的生命周期是什么?
liten到time_wait
9.tcp的11状态报错在哪里?
保存在tcb这个控制块里面,从liten到time_wait,这个tcb一直存在.
10.fd--结点如何一一对应上的呢?
fd通过五元组判断唯一性.五元组是客户端唯一性的标识.(send如何找到对端,就是通过这个五元组)
11.端口只有65535,为什么同时连接数能够做到上百万?
一个fd对应一个五元组(sip,dip,sport,dport,proto),对于特定的协议来说,就是四元组.
12.什么是syn洪水攻击?如何避免?
syn占着半连接队列,不发送确认请求建立连接,syn攻击会使有效的连接没法进来;
在外层加堡垒机或者防火墙给防住,软件内部很难实现.
13. 如何只发送syn不发送ack?
原生构造包,raw socket.
14.listen(fd,backlog);中的backlog是什么?
可以理解为:
(1)syn队列的长度;(linux2.2版本以前,MAC中是这样的)
(2)syn+accept队列的长度.(linux2.2以后的版本)
15.如何学好tcp?
站在设计者的角度思考如何实现可靠的流式协议.
16.send(fd,buffer,length,0);返回成功代表什么?
send返回0,只是表示把buffer中的数据放到了内核缓冲区中,至于什么时候发,发多少,这个都是由协议栈决定的.
17.内核缓冲区中的包有有哪几种状态?
(1)已发送已确认;
(2)已发送未确认;
(3)允许发送等待被发送.
(4)暂不允许被发送数据.
17.tcp如何保证高效的?
多个包一起发送.
18.什么是滑动窗口?
多个包在网络上走,这个窗口是动态的,有一个开始位置和结束位置.
19.编号1,2,4...25个包,此时25号包收到了ack,这表示什么?
表示25号以前的包都收到了,但是25号包还没有收到.注意,确认的包里会有滑动窗口大小的值.
20.窗口大小是怎么确定的?
是对端发送ack的时候那个包中的window size字段确定的.窗口的起始位置是ack的值,终止位置是cwnd.
21.会间隔着确认吗?如何保证顺序的?
比如说发送端有4,5,6,7,8,9,10,11号包等待发送,然后包到达对端的顺序是4,5,6,7,9,10,11,内核中会有一个定时器,比如说200ms,200ms到了之后会进行包的确认,如果在这个时间内还没有收到8号包,那么此时接收端就会给发送端发送8号确认包,之后9,10,11会和8一起进行重传,这个就是tcp一种保证顺序的一种手段.
22.既然有tcp的可靠传输,为什么也会用udp做可靠传输?
udp做可靠传输的场景
(1)udp做可靠传输,不会带拥塞控制,不会去计算带宽,可用于下载,迅雷会员一开,局域网只有你在用,其他人都上不了,这里的传输效率的提高牺牲了别人的上网条件;
(2)udp的实时性,tcp会有一个延迟确认(为了追求传输效率),upd则牺牲了效率,实现实时性,udp的包是一个包一个包确认,来一个包确认一次.
23.如何计算窗口cwind?什么时候应该变大?什么时候应该变小?
cwind应该是和rtt相关的,如果没有及时回复或者回复的时候没有达到理想的数值,系统可以认为
网络此时是比较拥塞;如果在规定的时间内超额完成里往返任务,系统可以认为此时的网络是比较空闲的.
当系统认为网络比较拥塞的时候,可以让窗口变小点;
当系统认为网络比较空闲的时候,可以让窗口变大点.
比如说计划10m内发送10个包并接受ACK确认包,
如果在10m内完成了这个任务,可以认为此时的网络是比较拥塞的,
如果在10m内没有完成这个任务,可以认为此时的网络是比较空闲的.
这个是Vegas算法采取的策略,RTT增加,拥塞窗口减小;RTT减小,拥塞窗口增大.
24.什么是rtt?
round trip time,一个发送的包从发送出去到接受确认的时间,一次往返的时间.
25.如何计算rtt?
rtt = 0.9*last_rtt + 0.1*cur_rtt,这个公式可以用来消除网络抖动,
比如rtt分别是0.1s,5s(进电梯了),2s,0.2s...,用这个公式就可以来在一定程度上消除一些抖动带来的影响.
26.cwnd与时间的关系是怎样的?
在门限值以下的时候,按照指数增长,是慢启动的过程;到达门限值以后,当出现返回不了数据或者丢包的情况时,开始进行拥塞控制,比如采用窗口大小腰斩.在此过程中,rtt用来计算超时.关于cwnd与时间的经典关系图中的duplicated acks(重复确认),这时可以认为是拥塞的,发生了重复确认,收到三个duplicated acks就会进入快速重传.cwnd是滑动窗口的大小.
27.思考一个过程,用tcp传输1m的数据的一个流程.
28.发送窗口,接受窗口和拥塞窗口?
应该是没有拥塞窗口这个概念的.
29.什么是快速重传?
在回复多个包的时候,将多个包的回复信息和send一起带过去.
30.在tcp的编程接口socket,bind,connect,listen,accpect,recv,send,close这些中,
哪些动作对方能感知到?
connect,send,close.
31.查看网络连接的状态的命令?
netstat
32.如何终止fin_wait_2状态?
只有kill进程,否则不会终止.此时只能接受数据不能发送数据,是主动发起断开连接请求一方的状态.
33.断开连接为什么要四次挥手?
因为是tcp通信时全双工的,需要A->B断了,也要B->A断了,就像谈恋爱,A和B说要分手,B也要和A说分手,才能断的干净.
33.time_wait有什么用?
防止最后一次确认包丢失变成僵尸包,无人收拾(难道这个包是放在内核中的吗?--探索一下,后面又说不会出现了~~~~~~)
time_wait的时间是2MSL, MSL的意思是Maximum Segment Lifetime.
(1)让TCP再次发送最后的ACK以防这个ACK丢失(被动关闭的一方超时并重发最后的FIN);保证TCP的可靠的全双工连接的终止。
(2)允许老的重复分节在网络中消失。
(3)TCP连接的建立和终止 在TIME_WAIT状态 时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过在函数setsockopt设置 SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口,如setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
33.time_wait的产生与影响分析?
https://blog.csdn.net/mingongge/article/details/106631854
34.出现close_wait状态?
调用close时间不对.
6.TCP的参数设置
这里说明的是CentOS5.3,并内核使用的是2.6.18-128.el5PAE #1 SMP .
修改部分TCP ,有的是为了提高性能与负载,但是存在降低稳定性的风险.有的则是安全方面的配置,则有可能牺牲了性能.
1.TCP keepalive TCP连接保鲜设置
echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
keepalive是TCP保鲜定时器。当网络两端建立了TCP连接之后,闲置idle(双方没有任何数据流发送往来)了tcp_keepalive_time后,
服务器内核就会尝试向客户端发送侦测包,来判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到
对方的回答(ack包),则会在tcp_keepalive_intvl后再次尝试发送侦测包,直到收到对对方的ack,如果一直没有收到对方的ack,一共
会尝试tcp_keepalive_probes次,每次的间隔时间在这里分别是15s, 30s, 45s, 60s, 75s。如果尝试tcp_keepalive_probes,
依然没有收到对方的ack包,则会丢弃该TCP连接。
2. syn cookies设置
echo 0 > /proc/sys/net/ipv4/tcp_syncookies
在CentOS5.3中,该选项默认值是1,即启用syn cookies功能。我们建议先关闭,直到确定受到syn flood攻击的时候再开启syn
cookies功能,有效地防止syn flood攻击。也可以通过iptables规则拒绝syn flood攻击。
3.TCP 连接建立设置
echo 8192 > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo 2 > /proc/sys/net/ipv4/tcp_syn_retries
echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
tcp_max_syn_backlog SYN队列的长度,时常称之为未建立连接队列。系统内核维护着这样的一个队列,用于容纳状态为SYN_RESC
的TCP连接(half-open connection),即那些依然尚未得到客户端确认(ack)的TCP连接请求。加大该值,可以容纳更多的等待连接的网络连接数。
tcp_syn_retries 新建TCP连接请求,需要发送一个SYN包,该值决定内核需要尝试发送多少次syn连接请求才决定放弃建立连接。
默认值是5. 对于高负责且通信良好的物理网络而言,调整为2
tcp_synack_retries
对于远端SYN连接请求,内核会发送SYN+ACK数据包来确认收到了上一个SYN连接请求包,然后等待远端的确认(ack数据包)。该值则
指定了内核会向远端发送tcp_synack_retires次SYN+ACK数据包。默认设定值是5,可以调整为2.
4. TCP 连接断开相关设置
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
echo 15000 > /proc/sys/net/ipv4/tcp_max_tw_buckets
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
tcp_fin_timeout 对于由本端主动断开连接的TCP连接,本端会主动发送一个FIN数据报,在收到远端ACK后,且并没有收到远端FIN包之前,
该TCP连接的状态是FIN_WAIT_2状态,此时当远端关闭了应用,网络不可达(拔网张),程序不可断僵死等等,本端会一直保留状态为
FIN_WAIT_2状态的TCP连接,该值tcp_fin_timeout则指定了状态为FIN_WAIT_2的TCP连接保存多长时间,一个FIN_WAIT_2的TCP连接最
多占1.5k内存。系统默认值是60秒,可以将此值调整为30秒,甚至10秒。
tcp_max_tw_buckets 系统同时处理TIME_WAIT sockets数目。如果一旦TIME_WAIT tcp连接数超过了这个数目,系统会强制清除并且
显示警告消息。设立该限制,主要是防止那些简单的DoS攻击,加大该值有可能消耗更多的内存资源。如果TIME_WAIT socket过多,
则有可能耗尽内存资源。默认值是18w,可以将此值设置为5000~30000 tcp_tw_resue 是否可以使用TIME_WAIT tcp连接用
于建立新的tcp连接。
tcp_tw_recycle 是否开启快带回收TIME_WAIT tcp连接的功能。
5. tcp 内存资源使用相参数设定
echo 16777216 > /proc/sys/net/core/rmem_max
echo 16777216 > /proc/sys/net/core/wmem_max
cat /proc/sys/net/ipv4/tcp_mem
echo “4096 65536 16777216″ > /proc/sys/net/ipv4/tcp_rmem
echo “4096 87380 16777216″ > /proc/sys/net/ipv4/tcp_wmem
rmem_max 定义了接收窗口可以使用的最大值,可以根据BDP值进行调节。
wmem_max 定义了发送窗口可以使用的最大值,可以根据BDP什值进行调整。
tcp_mem [low, pressure, high] TCP用这三个值来跟踪内存使用情况,来限定资源占用。通常情况下,在系统boot之时,内核会根据
可用内存总数计算出这些值。如果出现了Out of socket memory,则可以试着修改这个参数。
1)low: 当TCP使用了低于该值的内存页面数时,TCP不会考滤释放内存。
2)pressure: 当TCP使用了超过该值的内存页面数量,TCP试图稳定其对内存的占用,进入pressure模式,直到内存消耗达于low值,退出该模式。
3)hight:允许所有tcp sockets用于排队缓冲数据报的内存页数。
tcp_rmem [min, default, max]
1)min 为每个TCP连接(tcp socket)预留用于接收缓冲的内存数量,即使在内存出现紧张情况下TCP socket都至少会有这么多数量的内存用于接收缓冲。
2)default 为TCP socket预留用于接收缓冲的内存数量,默认情况下该值影响其它协议使用的 rmem_default的值,所以有可能被rmem_default覆盖。
3)max 该值为每个tcp连接(tcp socket)用于接收缓冲的内存最大值。该值不会影响wmem_max的值,设置了选项参数 SO_SNDBUF则不受该值影响。
tcp_wmem [min, default, max] 如上(tcp_rmen)只不过用于发送缓存。
注:
1)可以通过sysctl -w 或者写入/etc/sysctl.conf永久保存
2)性能调优仅在于需要的时候进行调整,调整以后需要采集数据与基准测试数据进行比较。建议,不需要盲从地调整这些参数。