• linux系统之一 全连接与半连接队列


    一、全连接与半连接队列

    在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:
    半连接队列,也称 SYN 队列;
    全连接队列,也称 accepet 队列;

    服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,
    接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。

    二、相关调试命令

    1.netstat -s可查看到当前系统半连接队列满导致的丢包统计,但该数字记录的是总丢包数

    2.半连接队列长度Linux内核中,主要受tcp_max_syn_backlog影响

    $ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
    128

    3.全连接队列长度是应用程序调用listen时传入的backlog以及内核参数net.core.somaxconn二者之中较小的那个

    $ cat /proc/sys/net/core/somaxconn
    128

    // 函数:int listen(int s, int backlog)

    4.在服务端可以使用 ss 命令,来查看 TCP 全连接队列的情况

    # -l 显示正在监听(listening)的socket
    # -n 不解析服务名称
    # -t 只显示tcp socket
    $ ss -ntl State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 1 128 127.0.0.1:6000 *:*

    Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接个数;
    Send-Q:当前全连接最大队列长度,上面的输出结果说明监听 6000 端口的 TCP 服务进程,最大全连接长度为 128;

    5.netstat -anlp 可查看具体socket状态

    $ netstat -anlp | grep 6000
    tcp        1      0 127.0.0.1:6000          0.0.0.0:*               LISTEN      21110/./server      
    tcp        0      0 127.0.0.1:6000          127.0.0.1:39932         ESTABLISHED -            #客户端connect返回了,服务端accept未返回  
    tcp        0      0 127.0.0.1:6000          127.0.0.1:39712         ESTABLISHED 21110/./server      
    tcp        0      0 127.0.0.1:39712         127.0.0.1:6000          ESTABLISHED 21256/./client      
    tcp        0      0 127.0.0.1:39932         127.0.0.1:6000          ESTABLISHED 21375/./client 

    6.ps可查看进程的状态

    $ ps -a
      PID TTY          TIME CMD
    14228 pts/8    00:00:00 ps
    21110 pts/11   00:00:00 server
    21256 pts/10   00:00:00 client
    21375 pts/9    00:00:00 client
    $ ps -t pts/11 -o pid,ppid,tty,stat,args,wchan | grep server             #pts/11 表示伪终端号6
    PID PPID TT STAT COMMAND WCHAN
    21110 18216 pts/11 S+ ./server sk_wait_data $ ps -t pts/10 -o pid,ppid,tty,stat,args,wchan | grep client
    PID   PPID  TT       STAT COMMAND                     WCHAN
    21256 18172 pts/10 S+ ./client n_tty_read $ ps -t pts/9 -o pid,ppid,tty,stat,args,wchan | grep client
    PID   PPID  TT       STAT COMMAND                     WCHAN
    21375 18096 pts/9 S+ ./client n_tty_read

    三个网络进程的STAT列都是“S+”,表明进程在为等待某些资源而睡眠。“+”位于后台的进程组。

    Linux在进程阻塞于accept或者connect时,输出inet_csk_accept或者wait_for_connect;

    在进程阻塞于套接字输入或输出时,输出tcp_data_wait或者sk_wait_data;

    在进程阻塞于终端I/O时,输出n_tty_read(有的文档写输出read_chan)

    三、服务端代码

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<unistd.h>
    #include<string.h>
    
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    
    
    int main()
    {
    //创建用于监听的套接字,这个套接字是一个文件描述符,用于检测有没有客户端发起一个新的连接
        int listenfd = socket(AF_INET,SOCK_STREAM,0);
        assert(listenfd != -1);
        
        struct sockaddr_in addr;
        memset(&addr,0,sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port =htons(6000);//转化端口号
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");//回环地址
    
    // 将得到的监听的文件描述符和本地的IP端口进行绑定
        int res = bind(listenfd,(struct sockaddr*)&addr,sizeof(addr));
        assert(res != -1);
        
    //设置监听(成功之后开始监听,监听的是客户端的连接)
        res = listen(listenfd,5);
        assert(res != -1);
    
    //通信
        while (1)
        {
            struct sockaddr_in cli_addr;
            socklen_t cli_len = sizeof(cli_addr);
            int c = accept(listenfd,(struct sockaddr*)&cli_addr,&cli_len);
            if(c == -1)
            {
                printf("Get One Client Link Error\n");
                continue;
            }
    
            while (1)
            {
                char buff[128] = {0};
                int n = recv(c,buff,127,0);//读取数据放在buff中,一次读取127个
                if(n <= 0)
                {
                    printf("Client will unlink\n");
                    break;
                }
                printf("%d : %s\n",c,buff);
                send(c,"OK",2,0);
            }
            close(c);
        }
        
    //断开连接,关闭套接字(四次挥手)
        close(listenfd);
    
    
        return 0;
    }
    server.c

    四、客户端代码

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<unistd.h>
    #include<string.h>
    
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    int main()
    {
    
    //创建一个通信的套接字,需要指定服务器的IP和端口号y
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        assert(sockfd != -1);
    
        struct sockaddr_in ser_addr;
        memset(&ser_addr,0,sizeof(ser_addr));
        ser_addr.sin_family = AF_INET;
        ser_addr.sin_port =htons(6000);//转化端口号
        ser_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//回环地址
    
    
    //连接服务器,需要知道服务器绑定的IP和端口
        int res = connect(sockfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
        assert(res != -1);
    
    //通信
        while (1)
        {
            
            printf("input: ");
            char buff[128] = {0};
            fgets(buff,127,stdin);
            if(strncmp(buff,"end",3) == 0)
            {
                break;
            }
    
            send(sockfd,buff,strlen(buff) - 1,0);//\n不发
    
            memset(buff,0,128);
            recv(sockfd,buff,127,0);
            printf("%s\n",buff);
        }
    
    //断开连接
        close(sockfd);
        
        return 0;
    }
    client.c

    注:先启动服务端,再启多个客户端

  • 相关阅读:
    Java基础技术多线程与并发面试【笔记】
    Java GC【笔记】
    Java JVM【笔记】
    Java基础技术JVM面试【笔记】
    Java HashSet和TreeSet【笔记】
    Java TreeMap 和 LinkedHashMap【笔记】
    Java HashMap【笔记】
    Java LinkedList【笔记】
    Java ArrayList【笔记】
    java基础技术集合面试【笔记】
  • 原文地址:https://www.cnblogs.com/573583868wuy/p/16310966.html
Copyright © 2020-2023  润新知