• 【项目 计网8】4.23 TCP状态转换 4.24半关闭、端口复用



    4.23 TCP状态转换

    在这里插入图片描述
    在这里插入图片描述

    • 2MSL(Maximum Segment Lifetime)
      主动断开连接的一方,最后进入一个TIME_WAIT状态,这个状态会持续:2msl
      msl:官方建议:2分钟,实际是30s

    当 TCP 连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK 后,连接的主动关闭方必须处于TIME_WAIT 状态并持续 2MSL 时间。

    这样就能够==让 TCP 连接的主动关闭方在它变成TIME_WAIT状态以后发送的最后一个 ACK 丢失的情况下,重新发送最终的 ACK。==最后这个ACK如果没有被B接收到(超时重传的计时内,小于2MSL),那么B就会再发一个FIN给A。

    主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号,被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN。事实上,被动关闭方总是重传 FIN 直到它收到一个最终的 ACK。

    关于三次握手四次挥手

    在握手的时候,第二次握手如果服务器不同意建立连接,直接不发ACK就可了。如果同意就发送ACK。当中没有数据牵扯。
    但是!断开连接的时候,比如A发完了所有消息准备断开。就会有前两次挥手。但此时B很有可能还有信息要发送给A。所以二三次挥手之间可能会间隔很长时间。直到B也决定断开连接,才会有三四次挥手。
    注意:一旦一二次挥手成功,A就只能回复B的消息收到了,而不能再给B发送新的报文。因为此时A到B的连接以及断开。只是B到A还没断开。
    B一般会受到所有ACK以后才会进行第三四次挥手。

    4.24半关闭、端口复用

    • 半关闭

    当 TCP 链接中 A 向 B 发送 FIN 请求关闭,另一端 B 回应 ACK 之后(A 端进入 FIN_WAIT_2状态),并没有立即发送 FIN 给 A,A 方处于半连接状态(半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据。
    在这里插入图片描述
    使用 close 中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为 0 时才关闭连接。shutdown 不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
    注意:

    1. 如果有多个进程(多线程)共享一个套接字,close 每被调用一次,计数减 1 ,直到计数为 0 时,也就是所用进程都调用了 close,套接字将被释放。
    2. 在多进程(多线程)中如果一个进程调用了 shutdown(sfd, SHUT_RDWR) 后,其它的进程将无法进行通信。
    3. 因为 shutdown是用于改变底层套接字的状态的,而close改变的只是文件描述符的引用计数。

    tcp_server.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[]) {
    
        // 创建socket
        int lfd = socket(PF_INET, SOCK_STREAM, 0);
    
        if(lfd == -1) {
            perror("socket");
            return -1;
        }
    
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = INADDR_ANY;
        saddr.sin_port = htons(9999);
        
        //int optval = 1;
        //setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    
        int optval = 1;
        setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
    
        // 绑定
        int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
        if(ret == -1) {
            perror("bind");
            return -1;
        }
    
        // 监听
        ret = listen(lfd, 8);
        if(ret == -1) {
            perror("listen");
            return -1;
        }
    
        // 接收客户端连接
        struct sockaddr_in cliaddr;
        socklen_t len = sizeof(cliaddr);
        int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
        if(cfd == -1) {
            perror("accpet");
            return -1;
        }
    
        // 获取客户端信息
        char cliIp[16];
        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
        unsigned short cliPort = ntohs(cliaddr.sin_port);
    
        // 输出客户端的信息
        printf("client's ip is %s, and port is %d\n", cliIp, cliPort );
    
        // 接收客户端发来的数据
        char recvBuf[1024] = {0};
        while(1) {
            int len = recv(cfd, recvBuf, sizeof(recvBuf), 0);
            if(len == -1) {
                perror("recv");
                return -1;
            } else if(len == 0) {
                printf("客户端已经断开连接...\n");
                break;
            } else if(len > 0) {
                printf("read buf = %s\n", recvBuf);
            }
    
            // 小写转大写
            for(int i = 0; i < len; ++i) {
                recvBuf[i] = toupper(recvBuf[i]);
            }
    
            printf("after buf = %s\n", recvBuf);
    
            // 大写字符串发给客户端
            ret = send(cfd, recvBuf, strlen(recvBuf) + 1, 0);
            if(ret == -1) {
                perror("send");
                return -1;
            }
        }
        
        close(cfd);
        close(lfd);
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    tcp_client.c

    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main() {
    
        // 创建socket
        int fd = socket(PF_INET, SOCK_STREAM, 0);
        if(fd == -1) {
            perror("socket");
            return -1;
        }
    
        struct sockaddr_in seraddr;
        inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
        seraddr.sin_family = AF_INET;
        seraddr.sin_port = htons(9999);
    
        // 连接服务器
        int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    
        if(ret == -1){
            perror("connect");
            return -1;
        }
    
        while(1) {
            char sendBuf[1024] = {0};
            fgets(sendBuf, sizeof(sendBuf), stdin);
    
            write(fd, sendBuf, strlen(sendBuf) + 1);
    
            // 接收
            int len = read(fd, sendBuf, sizeof(sendBuf));
            if(len == -1) {
                perror("read");
                return -1;
            }else if(len > 0) {
                printf("read buf = %s\n", sendBuf);
            } else {
                printf("服务器已经断开连接...\n");
                break;
            }
        }
    
        close(fd);
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    端口复用

    端口复用最常见的用途是:

    • 防止服务器重启时之前绑定的端口还未释放

    • 程序突然退出而系统没有释放端口
      (没有被释放的端口就无法再次建立连接)

    #include 
    #include 
    //设置套接字属性(不仅仅能设置端口复用)set socket option
    int setsockopt(int sockfd,int level,int optname,const void* optval,socklen_t optlen);
    -sockfd:打开的套接字的文件描述符
    -level:级别 SOL_SOCKET(端口复用的级别)
    -optname:
    	-SO_REUSEPORT	允许重用端口复用
    	-SO_REUSEADDR   允许重用地址复用
    -optval:端口复用的值(整形)
    	-1:可以复用
    	-0:不可以复用
    -optlen:optval参数的大小
    
    端口复用,设置的时机是在服务器端口绑定端口之前
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    常看网络相关信息的命令:netstat(netstate)
    参数:

    • a 所有的socket (all)

    • p 显示正在使用socket的程序名称(programs)

    • n 直接使用IP地址,而不通过域名服务器(numric)

    netstat -anp|grep 9999
    
    
    • 1
    • 2

    在这里插入图片描述有三个socket是因为服务器端有一个监听还有个数据通信的。

    grep 9999:这个命令会筛选出包含 “9999” 这个数字的行。

    所以整个命令的目的是查看哪些进程或连接正在使用端口 “9999”。

    在通信过程中,状态不发生改变

  • 相关阅读:
    MySQL解决group by分组后未排序问题
    Nginx优化文件上传大小限制
    JSD-2204-Elasticsearch-SpringData-酷鲨商城概述-Day07
    ErrCode: 13102001没有找到名称为 ‘xxx‘ 的数据源
    DiFi: A Go-as-You-Pay Wi-Fi Access System 精读笔记(三)
    葡萄糖-聚乙二醇-6-羧甲基荧光素 Glucose-PEG-6-FAM
    Centos修改系统时间
    【基于python+Django的有机食品推荐系统-哔哩哔哩】 https://b23.tv/lqRcF1y
    AI产品经理 | 入行AI大模型的必备知识
    代码质量与安全 | “吃狗粮”能够影响到代码质量?来了解一下!
  • 原文地址:https://blog.csdn.net/weixin_43896452/article/details/132527000