zoukankan      html  css  js  c++  java
  • 玩转TCP连接

    0.基本定义

    TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,而且TCP是全双工模式。

    面向连接的?  是的,必须收发两端先建立连接才能发消息,是建立在安全连接基础上的

    可靠的?   必须要可靠的连接,可靠的发送消息,平白无故丢消息那可不行

    基于字节流的?  TCP 在建立连接时,需要告诉对方 MSS(最大报文段大小),如果一次数据很大就会根据MSS切割成TCP报文段,当成有序的字节串发过去

    传输层的?  找出网络七层模型,看看TCP/UDP在哪层 (五层模型也是的)

    通信协议? 协议即约定的一套规范

    全双工模式? 很简单,服务端客户端建立连接,服务能发客户端收,客户端能发服务端收,双向的

    扩展:应用程序的端口号和应用程序所在主机的 IP 地址统称为 socket(套接字),IP:端口号,
    在互联网上 socket 唯一标识每一个应用程序,源端口+源IP+目的端口+目的IP称为”套接字对“,一对套接字就是一个连接,一个客户端与服务器之间的连接。

    1.头部报文各字段含义解析 
    seq: 序列号,标记当前数据包的位置,保证数据通信有序性 (数据段分割发送后 能顺序拼在一起)
    ack: 确认序列号, 接收端期望收到的下一个序列号。序列号  = 上一次收到的seq+1,告诉对方之前的数据都已正确收到
    TCP Flag: TCP 的状态机的,依次为URG,PSH,RST,ACK,SYN,FIN
    标志ACK: 发送端初次发送ACK = 0 ,接收端确认后响应 ACK = 1 ,告知发送端该条已接收
    标志SYN:表示同步序列号,TCP握手发的第一个数据包,用来建立TCP连接
    标志FIN:发送端的最后一波数据,标志FIN,表示已经最后一波 连接将被断开

    Window: 滑动窗口大小,用来进行流量控制

    SYN与ACK搭配使用 (初次建立连接时)
    发送端 初次发送SYN = 1, ACK = 0
    接收端 接收到后发送SYN = 1, ACK = 1

    2.三次握手全过程

    接收端进程启动,进入LISTEN(监听)模式,准备接收发送方的连接请求:

    三次握手第一步:发送端向接收端发出连接请求报文,这时报文头中SYN 标志位为1,同时设置一个初始序列号seq = x(随机数); 做完这步,发送方进入SYN_SENT (同步已发送状态) 。

    第一次握手客户端发送的报文称为同步请求报文,希望与服务端建立同步连接,SYN报文不携带数据。


    三次握手第二步:接收端收到来自服务端的连接请求报文后,确认收到,回复发送端一个响应包。响应报文中ACK(确认标志位)设置为1,将确认号ack 设置为第一步的请求序列号seq 加1(ack =x+1),

    另外自己也回客户端一个SYN包(可以建立同步连接),即SYN + ACK的包,包序列号seq = y(随机数),服务端进入SYN_RCVD(同步收到)状态。

    三次握手第三步:客户端收到来自服务端的 SYN + ACK 包,会发送一个ACK 确认包,ACK =1,seq = x+1( 第二步的ack),ack = y+1(第二步的seq+1)。

    随后双方都进入ESTABLISH状态 连接成功


    3.三次握手的意义? 为什么不是一次或者两次? 

    三次握手的意义:保证连接可靠性

    第一次握手客户端发送报文给服务端,收到服务端的应答表明客户端发送数据的能力ok;

    第二次握手服务端发送数据给客户端表示服务端接收数据能力ok(我正常收到你的数据了,告诉你一声);

    第三次客户端发报文给服务端表明服务端的发送数据的能力也ok,客户端接收数据能力也ok

    (我能正常收到你的数据,代表你发的数据没问题,我的接收能力也没问题,所以告知你一声)。所以要验证客户端和服务端发送&接收数据的能力都ok至少需要三次握手才能达到。

    “三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”

    也就是说发送端很久之前发了个连接的请求,然后因为网络等原因耽搁了,发送端等不下去了就洗洗睡了。

    n年以后接收端接到了连接请求,然后傻了吧唧的回复确认创建连接,然后一直等待发送端发消息,事实上发送端早就不鸟你了,拜拜浪费接收端连接资源。

    在《计算机网络》书中讲“三次握手”的目的是为了解决“网络中存在延迟的重复分组”的问题。

    4. 四次挥手怎么玩

    初始状态 客户端服务端都处于Established连接状态

    第一次挥手  客户端发送FIN = 1, seq = u, 等待服务端确认。 表示所有数据都发完了,服务端可以关闭接收了 (但是此时服务端依旧可以发消息来客户端接收)

    第二次挥手 服务端发送ACK = 1,ack = u+1, seq = v并将服务端所有数据发送完。 然后服务端处于CLOSE_WAIT状态等待关闭,客户端处于FIN_WAIT状态

    第三次挥手 服务端确认所有数据处理完,发送FIN = 1, seq = w, ack = u+1 然后服务端处于LAST_ACK状态 等客户端回信

    第四次挥手 客户端收到FIN = 1后,发送ACK = 1, seq = u+1, ack = w+1 通知服务端已收到 (客户端发送完会等待2MSL的时间,2MSL后能完全确定本次连接已结束)

    5.为什么最后客户端会等待2MSL的时间?

    假如客户端给服务端发送的ACK = 1丢失了,服务端等待 1MSL没收到,然后重新发送消息需要1MSL。如果再次接收到服务端的消息,则重启2MSL计时器,发送确认请求。

    客户端只需等待2MSL,如果没有再次收到服务端的消息,就说明服务端已经接收到自己确认消息;此时双方都关闭的连接,TCP 四次挥手完毕。 

    6.建立连接后客户端出问题挂了怎么办?

    建立连接后服务端监听为客户端创建的套接字,等待消息。如果客户端挂掉,服务端得不到通知就会白白等待浪费资源。

    TCP 协议中服务端有个计时器,每次收到客户端的响应报文都会重置这个计时器,服务端有个超时时间,通常是2个小时,2个小时没收到客户端的数据,

    服务端会每隔75秒发送探测报文段,连续10次探测报文没响应,认为客户端出现问题,服务器会关闭这个连接。

    类似于心跳机制

    7.滑动窗口问题

    出现场景:  当发送方发消息特别频繁,接收方的消费速度远远跟不上。

    解决办法:接收方每次发送消息顺带传过来一个窗口大小(win),告知发送方自己的接收能力。发送方再根据窗口大小(win)做流量控制

    发送端如何根据win去做流量控制的:原则是 窗口外的数据不可发送

    如图,客户端想从发送的消息为从左到右a,b,c,d,e,f,g,h,i,j,k,l,m    此时收到服务端发来的窗口大小(win)为5

    1.当收到窗口大小不变情况,当前已经发送了a,b,c,d,e  其中d,e已发送但是未收到确认(未确认的都算窗口内)。  那么此时可发送的数据仅为f,g,h 

    如果f,g,h都发完了但是一个确认也没收到,那就到头了,不会继续发i,j,k   

    2.当收到窗口大小变大的情况,当前已经发送了a,b,c,d,e  其中d,e已发送但是未收到确认。 此时收到了服务端的确认包,d得到了确认,并且告知窗口大小变为6

    那么此时窗口会变成这个样子,窗口右边界右移

    3.当收到窗口大小变小的情况,当前已经发送了a,b,c,d,e  其中d,e已发送但是未收到确认。  此时收到了服务端的确认包,d得到了确认,并且告知窗口大小变为3

    不会将窗口右边界向左移动,而是等着 ACK 确认的到来,不断将左边界向右移动,直到窗口大小值收缩到新大小3为止。

    8.半连接与全连接队列

     

    半连接队列: syns Queue   全连接队列:accept Queue

    很多场景需要对accept queue 大小做些调整。

    当机器并发量很高,accept queue(全连接队列) 可能会出现不够用的情况,会出现类似connection reset 和 connection timeout 异常,这个取决于机器上 tcp_abort_on_overflow 的设置,不同值服务端不同处理机制

    tcp_abort_on_overflow为0:连接建立过程中三次握手第三步时,发生全连接队列满了,server扔掉client 发过来的ack,那么client 会重新发送ack,直到超时,所以客户端会出现连接超时(connection timeout );

    tcp_abort_on_overflow为1:遇到全连接队列满了,server会发一个reset包给client,表示废掉这个这个连接,这个握手过程无效,客户端会看到很多connection reset by peer的错误;

    linux命令:   netstat -s | grep "listen"

    9.一台机器最多创建多少个TCP连接

     传闻说一台机器最多创建的TPC连接数为65535个。

    为什么? 因为linux默认能分配的端口号数量就是65535个。(其中0~1024的端口号早被内部限定了)

    查看机器可分配端口号命令: cat /proc/sys/net/ipv4/ip_local_port_range 

    那么一台机器TPC连接最多只能创建60000个左右吗?

    实则不然,创建一个TPC连接需要四元组:  源ip + 源端口号 + 目标ip +目标端口号

    只要保证每个TCP连接的四元组不完全重复,就可以不断创建新的TCP连接

    而上面讨论的端口号仅是源端口号有60000个,源ip不变的情况下只要目标ip与目标端口号不重复,就可以不断排列组合创建更多TCP连接。

    那是否可以无限创建了呢?

    看一下创建一个TCP连接需要哪些资源

    端口号限制: 上面提过,一般linux 四元组中的源端口号仅提供60000多个。  但是只要目标ip 和目标端口不重复 理论上可以没有限制

    文件描述符限制: linux下一切皆文件,每创建一个连接操作系统都会提供一个文件描述符,文件描述符数量有限制。  可通过改配置文件突破限制。

    受CPU限制: TCP连接会消耗一定的CPU资源,主要根据连接的读写请求等因素占用CPU。CPU长时间占满导致系统卡死     提高CPU配置呗 

    内存限制   : 每创建一个连接需要占用一定的Socket缓冲区,太多连接导致内存打满      扩容内存呗

    线程数限制: 如果是传统IO模型,每创建一个TCP连接会创建一个线程监听,大量的连接则需要大量线程,频繁的上下文切换导致系统崩溃 (C10K问题) 。  可以通过IO多路复用模型,一个线程监听多个连接改善

    如果上述五条的限制都能突破,恭喜你 可以无限创建TCP连接了

    10.网络拥塞控制

    网络拥塞控制,说白了就是  发送方判断当前网络状况,如果比较堵的话不要急着认为包丢了而不断的重新发送。

    如何判断当前网络状况?

    一般通过向网络中连续发送多个数据包来进行测试,测试过程中,如果发送数据包到达了一定的程度,网络通信就会阻塞

    第一种 递增发送数据包:  第一次发一个包,第二次发两个,第三次发三个...直到网络阻塞

    第二种 指数发送数据包: 第一次发一个,第二次发两个,第三次发四个...直到网络阻塞

    递增的起步速度比较慢 耗费时间太长,指数的增长速度又比较快。因此一般采用递增+指数相结合方式:先指数增长,后递增增长

    我们把一次性能够发送的数据包多少的窗口称之为拥塞窗口

    我们通过控制发送窗口的大小,也就是发送数据包的多少来进行拥塞控制。

    超时后如何拥塞控制?

    我们将增长的阀值进行降低,降低到 M 的一半大小,也就是 M/2。如下图所示,最大值为 24,此时发生拥塞,所以将阀值降为 12。

     当拥塞窗口的大小等于阀值12时,再进行线性增长。我们也把上边这种情况称之为快速恢复

  • 相关阅读:
    [置顶] 宏途_LCD调试流程.
    字典树的数据结构及基本算法的实现
    uva 10714 Ants(贪心)
    paip.输入法编程---增加码表类型
    chomp方法
    ios 限制输入长度
    我所理解的设计模式(C++实现)——策略模式(Strategy Pattern)
    Android用户界面 UI组件--AdapterView及其子类(一) ListView及各种Adapter详解
    C#系列教程——switch定义及使用
    局域网内linux由ip反解析主机名
  • 原文地址:https://www.cnblogs.com/ttaall/p/15721232.html
Copyright © 2011-2022 走看看