tcpx协议

tcp协议
详解报文首部
https://blog.csdn.net/Abbey_linux/article/details/102689173
重要:

image-20230904220121212
sequence number(占4个字节) TCP是面向字节流的,在TCP连接中传输的字节流中的每个字节都按照顺序编号。而序号字段值指的是本报文段所发送的数据的第一个字节的序号
offset 是指TCP报文段的首部长度,TCP报文段的数据起始处距离TCP 报文的起始处的距离
紧急URG 当 URG = 1 的时候,表示紧急指针(Urgent Pointer)有效。 它告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送。 URG 要与首部中的 紧急指针 字段配合使用。
推送PSH 当PSH=1的时候,表示该报文段高优先级,接收方TCP应尽快推送给接收应用程序,不用等到整个TCP缓存都填满后再交付 复位RST 当RST=1时,表示TCP连接中出现严重错误,需要释放并重新建立连接。携带RST标志的TCP报文段为 [复位报文段]。
窗口大小 Windows size 指出现在允许对方发送的数据量,告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据
校验和TCP Checksum 由发送端填充,接收端对TCP报文段执行CRC算法,以检验TCP报文段在传输过程中是否损坏。校验范围包括首部和数据两部分,是TCP可靠传输的重要保障。
紧急指针 仅在 URG = 1 时才有意义,它指出本报文段中的紧急数据的字节数。 当 URG = 1 时,发送方 TCP 就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。 因此,紧急指针指出了紧急数据的末尾在报文段中的位置。
tcp的连接管理
TIME_WAIT或者CLOSE_WAIT的原因以及如何解决
https://cloud.tencent.com/developer/article/2093503
close wait
(1) 程序问题:如果代码层面忘记了 CLOSE 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;或者代码不严谨,出现死循环之类的问题,导致即便后面写了 CLOSE 也永远执行不到。
(2) 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
time wait (能不能接受fin以外的?不能 延迟到达的报文段回被丢弃)
TIME_WAIT的作用: 简单说timewait之所以等待2MSL的时长,是为了避免因为网络丢包或者网络延迟而造成的tcp传输不可靠,而这个TIME_WAIT状态则可以最大限度的提升网络传输的可靠性。
同时TCP一般会禁止处于TIME_WAIT的连接上重建一个新的TCP连接, 这样做主要是为了避免新旧数据包出现串包的情况, 所以总结来说, TIME_WAIT的作用如下:
• 为实现TCP全双工连接的可靠释放
• 为使旧的数据包在网络因过期而消失
time wait 太多的危害:
• 在socket的TIME_WAIT状态结束之前,该socket所占用的本地端口号将一直无法释放。请注意客户端的端口总是有限的(65535), 耗尽了就会导致网络连接失败.
• 在高并发(每秒几万qps)并且采用短连接方式进行交互的系统中运行一段时间后,系统中就会存在大量的time_wait状态,如果time_wait状态把系统所有可用端口都占完了且尚未被系统回收时,就会出现无法向服务端创建新的socket连接的情况。此时系统几乎停转,任何链接都不能建立。
• 大量的time_wait状态也会系统一定的fd,内存和cpu资源,当然这个量一般比较小,并不是主要危害
• 方式一: 调整系统内核参数
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭; net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
$ cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl
$echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
优化完内核参数后,可以执行sysctl -p命令(/sbin/sysctl -p?),来激活上面的设置永久生效
• 方式二:调整短链接为长链接
在客户端将HTTP请求头里connection的值设置为:keep-alive。将短连接改成长连接。nginx做反向代理:从client到nginx的连接是长连接。 2.从nginx到server的连接是长连接。
为什么是三次 四次 +time wait
https://blog.csdn.net/u_nravel/article/details/109995465
HTTP1.0默认使用非持久连接,一个request和response后立马关闭TCP连接(为了实现client到web-server能支持长连接,必须在HTTP请求头里显示指定 Connection:keep-alive); HTTP1.1默认使用持久连接,也就是会重用TCP连接传输多个 request/response(要关闭keep-alive需要在HTTP请求头里显示指定 Connection:close)

编辑/etc/sysctl.conf优化内核

