TCP通信机制:三次握手、四次挥手、滑动窗口

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: TCP通信机制:三次握手、四次挥手、滑动窗口

1. TCP三次握手

TCP是一种面向连接的安全的流式传输协议,TCP报文的格式如下

  • 标志位URG、ACK、PSH、PST、SYN、FIN
  • SYN:请求建立连接
  • ACK:给对端应答
  • FIN:断开连接
  • 16位窗口大小:这里的窗口实际上就是滑动窗口(将在后面介绍),这个窗口大小只是记录了存放数据的缓冲区也就是窗口有多大,而不是实际存放数据的地方。
  • 32位序号:在请求建立连接时跟在SYN标志位后面的随机序号。
  • 32位确认序号:在应答时跟在ACK标志位后面的确认序号,它的值是对端在建立连接时跟随在对端发送的SYN后面的随机序号的值加上SYN携带的数据大小再加上1,如果对端SYN未携带数据,则直接在对端SYN后面随机序号的基础上加1。比如客户端发起连接并携带SYN 100(10),服务端收到后会回复ACK 111,表示连接请求已收到且10个字节的数据也已收到;如果客户端发起的连接为SYN 100,不携带数据,那么服务端回复为ACK 101。总之,ACK后面的确认序号值为对端SYN随机序号+携带数据大小+1。

TCP在建立连接的时候需要进行三次握手(TCP握手时一定有SYN标志,不带SYN标志的为建立连接后的正常数据传输)

  • 第一次握手:第一次握手应该由客服端发起,服务端被动等待连接。
  • 客户端:主动发起连接
  • 携带标志位SYN
  • 产生32位随机序号:比如100
  • 可以携带数据,也可以不携带数据,携带数据时要带上数据大小比如10字节 SYN 100(10),不携带数据为SYN 100。
  • 服务端:
  • 检测SYN的值是否为1。如果SYN值为0,则握手失败;如果SYN值为1,则握手成功,服务端回复,进行第二次握手。
  • 第二次握手:服务端回复并请求建立反向连接。
  • 服务端:服务端发送确认信号ACK并携带一个32位的确认序号,确认序号的值为客户端SYN随机序号的值+1,加的这个1实际上是SYN标志的大小1字节。同时,服务端向客户端发起一个反向的连接请求SYN,并携带一个32为随机序号。
  • 确认应答:ACK标志位+32位确认序号(值为客户端随机序号的值+1+携带的数据大小)
  • 客户端不带数据:ACK 101 —> 100+1
  • 客户端携带数据:ACK 111 —> 100+10+1
  • 发起一个连接请求:SYN+随机序号,同样可以携带数据也可以不携带数据
  • 不携带数据:SYN 200(0)
  • 携带数据:SYN 200(10)
  • 客户端:检测确认标志ACK是否为1,并校验确认序号是否正确。
  • 检测ACK:为1,对端收到并确认应答。
  • 校验:不携带数据时,确认序号为101,正好为自己发送的SYN100的值加1;携带数据时,确认序号为111,说明自己发送的10字节数据对端已收到。
  • 第三次握手:客户端向服务端发送确认数据包,完成反向连接的建立。
  • 客户端:确认应答,ACK+确认信号
  • 服务端未携带数据:ACK 201
  • 服务端携带数据:ACK 211
  • 服务端:
  • 检测ACK是否为1
  • 校验确认序号是否正确

至此,三次握手成功,双向连接均已建立,可以开始数据传输了。示意图如下:

2. TCP四次挥手

TCP断开连接时需要进行四次挥手:

  • 客户端与服务端哪一端主动断开连接都可以;
  • 挥手时需要一个标志位FIN,FIN后面也需要跟一个序号,序号的值为对端最后一次发送的ACK后面的确认序号;

四次挥手的过程如下:

  • 第一次挥手:某一端主动发起断开连接的请求。
  • 客户端:发送断开连接的请求
  • FIN + 序号(对端最后一个ACK后面的确认序号)
  • ACK + 序号(自己上一次ACK后面的确认序号)
  • 第二次挥手:另一端确认断开连接。
  • 服务端:
  • 检测FIN的值是否为1,如果不是1则挥手失败,即断开连接失败;
  • ACK + 序号(对端FIN后面的序号+收到数据的大小+1),告诉对端对方发送的数据自己收到了多少;
  • 第三次挥手:另一端请求断开反向连接(TCP是双向连接,经过前两次挥手只断开了单向连接,所以需要反向断开连接)。
  • 服务端:发送反向断开连接的请求。
  • FIN + 序号(客户端最后一次ACK所携带的确认序号);
  • ACK + 序号(自己上一次ACK后面的确认序号)
  • 第四次挥手:TCP双向连接断开。
  • 客户端:客户端对服务端发送的断开连接请求进行确认。
  • ACK + 序号(对端FIN后面的序号+收到数据的大小+1)

