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

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

posted @ 2022-05-25 21:20  划水的猫  阅读(1081)  评论(0编辑  收藏  举报