【问题标题】:Data sent from Tcp Client is received very slowlyTcp Client 发送的数据接收很慢
【发布时间】:2020-06-22 03:05:19
【问题描述】:

我要做的系统有一个 tcp 服务器和大约 1000 个 tcp 客户端。 每秒有 1000 个客户端向 tcp 服务器发送数据。 为了模拟这种情况,我首先使用下面的代码从一台 pc 连接到具有 50 个套接字的 tcp 服务器。

int main() {

    const char *hello = "Hello from client";

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);
    serv_addr.sin_addr.s_addr = inet_addr("192.168.1.39");

    vector<int> vec;


    for ( uint8_t i = 0; i < 50; i++ ) {

        int sock =  socket(AF_INET, SOCK_STREAM, 0);
        if ( sock < 0 ) {
            cout << "... Cant Allocated Socket\n";
            return -1;
        }

        if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
            cout << "... Connection Failed \n";
            return -1;
        }



        vec.push_back(sock);
    }

    for ( uint8_t i = 0; i < vec.size(); i++ ) {
        send(vec[i], hello, strlen(hello), 0);
        cout << "Message Send\n";
    }


    for ( uint8_t i = 0; i < vec.size(); i++ ) {
        shutdown(vec[i], 0);
        close(vec[i]);
    }
    return 0;
}

tcp客户端连接到tcp服务器后,将数据发送到tcp服务器并关闭socket。我可以从终端看到 tcp 客户端可以发送数据包而无需等待(小于 10 毫秒)

上面的tcp客户端代码可以正常工作,成功的将数据发送到tcp服务器。 我用下面的 tcp 服务器代码显示来自 tcp 客户端的数据。

#define _DEF_TCP_SERVER_PORT                                        8080
#define _DEF_TCP_SERVER_MAX_QUEUE_LISTEN                            12

bool finish_app = false;

struct TcpClient {
    int clientSocket;
    struct in_addr clientAddr;
};
vector<TcpClient> TcpClients;


struct _ServiceTcpServer {
    bool enable;
    int sock;
    uint16_t connectedClient;
    uint32_t sockLen;
    sockaddr_in tcpServerAddr;
    sockaddr_in remoteAddr;
};

struct _ServiceTcpServer _serviceTcpServer;


void init_tcp_server_socket() {

    _serviceTcpServer.tcpServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    _serviceTcpServer.tcpServerAddr.sin_family = AF_INET;
    _serviceTcpServer.tcpServerAddr.sin_port = htons(_DEF_TCP_SERVER_PORT);
    _serviceTcpServer.sockLen = sizeof(_serviceTcpServer.remoteAddr);

    int flag = 1;

    for ( ;; ) {
        _serviceTcpServer.sock = socket(AF_INET, SOCK_STREAM, 0);
        if ( _serviceTcpServer.sock < 0 ) {
            cout << "... Failed to allocate socket.\n";
            this_thread::sleep_for(chrono::seconds(1));
            continue;
        }

        if ( setsockopt(_serviceTcpServer.sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) ) {
            cout << "... Set SockOpt failed.\n";
            close(_serviceTcpServer.sock);
            this_thread::sleep_for(chrono::seconds(1));
            continue;
        }

        if( bind(_serviceTcpServer.sock, (sockaddr *) &_serviceTcpServer.tcpServerAddr, sizeof(_serviceTcpServer.tcpServerAddr)) == -1 ) {
            cout << "... Socket bind failed.\n";
            close(_serviceTcpServer.sock);
            this_thread::sleep_for(chrono::seconds(1));
            continue;
        }

        if ( listen(_serviceTcpServer.sock, _DEF_TCP_SERVER_MAX_QUEUE_LISTEN) != 0 ) {
            cout << "... Socket listen failed.\n";
            close(_serviceTcpServer.sock);
            this_thread::sleep_for(chrono::seconds(1));
            continue;
        }

        break;

    }


    cout << "Socket init done \n";
}

