<网络> 通信函数listen的第二个参数

目录

前言:

一、实验

二、listen 函数的第二个参数

(一)未完成连接队列和全连接队列

(二)为什么 TCP 中要维护连接队列?

(三)为什么连接队列的长度不能太长?

(四)全连接队列的长度决定因素


前言:

在编写 TCP 套接字跟 HTTP 的服务器代码时,我们使用 listen 函数将套接字设置为监听状态,等待客户端的连接请求。其中 listen 函数的第一个参数就是需要设置为监听状态的套接字,而 listen 函数的第二个参数我们一般将其设置为32,该参数常常被忽略或者不完全理解,那么该参数具体是什么含义呢?接下来通过实验来理解一下该参数。

一、实验

下面我们通过一个简单的实验来说明一下 listen 函数的第二个参数的含义

<网络> HTTP-CSDN博客 见一见请求 小节中:

  • 将 listen 的第二个参数值设置为2,即 default_port
  • 服务器初始化完成之后,先不调用 accept 函数获取底层连接,服务器启动就一个死循环。
#pragma once

#include "Sock.hpp"

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

class HttpServer;

class ThreadData
{
public:
    ThreadData(int sock, const uint16_t &port, const string &ip, HttpServer *httpsvr)
        :sock_(sock), port_(port), ip_(ip), httpsvr_(httpsvr)
    {}
    
    ~ThreadData()
    {
        if(sock_>=0) close(sock_);
    }
public:
    int sock_;
    uint16_t port_;
    string ip_;
    HttpServer *httpsvr_;
};

class HttpServer
{
    static const uint16_t default_port = 2;
    using func_t = function<string(const string &)>;
public: 
    HttpServer(func_t func, uint16_t port = default_port)
        : func_(func), _port(port)
    {}

    void Init()
    {
        listensock_.Socket();
        listensock_.Bind(_port);
        listensock_.Listen(); // 封装的listen函数此时为2
        logMessage(Debug, "Init server success");
    }

    void Start()
    {
        while(true)
        {
        }
    }

    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());

        ThreadData *td = static_cast<ThreadData*>(args);

        // 假设一次都读完了
        char buff[4096];
        std::string request;
        ssize_t s = recv(td->sock_, buff, sizeof(buff)-1, 0);
        if(s > 0)
        {
            buff[s] = 0;
            request = buff;
            string response = td->httpsvr_->func_(request);
            send(td->sock_, response.c_str(), response.size(), 0);
        }
        else
        {
            logMessage(Debug, "Cilent [%d -> %s:%d] Quit", td->sock_, td->ip_.c_str(), td->port_);
        }

        delete td;
        return nullptr;
    }

    ~HttpServer()
    {
        listensock_.Close();
    }
private:
    uint16_t _port;
    Sock listensock_;
    func_t func_;
};

运行该服务程序,使用 netstat -nltp 命令查看当前 TCP 网络协议,可以看出当前服务正处于监听状态:

接下来,我们依次 telnet 127.0.0.1 8081 四次向该服务发起连接请求,通过命令 sudo netstat -ntp | head -2 && sudo netstat -ntp | grep 8081 查看该服务器的连接数。如上所示,向服务器发送了四次请求,前三次请求处于 ESTABLISHED 状态,而第四次再次向服务发起连接时,服务器新增了一个状态为 SYN_RCVD 的连接:

服务器上面新增了一个状态为 SYN_RCVD 的连接,在<网络> 传输层的TCP/UDP协议-CSDN博客文章中已经清楚说明 SYN_RCVD 表示服务器没有对客户端发来的请求连接进行响应:

对于上面的 SYN_RCVD 状态的连接,由于服务器长时间不对其响应,所以三次握手失败,该连接被自动释放。再一次查询时,该连接消失:

根据以上的实验现象,可发现。在该实验中,无论有多少客户端向服务器发起连接请求,最终在服务器端最多只有三个连接会建立成功。当第四个连接请求到达服务器时,服务器只收到了该客户端发送的 SYN 请求,但并没有对其进行响应,该连接在一段时间之后就会自动关闭了。对于后续的连接请求,服务器会直接拒绝这些请求。我们可以猜测,是与listen函数的第二个参数有关。

二、listen 函数的第二个参数

(一)未完成连接队列和全连接队列