time wait 工作实际开发解决:
https://www.cnblogs.com/sea520/p/12611690.html
以上链接都提到一个命令行
netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’
它会显示例如下面的信息:
TIME_WAIT 814 CLOSE_WAIT 1 FIN_WAIT1 1 ESTABLISHED 634 SYN_RECV 2 LAST_ACK 1
net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle的开启都是为了回收处于TIME_WAIT状态的资源。
net.ipv4.tcp_fin_timeout这个时间可以减少在异常情况下服务器从FIN-WAIT-2转到TIME_WAIT的时间。
net.ipv4.tcp_keepalive_*一系列参数,是用来设置服务器检测连接存活的相关配置。
CLOSE_WAIT状态的原因与解决方法 https://blog.51cto.com/u_11979904/5948886
close_wait解决方法 基本的思想就是要检测出对方已经关闭的socket,然后关闭它。
需要判断socket,一旦read返回0,断开连接,read返回负,检查一下errno,如果不是AGAIN,也断开连接。(注:在UNP 7.5节的图7.6中,可以看到使用select能够检测出对方发送了FIN,再根据这条规则就可以处理CLOSE_WAIT的连接)
使用一个Heart-Beat线程,定期向socket发送指定格式的心跳数据包,如果接收到对方的RST报文,说明对方已经关闭了socket,那么我们也关闭这个socket。
设置SO_KEEPALIVE选项,并修改内核参数
首先,我们要防止不断开辟新的端口,这可以通过设置SO_REUSEADDR套接字选项做到:
sockConnected = socket(AF_INET, SOCK_STREAM, 0);
/// 允许重用本地地址和端口:
/// 这样的好处是,即使socket断了,调用前面的socket函数也不会占用另一个,而是始终就是一个端口
/// 这样防止socket始终连接不上,那么按照原来的做法,会不断地换端口。

int nREUSEADDR = 1;
setsockopt(sockConnected,
SOL_SOCKET,
SO_REUSEADDR,
(const char*)&nREUSEADDR,
sizeof(int));
其次,我们要设置SO_LINGER套接字选项:LINGER是“拖延”的意思。
如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是“从容关闭”:
因为在退出服务或者每次重新建立socket之前,我都会先调用
shutdown(sockConnected, SD_BOTH);/// 先将双向的通讯关闭

