【问题标题】:How to handle asynchronous send and receive in TCP listener如何在 TCP 监听器中处理异步发送和接收
【发布时间】:2019-11-22 22:16:06
【问题描述】:

我是套接字编程的新手。我正在尝试制作一个可以处理多个连接的 TCP 侦听器。

我找到了this 示例,这似乎非常有用。

此代码的问题在于它仅在收到数据时才向连接的客户端发送数据。我想异步向连接的客户端发送数据。

我看到select() 函数永远阻塞代码,直到事件到达套接字。

我想在select() 函数中设置一个延迟(而不是NULL),这样它每隔几微秒就会超时,如果有数据,程序就可以发送数据。在线if (buffer[0] > 0)

问题是:有没有更好的方法来做我想做的事?我可以通过其他方式强制select() 超时吗?

char buffer[1025]; 是一个从另一个线程填充的全局数组。下面的while(TRUE) 正在另一个线程上运行。

while(TRUE)   
    {   
        //clear the socket set  
        FD_ZERO(&readfds);   

    //add master socket to set  
    FD_SET(master_socket, &readfds);   
    max_sd = master_socket;   

    //add child sockets to set  
    for ( i = 0 ; i < max_clients ; i++)   
    {   
        //socket descriptor  
        sd = client_socket[i];   

        //if valid socket descriptor then add to read list  
        if(sd > 0)   
            FD_SET( sd , &readfds);   

        //highest file descriptor number, need it for the select function  
        if(sd > max_sd)   
            max_sd = sd;   
    }   

    //wait for an activity on one of the sockets , timeout is NULL ,  
    //so wait indefinitely  
    struct timeval timeout;
    timeout.tv_usec = 10000;
    activity = select( max_sd + 1 , &readfds , NULL , NULL , &timeout);   

    if ((activity < 0) && (errno!=EINTR))   
    {   
        cout << "select error" << endl;
    }   

    //If something happened on the master socket ,  
    //then its an incoming connection  
    if (FD_ISSET(master_socket, &readfds))   
    {   
        if ((new_socket = accept(master_socket,  
                (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)   
        {   
            perror("accept");   
            exit(EXIT_FAILURE);   
        }   

        //inform user of socket number - used in send and receive commands  
        printf("New connection , socket fd is %d , ip is : %s , port : %d  \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));   

        //send new connection greeting message  
        if( send(new_socket, message, strlen(message), 0) != strlen(message) )   
        {   
            perror("send");   
        }   

        puts("Welcome message sent successfully");   

        //add new socket to array of sockets  
        for (i = 0; i < max_clients; i++)   
        {   
            //if position is empty  
            if( client_socket[i] == 0 )   
            {   
                client_socket[i] = new_socket;   
                printf("Adding to list of sockets as %d\n" , i);   

                break;   
            }   
        }   
    }   

    if (buffer[0] > 0)
    {
        sd = client_socket[0];    
        send(sd , buffer , strlen(buffer) , 0 );        
    }  

    //else its some IO operation on some other socket 
    for (i = 0; i < max_clients; i++)   
    {   
        sd = client_socket[i];  


        if (FD_ISSET( sd , &readfds))   
        {   
            //Check if it was for closing , and also read the  
            //incoming message  
            if ((valread = read( sd , buffer, 1024)) == 0)   
            {   
                //Somebody disconnected , get his details and print  
                getpeername(sd , (struct sockaddr*)&address , \ 
                    (socklen_t*)&addrlen);   
                printf("Host disconnected , ip %s , port %d \n" ,  
                      inet_ntoa(address.sin_addr) , ntohs(address.sin_port));   

                //Close the socket and mark as 0 in list for reuse  
                close( sd );   
                client_socket[i] = 0;   
            }   

            //Echo back the message that came in  
            else 
            {   
                //set the string terminating NULL byte on the end  
                //of the data read  
                buffer[valread] = '\0';   
                send(sd , buffer , strlen(buffer) , 0 );                     
            }   
        }   
    }   
}

【问题讨论】:

  • 小心timeout的初始化,你只初始化tv_usec字段,而不是tv_sec字段,它会有一个indeterminate(并且看似随机/垃圾)价值。
  • 许多需要处理其他事件的单线程服务器的工作方式与此非常相似。曾经有一种古老的游戏类型叫做MUD,它的服务器大多是单线程的,并且有许多看似异步发生的事件,他们使用超时来实现select,就像你一样。今天可能会使用线程进行并行和异步操作。
  • @Someprogrammerdude 你的意思是为每个客户端使用一个线程?
  • 这是一种解决方案,或者一个线程用于所有通信(使用 select 和空超时),另一个用于其他事件。
  • 我不确定我是否完全理解。我将我在 geeksforgeeks 上找到的示例中的所有 main() 函数放入一个线程中。我在上面发布的代码是我在该示例中所做的更改。我怎么能打破另一个线程的选择?我只想能够在我必须发送一些数据时向客户端发送数据。但是如果没有从套接字接收到数据,我不能导致select 永远阻塞(设置了NULL 超时)

标签: c++ c sockets tcp


【解决方案1】:

此代码的问题在于它仅在收到数据时才向连接的客户端发送数据。

那是因为原始代码实现了一个请求/响应服务器——在这种情况下,它专门是一个回显服务器。

我想向连接的客户端异步发送数据。

您可能希望首先枚举服务器开始异步发送数据的条件/触发器。我即兴编造的一个例子是多回声服务器。一旦这样的服务器从客户端接收到一个字符串,它就会回显它 3 次,每个回显间隔(比如说)1 秒。

在这种情况下,可以使用选择超时来中断等待,并且可以修改“if buffer[0] > 0”检查以发送任何待处理的回显。

问题是:有没有更好的方法来做我想做的事?我可以通过其他方式强制 select() 超时吗?

man-page 表示有第三种方法可以打破选择,即安排要传递的信号。但是,我认为,这应该用于处理传递给服务器程序的信号,并且仅用于处理异步 IO 的选择超时。更准确地说,我已经看到生产级服务器在其异步循环中使用 epoll/kqueue(更好的选择)超时。所以,你的方法似乎很合理。

【讨论】:

    【解决方案2】:

    单线程服务器在调用select 或poll 时有一个大循环以拥有一个“计时器”列表——必须在特定时间完成的事情——这是很常见的。当代码不需要执行任何操作时,您可以检查在计时器列表指示需要执行某些操作之前还有多长时间,然后您调用 select 或 poll 指定该时间长度作为超时。

    所以你的循环看起来像这样:

    1. 检查计时器列表并计算需要多长时间才能执行某项操作。
    2. 调用select 或poll 指定超时时间。
    3. 如果我们发现任何套接字已准备好进行操作,请执行这些操作。
    4. 如果需要执行任何计时器,请执行它们。
    5. 转到步骤 1。

    如果您需要定期执行某项操作,请设置一个初始计时器以在第一次执行该操作时执行该操作。然后让计时器的执行代码设置一个新的计时器来再次执行此操作。

    【讨论】:

    • 将send 函数放在另一个线程中怎么样?我试过了,似乎工作正常!一个线程从连接的客户端监听,select 永远阻塞,而另一个线程只在需要发送数据时执行send。
    • @MrBit 你当然可以这样做。使用多个线程使架构更简单。缺点是您必须了解如何同步线程并避免竞争条件。但是一旦你学会了如何做到这一点,多线程绝对是要走的路。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-08
    • 2013-05-22
    • 2014-03-27
    • 2013-07-19
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    相关资源
    最近更新 更多