void tcp_user_accept_task() {

    while ( finish_app == false ) {
        int temp_sck = -1;
        temp_sck = accept(_serviceTcpServer.sock, (sockaddr *) &_serviceTcpServer.remoteAddr, &_serviceTcpServer.sockLen);
        if ( temp_sck == -1 ) {
            this_thread::sleep_for(chrono::seconds(2));
            continue;
        }
        TcpClient tcpClient;
        tcpClient.clientAddr = _serviceTcpServer.remoteAddr.sin_addr;
        tcpClient.clientSocket = temp_sck;
        TcpClients.push_back( tcpClient );
        cout << "... New connection request: " << temp_sck << endl;
        ++_serviceTcpServer.connectedClient;
        this_thread::sleep_for(chrono::milliseconds(50));
    }
}


uint8_t temp_recv[100];
void tcp_server_run() {

    while ( finish_app == false ) {

        for(uint16_t i = 0 ; i < _serviceTcpServer.connectedClient; i++ ) {
            int temp_cs = TcpClients[i].clientSocket;
            fcntl(temp_cs, F_SETFL, O_NONBLOCK);
            int temp_recvLen  = recv(temp_cs, temp_recv, 20, 0);
            if( temp_recvLen > 0 ) {
                time_t _time = chrono::system_clock::to_time_t(chrono::system_clock::now());
                cout << "Message Received At:" << ctime(&_time) << "   :";
                cout << temp_recv << endl;
                break;
            } else {
                this_thread::sleep_for(chrono::milliseconds(10));
            }
        }

        if ( temp_recv[0] == 'q' ) {
            finish_app = true;
        }
    }

    close(_serviceTcpServer.sock);
}




int main() {

    thread init_thread(init_tcp_server_socket);
    init_thread.join();
    thread accept_thread(tcp_user_accept_task);
    thread run_thread(tcp_server_run);
    accept_thread.join();
    run_thread.join();

    return 0;
}

但问题是在 1 秒内收到大约 3-4 个数据包,如屏幕图像所示。

注意: 当代码this_thread::sleep_for(chrono::milliseconds(10)); 评论时,问题就解决了。但是由于处理器没有休眠,所以处理器工作在 100%。 当客户端被接受时,我使用下面的代码和注释和fcntl(temp_cs, F_SETFL, O_NONBLOCK);

向客户端recv 添加了 10 us 超时
struct timeval _timeval;
_timeval.tv_sec = 0;
_timeval.tv_usec = 10;
setsockopt(tcpClient.clientSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &_timeval, sizeof(_timeval));

问题继续出现在“this_thread::sleep_for”中。

【问题讨论】:

  • 不确定是否相关,但我可以看到这段代码使用了非阻塞套接字和活动循环。为什么不使用简单的阻塞套接字?
  • @SergeBallesta 非阻塞适用于“fcntl(temp_cs, F_SETFL, O_NONBLOCK);”。因为我不应该等待任何客户端发送数据。我需要获取和处理发送数据的客户端。
  • ...如果recv 在给定连接上返回&lt; 0,为什么在tcp_server_run 中调用sleep_for(chrono::milliseconds(10))?为什么要睡觉而不是简单地继续下一个连接?
  • @QweQwe 你明白睡眠不只是阻塞处理器时间;它也会阻塞该线程上的 IO (即处理另一个连接),对吗?通常,您希望 IO 绑定服务器做的最后一件事就是在任何东西上休眠。它在那里工作,您的内核也是如此。这就是您为它们付费的原因。
  • 问题被该更改掩盖。现在您在流结束或连接重置时处于自旋循环中,并且您仍然有几个套接字泄漏。请阅读这里告诉你的内容。不然发帖有什么意义?

标签: c++ linux tcp tcpclient tcpserver


【解决方案1】:

您应该同时接收套接字,而不是查询每个套接字并在每次数据尚未准备好时休眠 10 毫秒。

正确的做法取决于平台

  • posix - 选择

  • linux - poll、epoll、io_submit

  • windows - I/O 完成端口

通常,选择 posix 标准就足以满足您的需求。 如果您想要多平台,您可能还想探索第三方库,例如 libeventlibev,它们已经为您包装了这些平台相关调用。

编码愉快!

【讨论】:

    猜你喜欢
    • 2011-09-13
    • 2017-11-16
    • 1970-01-01
    • 2012-03-10
    • 2012-04-12
    • 2010-11-13
    • 2013-07-04
    • 2019-04-26
    • 1970-01-01
    相关资源
    最近更新 更多