拓冰建站拓冰建站
首页 / 资讯中心 / 正文

linux笔记归纳18:传输层协议TCP

传输层协议TCP目录传输层协议TCP一、TCP协议格式1.1.报文格式1.2.TCP报头标志位1.3.连接重置1.4.紧急指针二、确认应答机制2.1.序列号2.2.确认号三、超时重传机制3.1.丢包问题3.2.超时重传机制四、连接管理机制4.1.连接状态4.2.建立连接的状态变化4.3.断开连接的状态变化4.2.三次握手4.5.四次挥手4.8.CLOSE_WAIT状态4.9.TIME_WAIT状态五、滑动窗口5.1.滑动窗口的具体实现5.2.滑动窗口的丢包问题5.3.快重传 VS 超时重传六、流量控制6.1.流量控制机制6.2.16位窗口大小七、拥塞控制7.1.网络拥塞问题7.2.慢启动机制7.3.拥塞窗口八、延迟应答九、捎带应答十、面向字节流十一、粘包问题十二、TCP异常12.1.进程终止12.2.机器重启12.3.机器掉电十三、TCP总结13.1.可靠性本质13.2.保证可靠性的手段13.3.提高效率的手段十四、基于TCP的应用层协议十五、TCP/UDP用途对比15.1.UDP的用途15.2.TCP的用途十六、UDP实现可靠传输16.1.引入序号16.2.引入确认应答16.3.引入超时重传十七、TCP全连接队列与tcpdump抓包17.1.listen的第二个参数17.2.全连接队列原理17.3.网络连接的理解17.4.TCP dump抓包TCPTransmission Control Protocol传输控制协议一、TCP协议格式1.1.报文格式分离问题4位首部长度实现报头与有效载荷分离分用问题16目的端口号将有效载荷交付给上层16位源端口号表示数据从哪个进程来16位目的端口号表示数据去哪个进程32位序号数据序号自己发送的报文序号32位确认序号应答序号对对方的报文做确认16位窗口大小接收缓冲区的剩余空间大小16位校验和由发送端填充进行CRC校验接收端校验不通过就会认为数据有问题16位紧急指针有效载荷中特定的偏移量表示哪些部分是紧急数据1.2.TCP报头标志位TCP报头中标识报文类型的字段根据报文的不同类型接收方有不同的做法SYNSynchronize标志位同步标志位表示请求建立连接ACKAcknowledgment标志位确认标志位表示确认号是否有效FINFinish标志位结束标志位表示请求断开连接PSHPush标志位推送标志位通知接收方立刻把接收缓冲区数据交给上层应用程序RSTReset标志位复位标志位表示建立连接失败后对方要求重新建立连接URGUrgent标志位紧急标志位表示紧急指针是否有效// linux kernel include/linux/tcp.h struct tcphdr { __be16 source; __be16 dest; __be32 seq; __be32 ack_seq; #if defined(__LITTLE_ENDIAN_BITFIELD) __u16 res1 : 4, doff : 4, fin : 1, syn : 1, rst : 1, psh : 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; __sum16 check; __be16 urg_ptr; };1.3.连接重置通信时连接出现任何问题都可以进行重置第三次握手没有应答可靠性无法得到保证客户端只要发出ACK就认为建立连接成功服务器只要接收ACK就认为建立连接成功因为有时间差所以双方对于连接建立是否成功的认知不一致当服务端没有收到应答客户端发送数据服务端会发送RST1.4.紧急指针TCP通过序号保证可靠性让数据按序到达接收缓冲区接收缓冲区是一个字节流式的接收队列如果有数据想被优先读取处理就需要通过紧急指针让报文数据在向上层交付时进行插队快速响应16位紧急指针表示的是在当前报文有效载荷中在特定偏移量处有紧急数据二、确认应答机制2.1.序列号序列号TCP将每个字节的数据进行编号作用确认应答按序到达去重2.2.确认号确认号每一个ACK都带有对应的确认号作用告诉发送方已经接收了哪些数据下次该从哪里发送数据对历史报文做100%的可靠性保证将发送缓冲区看成一个字符类型的数组每一个字节天然就有编号三、超时重传机制3.1.丢包问题发送方收到应答意味着接收方100%收到发送方没有收到应答情况1数据丢失数据可能丢失接收方可能没收到情况2应答丢失应答没有收到但是接收方收到数据3.2.超时重传机制如果发送方在特定的时间间隔收不到应答就判定报文丢失这个特定的时间间隔为了适应网络环境一定是在不断变化的网络环境好的时候间隔短一点网络环境差的时候间隔长一点在Linux中以500ms为一个单位进行控制每次判定超时重发的超时时间都是500ms的整数倍发送一次等500ms如果重发一次仍得不到应答等待2 * 500ms如果重传后仍得不到应答等待4 * 500ms依次以指数形式递增积累重传一定次数TCP认为网络或对端主机异常强制关闭连接四、连接管理机制4.1.连接状态正常情况下TCP经过三次握手建立连接、四次挥手断开连接一个服务端可能会同时存在非常多的连接连接有不同的状态有的连接处于SYN_RCVD状态有的连接处于ESTABLISHED状态有的连接处于CLOSE_WAIT状态有的连接处于LAST_ACK状态有的连接处于CLOSED状态服务端需要对各个状态的连接进行管理就需要先描述再组织在内核中连接的本质是一个struct sock结构体有状态、序号、缓冲区大小窗口大小等属性在服务端与客户端建立连接时有时间和空间成本一旦连接过多内存不足服务器就会崩溃4.2.建立连接的状态变化服务端状态变化[CLOSE → LISTEN]服务端调用listen进入LISTEN状态等待客户端连接[LISTEN → SYN_RCVD]服务端监听到客户端的连接请求将该连接放入内核等待队列向客户端发送确认报文[SYN_RCVD → ESTABLISHED]服务端接收到客户端的确认报文连接建立成功客户端状态变化[CLOSE → SYN_SENT]客户端调用connect发送同步报文申请建立连接[SYN_SENT → ESTABLISHED]客户端接送到服务端的确认报文连接建立成功4.3.断开连接的状态变化客户端状态变化[ESTABLISHED → FIN_WAIT_1]客户端主动调用close向服务端发送结束报文[FIN_WAIT_1 → FIN_WAIT_2]客户端接收到服务端的确认报文等待服务端的结束报文[FIN_WAIT_2 → TIME_WAIT]客户端接收到服务端的结束报文向服务端发送确认报文[TIME_WAIT → CLOSED]客户端等待两个MSL时间进入CLOSED状态关闭连接成功服务端状态变化[ESTABLISHED → CLOSE_WAIT]服务端接收到客户端的结束报文向客户端发送确认报文[CLOSE_WAIT → LAST_ACK]服务端调用close向客户端发送结束报文[LAST_ACK → CLOSED]服务端接收到客户端的确认报文关闭连接成功4.2.三次握手三次握手的概念通信双方建立连接的过程让通信双方获取对方接收缓冲区的数据接收能力三次握手前双方不能发送消息前两次握手不能携带数据只能发送报头三次握手的实现内核层SYNSYN ACKACK用户层客户端调用connect发起连接连接建立完成后服务端调用accept接收connect用来发起三次握手具体流程由客户端与服务端的操作系统自己完成accept完全不参与三次握手只获取底层建立好的连接三次握手的原因理由一以最短方式验证全双工确认双方网络通常理由二以最小成本确认双方通信意愿面对客户端的连接请求服务端都要无条件接受所以ACK与SYN在一个报文同时设置而在断开连接时客户端给服务端发FIN报文服务端可能不会接受需要进行四次挥手4.5.四次挥手四次挥手的概念通信双方断开连接的过程需要双方断开连接的共识通信一方发出申请后另一方应答通信另一方再发出申请一方应答四次挥手的实现Client→Server客户端FIN我要发的数据发完了我要和你断开连接服务端ACK我收到了Server→Client服务端FIN我要发的数据发完了我要和你断开连接客户端ACK我收到了四次挥手的原因理由一以最短次数完成全双工断开连接的请求理由二服务端ACK与FIN的时间间隔不确定shutdown系统调用关闭套接字的读端、写段或者读写端4.8.CLOSE_WAIT状态客户端退出后如果服务端没有调用close关闭套接字服务端则会一直处于CLOSE_WAIT状态依旧占用sockfd连接没有释放sockfd用完后必须要进行关闭否则会造成文件描述符泄漏#include Socket.hpp #include iostream #include memory #include sys/wait.h #include functional using namespace SocketModule; using namespace LogModule; // IO服务 using ioservice_t std::functionvoid(std::shared_ptrSocket sock, InetAddr client); // TCP服务器: 实现连接, 不需要关心传递的数据 class TcpServer { public: TcpServer(uint16_t port) : _port(port), _listensockptr(std::make_uniqueTcpSocket()), _isrunning(false) { _listensockptr-BuildListenSocketMethod(_port); } void Start(ioservice_t callback) { _isrunning true; while (_isrunning) { InetAddr client; auto sock _listensockptr-Accept(client); if (sock nullptr) { continue; } LOG(LogLevel::DEBUG) accept success... client.StringAddr(); // // 多进程实现 // pid_t id fork(); // if (id 0) // { // LOG(LogLevel::FATAL) fork error...; // exit(FORK_ERR); // } // else if (id 0) // { // // 子进程 // // 关闭listensockfd // _listensockptr-Close(); // if (fork() 0) // { // exit(0); // } // // 孙子进程 // callback(sock, client); // 执行IO服务 // sock-Close(); // exit(OK); // } // else // { // // 父进程 // // 关闭sockfd // sock-Close(); // pid_t rid ::waitpid(id, nullptr, 0); // (void)rid; // } } _isrunning false; } ~TcpServer() { } private: uint16_t _port; std::unique_ptrSocket _listensockptr; bool _isrunning; };客户端退出操作系统会自动关闭客户端的套接字服务端如果没有调用close则该套接字不会释放4.9.TIME_WAIT状态主动断开连接的一方要进入TIME_WAIT状态等待两个MSL时间后才能回到CLOSED状态MSLMaximun Segment Lifetime单个TCP报文在网络中存活的最大时间保证两个传输方向上未被接收或者迟到的报文自动消失报文没有丢但是被发送方判定为超时当连接关闭后该报文依旧在网络中残存此时如果服务器立刻重启就可能会收到这个迟到的数据会造成一些异常的情况#include Socket.hpp #include iostream #include memory #include sys/wait.h #include functional using namespace SocketModule; using namespace LogModule; // IO服务 using ioservice_t std::functionvoid(std::shared_ptrSocket sock, InetAddr client); // TCP服务器: 实现连接, 不需要关心传递的数据 class TcpServer { public: TcpServer(uint16_t port) : _port(port), _listensockptr(std::make_uniqueTcpSocket()), _isrunning(false) { _listensockptr-BuildListenSocketMethod(_port); } void Start(ioservice_t callback) { _isrunning true; while (_isrunning) { InetAddr client; auto sock _listensockptr-Accept(client); if (sock nullptr) { continue; } LOG(LogLevel::DEBUG) accept success... client.StringAddr(); // 多进程实现 pid_t id fork(); if (id 0) { LOG(LogLevel::FATAL) fork error...; exit(FORK_ERR); } else if (id 0) { // 子进程 // 关闭listensockfd _listensockptr-Close(); if (fork() 0) { exit(0); } // 孙子进程 callback(sock, client); // 执行IO服务 sock-Close(); exit(OK); } else { // 父进程 // 关闭sockfd sock-Close(); pid_t rid ::waitpid(id, nullptr, 0); (void)rid; } } _isrunning false; } ~TcpServer() { } private: uint16_t _port; std::unique_ptrSocket _listensockptr; bool _isrunning; };客户端先关闭服务端先关闭TIME_WAIT引起bind失败服务端主动关闭连接进入TIME_WAIT状态IP与端口号仍然被系统占用此时杀掉服务端进程立刻重启服务端程序再次绑定同一个接口就会报错setsockopt函数作用创建端口号相同但IP地址不同的多个套接字描述符// Socket.hpp void SocketOrDie() override { _sockfd ::socket(AF_INET, SOCK_STREAM, 0); if (_sockfd 0) { LOG(LogLevel::FATAL) socket error; exit(SOCKET_ERR); } int opt 1; setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); LOG(LogLevel::INFO) socket success; }五、滑动窗口5.1.滑动窗口的具体实现滑动窗口的大小无需等待确认应答而可以继续发送数据的最大值对方的接收缓冲区剩余空间的大小对方的接收缓冲区的数据接收能力滑动窗口的移动start与end下标的增加start报文确认序号endstart 对方接收窗口大小滑动窗口的本质实现流量控制的方案滑动窗口的特点滑动窗口不可向左滑动已发送的数据不代表是无效的而是指这部分空间可以再次被利用不需要刻意地清空发送缓冲区序号在发送的过程中数字依次增大滑动窗口可以任意改变大小根据接收方的窗口大小动态调整更新5.2.滑动窗口的丢包问题情况一最左侧丢失最左侧报文数据丢失滑动窗口左侧不变让对应的报文保存在滑动窗口中方便后续重传最左侧报文应答丢失滑动窗口正常工作可以通过后续的应答进行确认确认的消息一定是连续的在发送时也必须连续发送保证了滑动窗口不能跳跃情况二中间丢失滑动窗口会向右滑动到中间丢失的位置变成最左侧丢失的情况情况三最右侧丢失滑动窗口会向右滑动到最右侧丢失的位置变成最左侧丢失的情况5.3.快重传 VS 超时重传高速重发机制触发条件发送报文数据丢失接收方发送三个同样的确认应答作用提高效率超时重传机制触发条件超时并且没有应答作用无法触发快重传时用来兜底六、流量控制6.1.流量控制机制意义将数据发送的流量变得合理慢了加快快了减慢接收缓冲区填满后发送方发送的报文直接丢弃浪费资源效率低下按量发送时发送方必须要知道接收方的接收缓冲区中剩余空间的大小在报头中填写自己接收缓冲区中剩余空间的大小表示自己的接收能力将滑动窗口设置为0后主机A就不会向主机B发送携带数据的报文主机B的操作系统需要等待上层消费者把数据读走才能释放缓冲区主机A会周期性地发送窗口探测等待主机B发送窗口更新的通知当主机A多次询问主机B无果后发送RST报文直接关闭TCP连接6.2.16位窗口大小16位窗口大小16位数字最大表示为65535TCP的窗口理论上最大就是65535个字节但是TCP首部的40字节选项中包含了窗口扩大因子M实际窗口大小是窗口值左移M位七、拥塞控制7.1.网络拥塞问题同样是丢包丢包少和丢包多得出的结论是不同的就像学校考试200人有2、3个人挂科是学生问题但198、199人挂科就是课程问题TCP不仅要考虑双方主机的问题还要考虑网络本身的问题如果TCP通信中出现大量丢包就不是双方主机问题而是网络本身问题此时如果立即重发可能会增加网络的负载使网络更加拥堵7.2.慢启动机制先发送少量的数据探路摸清当前的网络状态再决定按照多大的速度传输数据指数增幅发送数据前期发送缓慢直到网络不拥堵后就会尽快恢复网络通信7.3.拥塞窗口拥塞窗口的本质一个临界值低于该值时网络大概率不拥塞高于该值时网络可能发生拥塞拥塞窗口的作用实现拥塞控制衡量网络是否会拥堵的指标拥塞窗口的实现拥塞窗口的数值开始是以指数增长的当达到慢启动阈值后就会变为线性增长发生网络拥塞后从0开始新的慢启动阈值是上次网络拥塞时窗口大小的一半理论上拥塞窗口不会一直增大网络带宽会限制拥塞窗口的大小拥塞窗口的增加并不代表发送的数据量一定在增加发送的数据量由滑动窗口大小决定滑动窗口的大小 min{对方接收缓冲区的剩余大小拥塞窗口}八、延迟应答如果接收数据的主机立刻应答此时返回的窗口可能比较小在延迟期间接收方的上层可能会拿走接收缓冲区中的数据此时返回的窗口大小会更大网络吞吐量大提高传输效率数量限制每隔N个包应答一次时间限制超过最大延迟时间应答一次九、捎带应答在ACK报文中加入有效载荷发送给对方大部分的报文ACK都是设置为1的捎带应答的作用可以减少报文数量降低网络开销提高网络通信的效率十、面向字节流TCP协议会在内核中创建一个发送缓冲区和接收缓冲区使得TCP读写不需要一一匹配写100个字节数据可以调用一次write写100个字节也可以调用100次write每次写一个字节读100个字节数据可以调用一次read读100个字节也可以调用100次read每次读一个字节十一、粘包问题应用层的数据包在TCP协议中没有明确的边界可能会接收半个或者一个半的数据由应用层解决通过自定义协议加序列化和反序列化明确报文与报文之间的边界十二、TCP异常12.1.进程终止进程终止会释放文件描述符仍然可以发送FIN自动进行四次挥手和正常关闭没有区别12.2.机器重启和进程终止的情况相同关机之前操作系统需要终止所有的进程12.3.机器掉电客户端网线断开但服务端认为连接还在服务端向客户端发送报文服务端发现连接已经不在就会发送RESET重置连接TCP内置保活定时器定期询问连接是否还在如果不在则释放连接内置的保活机制是几十分钟级一般的保活机制由应用层自己来完成十三、TCP总结13.1.可靠性本质确认应答可以保证历史报文的可靠性而通信周期中最新的报文永远没有应答无法保证可靠性13.2.保证可靠性的手段校验和、序列号、确认应答超时重发、连接管理、流量控制、拥塞控制13.3.提高效率的手段滑动窗口、快速重传、延迟应答、捎带应答十四、基于TCP的应用层协议HTTP、HTTPS、SSH、Telnet、FTP、SMIP十五、TCP/UDP用途对比15.1.UDP的用途用于对高速传输和实时性要求较高的通信领域比如广播、直播、视频传输15.2.TCP的用途用于可靠传输的情况比如文件传输、银行转账、登录注册十六、UDP实现可靠传输16.1.引入序号保证数据顺序16.2.引入确认应答确保对端收到数据16.3.引入超时重传如果隔一段时间没有应答就重新发送数据十七、TCP全连接队列与tcpdump抓包17.1.listen的第二个参数backlog 1表示在全连接队列中能够三次握手成功的连接个数17.2.全连接队列原理全连接队列的结构本质生产者消费者模型全连接队列的连接是上层来不及处理的连接如果连接数超过队列个数多余连接就会无法完成握手保持SYN_SENT状态全连接队列不能为空否则会增加服务的闲置率减少给用户提供服务的效率全路径队列不能太长否则会浪费空间用户的体验差17.3.网络连接的理解17.4.TCP dump抓包捕捉所有网络接口上的TCP报文sudo tcpdump -i any tcp捕获指定网络接口上的TCP报文ifconfig eth0: flags4163UP,BROADCAST,RUNNING,MULTICAST mtu 1500 inet 172.18.45.153 netmask 255.255.192.0 broadcast 172.18.63.255 inet6 fe80::216:3eff:fe03:959b prefixlen 64 scopeid 0x20link ether 00:16:3e:03:95:9b txqueuelen 1000 (Ethernet) RX packets 34367847 bytes 9360264363 (9.3 GB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 34274797 bytes 6954263329 (6.9 GB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 sudo tcpdump -i eth0 tcp捕获特定源或目的IP地址的TCP报文sudo tcpdump src host 192.168.1.100 and tcp sudo tcpdump dst host 192.168.1.200 and tcp sudo tcpdump src host 192.168.1.100 and dst host 192.168.1.200 and tcp捕获特定端口的TCP报文sudo tcpdump port 80 and tcp保存捕获的数据包到文件sudo tcpdump -i eth0 port 80 -w data.pcap
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门