3. TCP连接与数据传输过程

首先看一个TCP连接与数据传输的示意图

在图中:

  • 1-3:三次握手阶段(握手阶段一定有SYN标志)
  • 4-6:数据传输阶段
  • 7-10:四次挥手阶段(挥手阶段一定有FIN标志)

图中的<mss 1460>表示最大数据长度,即告知对端给我发送数据的时候不要超过这个最大长度。

详细分析上述过程的完整图示如下

4. TCP滑动窗口机制

首先看滑动窗口的示意图

在图中,发送端速度快,接收端速度慢,一般来说谁先发送SYN谁就是客户端,因为客户端总是主动连接服务端,而服务端则被动等待客户端的连接。

在TCP中,滑动窗口实际上就是一块缓冲区(缓存)。在上图中,客户端与服务端进行数据传输的时候总是带有一个win 4096或win 6144等标志,这个win就代表滑动窗口的意思,而后面的数字则代表滑动窗口所表示的缓存区的大小。比如客户端发起的第一条握手请求,即fast sender所代表的1处,SYN, 0(0), win 4096, <mss 1460>

  • SYN表示请求建立连接
  • 0(0)表示随机序号为0,携带数据大小为0
  • win 4096 表示滑动窗口大小为4096字节,即4KB,可以参考图中右侧虚线框起来的部分
  • <mss 1460>表示允许对端一次发送的最大数据长度为1460字节

通过server端发送的 win 6144, <mss 1024> 可知,服务端滑动窗口大小为6KB,一次可以接受1KB数据。client端发送数据的速度是要快于server端接收数据的速度的,所以client端会发送多条数据,其中每条数据为1KB(由server端的mss指定最大接收长度),总共发送的数据不能超过6KB(server端的滑动窗口大小)。所以上图中client端总共发送了6条数据,每条数据长度为1KB(可见图中序号2-9数据传输),正好大小总共为6KB,等于server端窗口大小(实际上是server端缓冲区的大小)。

此时,server端的缓冲区已经满了,不能再接收数据了,只有当server端把数据读出的时候,缓冲区才能空出位置接受新数据。当server端读取了2K数据,那么server端的缓冲区将空余出2K字节,请见上图中的序号10处,server端向client端回复 ACK,6145, win 2048,表示server端收到了6144字节(6KB)数据,6145是由6144+1来的,win 2048表示server端现在缓冲区空余2K空间。当server端再次读出2K数据的时候,server再次向client发送一条数据 ACK,6145, win 4096,这里ACK和确认序号不变,win所代表的窗口大小发生了变化,变成了当前空余的缓冲区大小4KB,可见上图中序号11所代表的数据传输,此时在server发送数据10和11之间,client端并没有发送数据,但是server端要向client端告知自己的空闲缓冲区大小。

最后,12-18的过程实际上就是四次挥手的过程,client发送FIN断开连接,此时server还没有处理完缓冲区中的数据,所以每处理一批数据都会向client发送一条信息并告知缓冲区剩余大小,直到缓冲区中的数据全部处理完毕,server向client发送FIN断开连接。实际上滑动窗口就是缓冲区的大小,并且在发送数据过程中,并不是client发一条server就必须收一条,也可以发多条收多条,这是因为TCP是流式传输,一端发送的数据虽然没有立即被处理,但是已经存起来了,就存在了滑动窗口所表示的缓冲区中,当缓冲区满了,发送端就会临时阻塞,等待接收端缓冲区出现剩余空间。

上面所描述的过程都是所有数据传输都成功的前提下进行的,实际上,每条数据的发送都可能会失败,当发生传输失败的情况,TCP会进行重传,重传的示意图如下:

5. server服务端与client客户端编程实现

server.c

