Linux下的网络编程——C/S模型TCP(二)

前言:

TCP协议被广泛应用 其根本原因就是提供了详尽的可靠性保证 基于TCP的上层应用非常多 比如HTTP、HTTPS、FTP、SSH、MySQL等。TCP是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓“连接”,其实是客户端和服务器端内存里保持的一份关于对方的信息(IP地址、端口号)下面让我们学习一下什么是TCP协议吧

TCP协议

1.TCP通信时序

下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手四次握手

三次握手建立连接四次握手根据半关闭断开连接

 (1).三次握手

        1)客户端向主机端发送申请建立连接

        2)主机端向客户端发送接收到申请请求并确定连接

        3)客户端接收到主机端并确定是最近的一次确定连接申请后,确定连接

        主动发起连接请求端,发送 SYN 标志位,请求建立连接。 携带序号号、数据字节数(0)、滑动窗口大小。

       被动接受连接请求端,发送 ACK 标志位,同时携带 SYN 请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。

       主动发起连接请求端,发送 ACK 标志位,应答服务器连接请求。携带确认序号。
 

 (2).四次握手

        1)客户端向主机端发送申请断开连接

        2)主机端向客户端发送信息,说明已经接收到客户端的请求

        3)待主机端处理完线路上两者之间的数据后主动推送数据给客户端代表此时已经处理完所有数据

        4)客户端确定后,最后确定并释放自己的连接

           主动关闭连接请求端, 发送 FIN 标志位。 

            被动关闭连接请求端, 应答 ACK 标志位。          ----- 半关闭完成


            被动关闭连接请求端, 发送 FIN 标志位。

            主动关闭连接请求端, 应答 ACK 标志位。         ----- 连接全部关闭

注意:不能三次挥手的原因:第四次挥手用于确定主机的数据已经发送完毕的确认

2.滑动窗口(TCP流量控制)

        介绍UDP时我们描述了这样的问题:如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,而接收缓冲区的大小是固定的,就会丢失数据。TCP协议通过“滑动窗口(Sliding Window)”机制解决这一问题。看下图的通讯过程:

  1. 发送方仅能发送发送窗口内的数据,发送窗口之外的数据不允许发送
  2. 接收方只能接收接收窗口内的数据,接收窗口之外的数据不能被接收;
  3. 窗口的大小由接收方控制,用于调整传输的速率

  1. 发送端发起连接,声明最大段尺寸是1460,初始序号是0,窗口大小是4K,表示“我的接收缓冲区还有4K字节空闲,你发的数据不要超过4K”。接收端应答连接请求,声明最大段尺寸是1024,初始序号是8000,窗口大小是6K。发送端应答,三方握手结束。
  2. 发送端发出段4-9,每个段带1K的数据,发送端根据窗口大小知道接收端的缓冲区满了,因此停止发送数据。
  3. 接收端的应用程序提走2K数据,接收缓冲区又有了2K空闲,接收端发出段10,在应答已收到6K数据的同时声明窗口大小为2K。
  4. 接收端的应用程序又提走2K数据,接收缓冲区有4K空闲,接收端发出段11,重新声明窗口大小为4K。
  5. 发送端发出段12-13,每个段带2K数据,段13同时还包含FIN位。
  6. 接收端应答接收到的2K数据(6145-8192),再加上FIN位占一个序号8193,因此应答序号是8194,连接处于半关闭状态,接收端同时声明窗口大小为2K。
  7. 接收端的应用程序提走2K数据,接收端重新声明窗口大小为4K。
  8. 接收端的应用程序提走剩下的2K数据,接收缓冲区全空,接收端重新声明窗口大小为6K。
  9. 接收端的应用程序在提走全部数据后,决定关闭连接,发出段17包含FIN位,发送端应答,连接完全关闭。

        上图在接收端用小方块表示1K数据,实心的小方块表示已接收到的数据,虚线框表示接收缓冲区,因此套在虚线框中的空心小方块表示窗口大小,从图中可以看出,随着应用程序提走数据,虚线框是向右滑动的,因此称为滑动窗口

发送给连接对端,本端的缓冲区大小(实时),保证数据不会丢失 

3.TCP状态转换

结合三次握手、四次挥手 理解记忆。 

   1). 主动发起连接请求端:    

CLOSE  --》 发送SYN  --》  SEND_SENT   --》   接收 ACK 、SYN   --》   SEND_SENT   --》  发送 ACK   --》    ESTABLISHED(数据通信态)

  server:
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

void do_sigchild(int num)
{
    while (waitpid(0, NULL, WNOHANG) > 0)
        ;
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i, n;
    pid_t pid;
    struct sigaction newact;

    newact.sa_handler = do_sigchild;
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    sigaction(SIGCHLD, &newact, NULL);

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, 20);

    printf("Accepting connections ...\n");
    while (1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        printf("-------------------------%d\n", connfd);

        pid = fork();
        if (pid == 0) {
            Close(listenfd);
            while (1) {
                n = Read(connfd, buf, MAXLINE);
                if (n == 0) {
                    printf("the other side has been closed.\n");
                    break;
                }
                printf("received from %s at PORT %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                        ntohs(cliaddr.sin_port));

                for (i = 0; i < n; i++)
                    buf[i] = toupper(buf[i]);

                Write(STDOUT_FILENO, buf, n);
                Write(connfd, buf, n);
            }
            Close(connfd);
            return 0;
        } else if (pid > 0) {
            Close(connfd);
        }  else
            perr_exit("fork");
    }
    return 0;
}
  client:
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;

    sockfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    while (fgets(buf, MAXLINE, stdin) != NULL) {
        Write(sockfd, buf, strlen(buf));
        n = Read(sockfd, buf, MAXLINE);
        if (n == 0) {
            printf("the other side has been closed.\n");
            break;
        }
        else
            Write(STDOUT_FILENO, buf, n);
    }

    Close(sockfd);

    return 0;
}

    2). 主动关闭连接请求端:

