TCP/IP中关于socket中listen参数与内核配置的问题

287 阅读4分钟

实验内容

设置一个server和一个client,其中server只取出一个accept连接并原地阻塞,client在执行connect函数之后阻塞,查看server端的状态信息

内核部分参数配置

net.core.somaxconn
全连接队列长度 = min(backlog, 内核参数 net.core.somaxconn)
backlog 由int listen(int sockfd, int backlog)传入
修改方式 echo 1 > /proc/sys/net/core/somaxconn
net.ipv4.tcp_abort_on_overflow
超出处理能力时,对新来的SYN直接回RST,丢弃连接
0:关闭。1:开启
修改方式 echo 1 > /proc/sys/net/ipv4/tcp_abort_on_voerflow
net.ipv4.tcp_max_syn_backlog
SYN_RCVD状态连接的最大数(半连接队列的长度)
半连接队列长度 = min(backlog, 内核参数 net.core.somaxconn, 内核参数tcp_max_syn_backlog)
修改方式 echo 1 > /proc/sys/net/ipv4/tcp_max_syn_backlog
    

实验代码

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8089 // 端口号
#define BACKLOG 1 // 监听队列长度

int main() {
int listen_fd, conn_fd; // 监听套接字和连接套接字
struct sockaddr_in serv_addr, cli_addr; // 服务端和客户端地址结构
socklen_t cli_len; // 客户端地址长度
char buffer[1024]; // 缓冲区
int n; // 读写字节数

// 创建监听套接字
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
    perror("socket error");
    exit(1);
}

// 设置服务端地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 绑定监听套接字到服务端地址
if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
    perror("bind error");
    exit(1);
}

// 开始监听
if (listen(listen_fd, 1) == -1) {
    perror("listen error");
    exit(1);
}

printf("Server is listening on port %d\n", PORT);

while (1) {
    // 接受客户端连接请求
    cli_len = sizeof(cli_addr);
    conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len);
    printf("成功接收!\n");
    while(1)
    {

    }
    if (conn_fd == -1) {
        perror("accept error");
        continue;
    }

    // printf("Client %s:%d connected\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

    // // 读取客户端发送的数据
    // n = read(conn_fd, buffer, sizeof(buffer));
    // if (n == -1) {
    //     perror("read error");
    //     close(conn_fd);
    //     continue;
    // }
    // buffer[n] = '\0';
    // printf("Received from client: %s\n", buffer);

    // // 向客户端发送数据
    // n = write(conn_fd, buffer, strlen(buffer));
    // if (n == -1) {
    //     perror("write error");
    //     close(conn_fd);
    //     continue;
    // }
    // printf("Sent to client: %s\n", buffer);

    // // 关闭连接套接字
    // close(conn_fd);
    // printf("Client %s:%d disconnected\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
}

// 关闭监听套接字
close(listen_fd);
return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8089 // 端口号
#define SERVER_IP "101.33.199.60" // 服务端IP地址

int main() {
int sock_fd; // 套接字描述符
struct sockaddr_in serv_addr; // 服务端地址结构
char buffer[1024]; // 缓冲区
int n; // 读写字节数

// 创建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
    perror("socket error");
    exit(1);
}

// 设置服务端地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

// 连接到服务端
if (connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
    perror("connect error");
    exit(1);
}

printf("Connected to server %s:%d\n", SERVER_IP, PORT);

// 从标准输入读取数据
printf("Enter a message: ");
fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // 去掉换行符

while(1){}
// 向服务端发送数据
n = write(sock_fd, buffer, strlen(buffer));
if (n == -1) {
    perror("write error");
    exit(1);
}
printf("Sent to server: %s\n", buffer);


// // 读取服务端发送的数据
// n = read(sock_fd, buffer, sizeof(buffer));
// if (n == -1) {
//     perror("read error");
//     exit(1);
// }
// buffer[n] = '\0';
// printf("Received from server: %s\n", buffer);

// // 关闭套接字
// close(sock_fd);
return 0;
}

window下的客户端的话要增加2s的实验,因为发送RST报文到windows系统需要时间 , demo如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h> // For using inet_pton() or InetPton()

// Define the server address and port
#define SERVER_ADDR "101.33.199.60" // Change this to the actual Linux server IP address
#define SERVER_PORT 8089

// Define the buffer size and the message to send
#define BUFFER_SIZE 1024
#define MESSAGE "Hello from Windows client!"

