欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

对于TCP协议栈的一些认识和思考

程序员文章站 2022-05-18 16:01:50
...

1.为什么使用TCP?

1.数据可靠,必达;
2.传输效率不低;
3.顺序传输数据.

2.TCP报文头图示

 对于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.状态图的转变

对于TCP协议栈的一些认识和思考

 

   对于TCP协议栈的一些认识和思考

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)性能调优仅在于需要的时候进行调整,调整以后需要采集数据与基准测试数据进行比较。建议,不需要盲从地调整这些参数。