ESTABLISHED(数据通信态)  --》   发送 FIN  --》  FIN_WAIT_1    --》   接收ACK     --》 FIN_WAIT_2(半关闭) 

接收对端发送 FIN    --》    FIN_WAIT_2(半关闭) --》   回发ACK    -- 》  TIME_WAIT(只有主动关闭连接方,会经历该状态) --》   等 2MSL时长    --》    CLOSE 

    3). 被动接收连接请求端:

CLOSE    --》   LISTEN    -- 》   接收 SYN    --》     LISTEN    -- 》   发送 ACK、SYN    --》 SYN_RCVD    --》    接收ACK     --》     ESTABLISHED(数据通信态)

    4). 被动关闭连接请求端:

ESTABLISHED(数据通信态) --》   接收 FIN    --》   ESTABLISHED(数据通信态)   --》    发送ACK    --》     CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态)      --》   发送FIN  --》     LAST_ACK     --》    接收ACK     -- 》    CLOSE   

 

重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)

                   netstat -apn | grep  端口号

4.   2MSL时长:

一定出现在【主动关闭连接请求端】。 --- 对应 TIME_WAIT 状态。

保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)


http://www.niftyadmin.cn/n/5045296.html

相关文章

Date类的学习笔记-超级详细

Date 的定义, 在开始研究这个之前我们首先要能够明白一点&#xff0c;这个 Date 其实本质上是一个对象&#xff0c;我们通过这个对象可以去构建变量&#xff0c;知道这个之后就可以开展后续的研究了 JDK 通用 Date 类的构造方法 测试 获取当前的时间 // 构造这个日期对象Date…

用PHP实现极验验证功能

极验验证是一种防机器人的验证机制&#xff0c;可以通过图像识别等方式来判断用户是否为真实用户。在实现极验验证功能时&#xff0c;您需要进行以下步骤&#xff1a; 1 注册极验账号&#xff1a; 首先&#xff0c;您需要在极验官网注册账号并创建一个应用&#xff0c;获取相应…

哪些远程桌面软件适合与团队共享屏幕和文件

远程桌面软件是一种方便团队协作和共享工作的工具。它们能够允许用户通过互联网连接到其他计算机&#xff0c;实现远程访问、共享屏幕和文件、以及协同工作。以下是一些适合与团队共享屏幕和文件的常用远程桌面软件。 1、RayLink RayLink是一款功能强大且易于使用的远程桌面软…

应用案例 | 使用dataFEED OPC Suite将汽车零部件工厂数据集成到SAP Business Suite

一 背景 某知名空气过滤和热管理组件供应商是一家专业的汽车零部件制造集团——专注于液体和空气过滤系统、进气系统以及热管理组件的生产与销售。该集团在全球范围内拥有24个生产工厂&#xff0c;并在运营中广泛采用了SAP Business Suite作为其企业资源规划&#xff08;ERP&a…

Linux工具——gdb

目录 一&#xff0c;gdb简介 二&#xff0c;gdb的指令 1.查看gdb是否有下载 2.需要明确的知识点 3.调试指令 1.打开调试代码 2.以某行为起点显示代码 3.打断点&#xff0c;查看断点&#xff0c;删除断点&#xff0c;禁用断点&#xff0c;启用断点 4.逐过程&#xff0c;逐…

LocalDateTime工具类篇

TimeFormatEnum import lombok.Getter;import java.time.format.DateTimeFormatter;/*** 描述 : 时间格式化类型枚举类.** <p>说明&#xff1a;* 1. TimeFormatEnum.LONG_DATE_PATTERN_LINE.formatter; // 获取默认时间格式: yyyy-MM-dd HH:mm:ss* 2. 对于LocalDate&…

C语言 数据结构 顺序表的 插入与删除

请输入n个元素&#xff0c;-1结束: 1 2 3 4 100 -1 顺序表&#xff1a; 1 2 3 4 100 指定一个元素&#xff0c;在此元素之前插入一个新元素:5 5在表的[-1]下标位置 .请输入一个新元素值:11 插入位置无效 顺序表&#xff1a; 1 2 3 4 100 请输入列表中要删除的元素&#xff1a;4…

Shell-条件控制语句2

案例: 写一个脚本传参的方式做一个计算器 加减乘除 [roottest day3]# cat count.sh #!/bin/bash expr $1 $2 &>/dev/null [ $? -ne 0 ] && exit echo $1$2$[$1$2] echo $1-$2$[$1-$2] echo $1*$2$[$1*$2] echo $1/$2$[$1/$2…