int main() {
    // Initialize the Winsock library
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
        printf("WSAStartup failed: %d\n", result);
        return 1;
    }

    // Create a socket for connecting to the server
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        printf("socket failed: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // Set up the server address structure
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    // Use inet_pton() or InetPton() instead of inet_addr()
    result = inet_pton(AF_INET, SERVER_ADDR, &(serverAddr.sin_addr));
    // result = InetPton(AF_INET, SERVER_ADDR, &(serverAddr.sin_addr));
    if (result <= 0) {
        printf("inet_pton failed: %ld\n", WSAGetLastError());
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }

    // Connect to the server
    result = connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    if (result == SOCKET_ERROR) {
        printf("connect failed: %ld\n", WSAGetLastError());
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }

    printf("成功连接\n");

    Sleep(2000);

    // Send a message to the server
    result = send(clientSocket, MESSAGE, strlen(MESSAGE), 0);
    printf("result : %d\n", result);
    if (result == SOCKET_ERROR) {
        printf("send failed: %ld\n", WSAGetLastError());
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }
    printf("result = : %d", result);
    while (1) {}

    // Close the socket and clean up the Winsock library
    closesocket(clientSocket);
    WSACleanup();

    return 0;
}

实验步骤

  1. 运行服务端,让客户端正常连接
  2. 运行多个客户端,使得tcp的全连接队列满
  3. 再运行一个tcp客户端,使用tcpdump抓取报文信息

结果演示

查看端口连接情况(这里已经达到步骤bss -lnt
State       Recv-Q Send-Q             Local Address:Port                            Peer Address:Port              
LISTEN      2      1                              *:8089 


查看端口状态
netstat -ano |grep 8089
tcp        2      0 0.0.0.0:8089            0.0.0.0:*               LISTEN      off (0.00/0/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10421    ESTABLISHED off (0.00/0/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10468    ESTABLISHED off (0.00/0/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10390    ESTABLISHED off (0.00/0/0)

溢出情况查询(128是之前实验导致的。这里从128开始)
netstat -s | grep overflowed
 128 times the listen queue of a socket overflowed

 tcpdump报文情况(在满之前都是一样的,Tcp三次握手)
 tcpdump port 8089
 02:03:34.367641 IP 223.74.237.181.trim > VM-12-11-centos.8089: Flags [S], seq 2201118136, win 64240, options [mss 1424,nop,wscale 8,nop,nop,sackOK], length 0
02:03:34.367700 IP VM-12-11-centos.8089 > 223.74.237.181.trim: Flags [S.], seq 3189354376, ack 2201118137, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
02:03:34.391116 IP 223.74.237.181.trim > VM-12-11-centos.8089: Flags [.], ack 1, win 261, length 0

在Tcp全连接队列已满的情况下,我们再发起一个客户端连接,看看会发生什么

查看端口连接情况(没有变化)
ss -lnt
State       Recv-Q Send-Q             Local Address:Port                            Peer Address:Port              
LISTEN      2      1                              *:8089 


查看端口状态 (多出一个SYN_RECV的状态)
netstat -ano |grep 8089
tcp        2      0 0.0.0.0:8089            0.0.0.0:*               LISTEN      keepalive (0.12/0/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10745    SYN_RECV    on (4.52/3/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10421    ESTABLISHED off (0.00/0/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10468    ESTABLISHED off (0.00/0/0)
tcp        0      0 10.0.12.11:8089         223.74.237.181:10390    ESTABLISHED off (0.00/0/0)


溢出情况查询(128是之前实验导致的。这里从128开始)
 129 times the listen queue of a socket overflowed


  tcpdump报文情况
  02:04:28.787681 IP 223.74.237.181.10745 > VM-12-11-centos.8089: Flags [S], seq 3213856928, win 64240, options [mss 1424,nop,wscale 8,nop,nop,sackOK], length 0
02:04:28.787742 IP VM-12-11-centos.8089 > 223.74.237.181.10745: Flags [S.], seq 335259074, ack 3213856929, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
02:04:28.807390 IP 223.74.237.181.10745 > VM-12-11-centos.8089: Flags [.], ack 1, win 261, length 0
02:04:28.807423 IP VM-12-11-centos.8089 > 223.74.237.181.10745: Flags [R], seq 335259075, win 0, length 0

可以看到,当全连接队列满时,服务端收到客户端的SYN请求,仍会发送SYN应答,但当客户端发送第三次握手信息时,由于全连接队列已满故内核会丢掉此次连接。又由于我们配置了RST重置,所以我们会看到服务端发送了一个RST报文。

 [root@VM-12-11-centos test_demo]# ./client
connect error: Connection reset by peer
当再次运行客户端时可以发现客户端直接断开了连接