/************************************************************
  >File Name  : server.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年08月14日 星期日 19时53分24秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
int main(int argc, char* argv[])
{
  //创建用于监听的套接字
  int lfd = socket(AF_INET, SOCK_STREAM, 0);
  if(lfd == -1)
  {
    perror("socket err");
    exit(1);
  } 
  //bind
  struct sockaddr_in server_addr;
  //init
  memset(&server_addr, 0, sizeof(server_addr)); 
  //bzero(&server_addr, sizeof(serve_addr));
  server_addr.sin_family = AF_INET; //IPv4
  server_addr.sin_port = htons(8765);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //使用本机任意IP
  int ret = bind(lfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
  if(ret == -1)
  {
    perror("bind err");
    exit(1);
  }
  //设置监听
  ret = listen(lfd, 128);
  if(ret == -1)
  {
    perror("listen err");
    exit(1);
  }
  //等待并接受连接请求
  struct sockaddr_in client_addr;
  socklen_t client_len = sizeof(client_addr);
  int cfd = accept(lfd, (struct sockaddr*)&client_addr, &client_len);
  if(cfd == -1)
  {
    perror("accept err");
    exit(1);
  }
  char ipbuf[64];
  printf("client ip: %s, port: %d\n",
      inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
      ntohs(client_addr.sin_port));
  //通信
  while(1)
  {
    //接收数据
    char buf[1024] = {0};
    int len = read(cfd, buf, sizeof(buf));
    if(len < 0)
    {
      perror("read err");
      break;
    }
    else if(len == 0)
    {
      printf("client disconnect ...\n");
      break;
    } 
    else 
    {
      //读到数据
      printf("read buf : %s\n", buf);
      for(int i = 0; i < len; i++)
      {
        buf[i] = toupper(buf[i]);
      }
      printf("toupper read buf: %s\n", buf);
      //发送数据
      write(cfd, buf, strlen(buf) + 1);
    }
  }
  close(lfd);
  close(cfd);
  return 0;
}

client.c

/************************************************************
  >File Name  : client.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年08月15日 星期一 11时13分29秒
 ************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
  if(argc < 2)
  {
    printf("err:./exe port\n");
    return -1;
  }
  int port = atoi(argv[1]);
  //创建套接字
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  if(fd == -1)
  {
    perror("socket err");
    exit(1);
  }
  //连接服务器
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);
  int ret = connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
  if(ret == -1)
  {
    perror("connect err");
    exit(1);
  }
  //通信
  while(1)
  {
    //接收键盘输入
    char buf[512];
    printf("please input string: \n");
    fgets(buf, sizeof(buf), stdin);
    //发送给服务器
    write(fd, buf, strlen(buf));
    //接收服务端数据
    int len = read(fd, buf, sizeof(buf));
    if(len < 0)
    {
      perror("read err");
      exit(1);
    }
    else if(len == 0)
    {
      printf("server close connect ...\n");
      break;
    }
    else
    {
      printf("read buf: %s, buflen: %d\n", buf, len);
    }
  } 
  close(fd);
  return 0;
}


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
缓存 网络协议 安全
TCP三次握手四次挥手及常见问题解决方案
TCP三次握手四次挥手及常见问题解决方案
TCP三次握手四次挥手及常见问题解决方案
|
网络协议 测试技术
软件测试|TCP三次握手四次挥手
软件测试|TCP三次握手四次挥手
143 0
软件测试|TCP三次握手四次挥手
|
网络协议 网络性能优化
计算机网络【UDP与TCP协议(三次握手、四次挥手)】(下)
计算机网络【UDP与TCP协议(三次握手、四次挥手)】(下)
计算机网络【UDP与TCP协议(三次握手、四次挥手)】(下)
|
缓存 网络协议 网络性能优化
计算机网络【UDP与TCP协议(三次握手、四次挥手)】(上)
计算机网络【UDP与TCP协议(三次握手、四次挥手)】(上)
计算机网络【UDP与TCP协议(三次握手、四次挥手)】(上)
|
网络协议
TCP/UDP相关-三次握手四次挥手以及为什么三次握手-如何实现可靠UDP传输
TCP/UDP相关-三次握手四次挥手以及为什么三次握手-如何实现可靠UDP传输
137 0
|
网络协议
TCP三次握手与四次挥手
TCP三次握手与四次挥手
151 0
|
4月前
|
机器学习/深度学习 人工智能 网络协议
TCP/IP五层(或四层)模型,IP和TCP到底在哪层?
TCP/IP五层(或四层)模型,IP和TCP到底在哪层?
69 4
|
监控 网络协议 网络架构
IP协议【图解TCP/IP(笔记九)】
IP协议【图解TCP/IP(笔记九)】
|
域名解析 网络协议
IP协议, TCP协议 和DNS 服务分别是干什么的?
大家好,我是阿萨。昨天讲解了网络四层协议[TCP/IP协议族分为哪4层?]今天我们学习下IP 协议, TCP 协议和DNS 协议分别是干什么的。
273 0
IP协议, TCP协议 和DNS 服务分别是干什么的?
|
网络协议
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。