实际上,Linux 内核协议栈为一个 TCP 连接管理使用两个队列:

  • 监听队列(Listen Queue):也称为未完成连接队列(incomplete connection queue)或半连接队列(half-open connect queue)。该队列用于存放服务器正在等待完成三次握手过程的连接请求。当服务器调用 listen 函数将套接字设置为监听状态后,它会开始接受传入的连接请求,并将这些请求放入监听队列中。三次握手过程中,服务器收到客户端的 SYN 请求后,会将该连接请求从监听队列中取出并处理。
  • 已完成连接队列(Completed Connection Queue):也称为全连接队列。该队列用于存放已经完成三次握手并建立连接。当服务器成功处理完成连接请求并完成三次握手后,将连接从监听队列移到全连接队列中,表示该连接已经准备好进行数据交换。

全连接队列的长度实际上受到 listen 函数的第二个参数的影响。一般情况下,TCP 全连接队列的长度等于 listen 函数的第二个参数的值+1

在上面实验中,listen 的参数我们设置为2,这意味着服务器的全连接队列的长度为3。因此,服务器最多允许同时存在三个处于 ESTABLISHED 状态的连接。

半连接队列(监听队列)和全连接队列(已完成连接队列)在连接管理中起着不同的作用。半连接队列用于存放未完成连接的请求,而全连接队列用于存放已经建立连接的队列。通过合理设置 listen 函数的第二个参数,可以控制全连接队列的长度,从而限制并发连接数从而保护服务器资源。

(二)为什么 TCP 中要维护连接队列?

在服务器端启动时,服务器预先创建多个线程用于处理客户端的请求。主线程会从底层接受连接并将其交给这些服务线程进行处理。如果向服务器发起连接的客户端较少,那么连接一旦在底层建立好,主线程会立即接受并将其交给服务线程处理。这样就可以快速响应连接请求并提供服务了。

在高负载的环境下,服务器可能会同时收到多个连接请求。如果每个服务线程都在为某个连接提供服务,主线程在这段时间内无法接收新的连接请求。这时,已经建立好的连接就会被放入连接队列中。只有当某个服务线程空闲时,主线程才能从连接队列中获取已建立的连接。

如果没有连接队列,当所有服务线程都在提供服务时,新的连接请求将直接被拒绝,这可能导致一些客户端无法及时建立连接。通过设置连接队列,当某个服务线程完成服务后,如果连接队列中有已建立的连接,主线程可以立即从连接队列中获取一个连接并交给该服务线程处理。这样可以保证服务器几乎是满载工作的状态,最大限度的提供服务能力。

(三)为什么连接队列的长度不能太长?

  • 连接队列需要占用服务器的内存资源来存储连接请求。连接队列过长会占用更多的空间,可能导致服务器的性能下降。且队列中的连接需要等待较长的时间才能得到服务。这会增加客户端的连接建立和响应延迟,降低了用户体验。
  • 连接队列中的连接请求需要维护其状态信息,包括三次握手、超时处理等。如果队列过长,维护这些状态信息需要消耗更多的 CPU 资源和时间。
  • 较长的连接队列可能会增加服务器面临的潜在风险。例如,如果恶意客户端发送大量的连接请求,将连接队列撑满,可能导致服务器无法响应真正的合法连接请求。这可能导致服务器遭受拒绝服务(Denial of Service,DoS)攻击。

因此,为了避免上述问题的出现,连接队列的长度需要适度。通常,系统会设置一个合理的值,不是一个固定的数值,它取决于多种因素,如系统的性能需求、网络拥塞情况、请求处理时间等,以平衡服务器的资源利用和客户端的响应请求。可以根据实际的场景和需要对连接队列长度进行调整。

(四)全连接队列的长度决定因素

全连接队列的长度由以下两个因素决定:

  • 用户层调用 listen 函数时传入的第二个参数 backlog该参数表示在队列中等待被接受的连接的最大数量。具体的取值范围可能会受到操作系统或网络库的限制。
  • 系统变量 net.core.somaxconn这是一个系统变量,用于限制连接队列的最大长度。它表示操作系统允许的最大连接队列长度。该值在不同的操作系统中可能会有所不同,通常默认值为 128

实际上,连接队列长度不一定等于 backlog net.core.somaxconn 的值,而是取两者中的 较小值+1 作为连接队列实际长度。

可以使用命令 sysctl net.core.somaxconn 来查看其的值:

设置合适的连接队列长度,可以通过调整 backlog 参数和 net.core.somaxconn 变量的值,使其适应服务器的负载和性能要求。

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值