closesocket(sockConnected); /// 安全起见,每次建立Socket连接前,先把这个旧连接关闭
设置SO_LINGER为零(亦即linger结构中的l_onoff域设为非零,但l_linger为0),便不用担心closesocket调用进入“锁定”状态(等待完成),不论是否有排队数据未发送或未被确认。这种关闭方式称为“强行关闭”,因为套接字的虚电路立即被复位,尚未发出的所有数据都会丢失。在远端的recv()调用都会失败,并返回WSAECONNRESET错误。
如果你在关闭连接前还是出现CLOSE_WAIT,建议你取消shutdown的调用,直接两边closesocket试试。
TIME_WAIT的时间会非常长,因此server尽量减少主动关闭连接。
几种 TCP 连接中出现 RST 的情况
https://zhuanlan.zhihu.com/p/568419790
在 TCP 协议中 RST 表示复位,用来异常的关闭连接。发送 RST 包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓存区的包发送 RST 包。而接收端收到 RST 包后,也不必发送 ACK 包来确认。
1 端口未打开
当然在某些操作系统的主机上,未必是这样的表现。比如向一台 WINDOWS7 的主机发送一个连接不存在的端口的请求,这台主机就不会回应。
2 请求超时
在主机 89 上的程序在建立了 socket 之后,用 setsockopt 的 SO_RCVTIMEO 选项设置了 recv 的超时时间为 100ms。而我们看上面的抓包结果表示,从主机 89 发出 SYN 到接收 SYN 的时间多达 110ms。(从 15:01:27.799961 到 15:01:27.961886, 小数点之后的单位是微秒)。因此主机 89 上的程序认为接收超时,所以发送了 RST 拒绝进一步发送数据。
3 提前关闭
操作系统接收到的来自 TCP 连接中的每一个字节,我都会让应用程序接收到。如果应用程序不接收怎么办?你猜对了,RST。
是打开一个 socket 然后连接一个服务器并发送 5000 个字节。刚才我们看服务器的代码,每次只接收 4096 个字节。客户端的 49660 端口向服务器的 9877 端口发送了 5000 个字节的数据,然后服务器端发送了一个 ACK 进行了确认,紧接着服务器向客户端发送了一个 RST 断开了连接。
4 在一个已关闭的 socket 上收到数据
rst原因以及解决方案
https://blog.csdn.net/zhanghaoyu_java/article/details/118996554
原因
• 对方端口未打开,发生在连接建立
• 全连接队列已满,发生在连接建立(超过超时重传次数、网络暂时不可达)
• close Socket 时recv buffer 不为空:客户端发了两个请求,服务器只从buffer 读取第一个请求处理完就关闭连接,tcp层认为数据没有正确提交到应用,使用rst关闭连接。
• SO_LINGER 应用强制使用rst 关闭(该选项会直接丢弃未发送完毕的send buffer,可能造成业务错误;当socket设置了Linger选项,并Linger时间为0时,当调用close来关闭socket时,也会向对端发送RST报文)
• keepalive 超时(连续发送5次之后,都没有收到对端的KeeAliveReply报文时,会发送RST报文。)
• 在一个已关闭的socket上接收数据;(服务器关闭或异常终止了连接,由于网络问题,客户端没有收到服务器的关闭请求,这称为TCP半打开连接。就算重启服务器,也没有连接信息。如果客户端向提其写入数据,对方就会回应一个RST报文段。)
• TCP在建链过程中收到异常报文时,会发送RST报文(设置 connect_timeout等)
• 非正常包 (连接已经关闭,seq 不正确等)
• 移动链路/负载等设备/被GFW拦截
其他
服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset”;
服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示“Connection reset by peer”。(当tcp_abort_on_overflow=1,服务端accept队列满了,客户端发来ack,服务端直接返回RST通知client,表示废掉这个握手过程和这个连接,client会报connection reset by peer)
rst解决方案:

  1. 出错了重试;如果服务不是“幂等”的则不能使用该方法
  2. 客户端和服务器统一使用TCP长连接;客户端使用TCP长连接很容易配置(直接设置HttpClient就好);服务器配置长连接:需要设置tomcat的maxKeepAliveRequests、connectionTimeout等参数,如果使用了nginx进行反向代理或负载均衡,此时也需要配置nginx以支持长连接(nginx默认是对客户端使用长连接,对服务器使用短连接)
  3. 客户端和服务器统一使用TCP短连接。
    socket编程中的强制关闭与优雅关闭+异常退出
    https://blog.csdn.net/Bad_Sheep/article/details/6157738
    优雅关闭:如果发送缓存中还有数据未发出则其发出去,并且收到所有数据的ACK之后,发送FIN包,开始关闭过程。
    强制关闭:如果缓存中还有数据,则这些数据都将被丢弃,然后发送RST包,直接重置TCP连接。
    shutdown函数的原型是:int shutdown(SOCKET s,int how);
    该函数用于关闭TCP连接,但并不关闭socket句柄。其第二个参数可以取三个值:SD_RECEIVE,SD_SEND,SD_BOTH。
    • SD_RECEIVE表明关闭接收通道,在该socket上不能再接收数据,如果当前接收缓存中仍有未取出数据或者以后再有数据到达,则TCP会向发送端发送RST包,将连接重置。
    • SD_SEND表明关闭发送通道,TCP会将发送缓存中的数据都发送完毕并收到所有数据的ACK后向对端发送FIN包,表明本端没有更多数据发送。这个是一个优雅关闭过程。
    • SD_BOTH则表示同时关闭接收通道和发送通道。
    int closesocket(SOCKET s);关闭socket句柄,并释放相关资源。是否优雅关闭和一个socket选项有关:SO_LINGER 选项,该选项的设置值决定了closesocket的行为。该选项的参数值是linger结构,其定义是:
    typedef struct linger {u_short l_onoff;u_short l_linger;} linger;
    • 当l_onoff值设置为0时,是一个优雅关闭过程。closesocket会立即返回,并关闭用户socket句柄。如果此时缓冲区中有未发送数据,则系统会在后台将这些数据发送完毕后关闭TCP连接。有一个副作用就是socket的底层资源会被保留直到TCP连接关闭,这个时间用户应用程序是无法控制的。
    • 当l_onoff值设置为非0值,而l_linger也设置为0,这是一个强制关闭过程
    • 当l_onoff值设置为非0值,而l_linger也设置为非0值时,同时如果socket是阻塞式的,这时是优雅关闭过程;如果TCP在l_linger表明的时间内没有将所有数据发出,则会丢弃所有未发数据然后TCP发送RST包重置连接,此时就是一个强制关闭过程了。
    另外还有一个socket选项SO_DONTLINGER,它的参数值是一个bool类型的,如果设置为true,则等价于SO_LINGER中将l_onoff设置为0。
    注意SO_LINGER和SO_DONTLINGER选项只影响closesocket的行为,而与shutdown函数无关,shutdown总是会立即返回的。
    最好的关闭方式是这样的:
    发送完了所有数据后:
    (1)调用shutdown(s, SD_SEND),如果本端同时也接收数据时则执行第二步,否则跳到第4步。//在实际编程中,我们经常也不调用shutdown,而是直接调用closesocket,利用closesocket隐含触发TCP连接关闭过程的特性。
    (2)继续接收数据,
    (3)收到FD_CLOSE事件后,调用recv函数直到recv返回0或-1(保证收到所有数据),
    (4)调用closesocket,关闭socket句柄。
    其他重要的:(发送/接收端异常退出或被kill掉进程)
    发送端应用程序即便是异常退出或被kill掉进程,操作系统也不会丢弃发送缓冲区中的未发送数据,而是会在后台将这些数据发送出去。(但是这是在socket的发送缓存不为0的前提下,当socket的发送缓存设置为0(通过SO_SNDBUF选项)时比较特殊,此时不论socket是否是阻塞的,send函数都会被阻塞直到传入的用户缓存中的数据都被发送出去并被确认,因为此时在驱动层没有分配缓存存放用户数据,而是直接使用的应用层的用户缓存,所以必须阻塞直到数据都发出,否则可能会造成系统崩溃。)
    另外,如果是接收端的应用程序异常退出或被kill掉进程,并且接收缓存中还有数据没有取出的话,那么接收端的TCP会向发送端发送RST包,重置连接,因为后续数据已经无法被提交应用层了。
    (怎么这些东西被kill了还能发数据or RST)
    tcp连接中各种状态及故障排查
    https://zhuanlan.zhihu.com/p/451702640
    linux查看tcp的状态命令: 1)、netstat -nat 查看TCP各个状态的数量 2)、lsof -i:port 可以检测到打开套接字的状况 3)、 sar -n SOCK 查看tcp创建的连接数 4)、tcpdump -iany tcp port 9000 对tcp端口为9000的进行抓包 5)、tcpdump dst port 9000 -w dump9000.pcap 对tcp目标端口为9000的进行抓包保存pcap文件wireshark分析。 6)、tcpdump tcp port 9000 -n -X -s 0 -w tcp.cap 对tcp/http目标端口为9000的进行抓包保存pcap文件wireshark分析。
    Linux错误信息(errno)列表
    经常出现的错误:
    22:参数错误,比如ip地址不合法,没有目标端口等
    101:网络不可达,比如不能ping通
    111:链接被拒绝,比如目标关闭链接等
    115:当链接设置为非阻塞时,目标没有及时应答,返回此错误,socket可以继续使用。比如socket连接
    TCP通信中服务器处理客户端意外断开
    像这种如果一方已经关闭或异常终止连接,而另一方却不知道,我们将这样的TCP连接称为半打开的。解决意外中断办法都是利用保活机制。而保活机制分又可以让底层实现也可自己实现。
    1、自己编写心跳包程序(可能出现客户端重连前服务端还没有判断超时断开,重复此过程则会导致大量假的ESTABLISHED连接和CLOSE_WAIT连接。)
    2、启动TCP编程里的keepAlive机制(客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。)
    我们需要手工开启Keepalive功能并设置合理的Keepalive参数。全局设置可更改/etc/sysctl.conf,加上: net.ipv4.tcp_keepalive_intvl = 20 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_keepalive_time = 60
    如果现在服务器要关掉,但还有连接,如何实现不影响这个连接的请求关掉当前服务器?
    没看懂这问题
    read(socketfd,buffer,n)时,返回0 –>对端调用了close或者关闭了写
    tcp可靠性机制
    保证可靠性的机制:
    https://blog.csdn.net/m0_45861545/article/details/120640007
    • 校验和
    • 序列号/确认应答
    • 超时重传
    • 连接管理
    • 流量控制(滑动窗口控制)
    • 拥塞控制
    校验和:

image-20230910185131760

  1. TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。
  2. TCP的校验和是必需的,而UDP的校验和是可选的。
  3. TCP和UDP计算校验和时,都要加上一个12字节的伪首部。
    tcp伪首部:12字节伪首部包括:
    • 源IP地址
    • 目的IP地址
    • 保留字节(置0)
    • 传输层协议号(TCP是6)
    • TCP报文长度(首部 + 数据)
    计算方式:在数据传输的过程中,将发送的数据段都当做一个16位的整数。将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和。
    流量控制:滑动窗口
    https://www.bilibili.com/video/BV1zb4y1o7f9/
    必要性:
posted @ 2023-09-10 22:32  地球修者  阅读(42)  评论(0编辑  收藏  举报