如何在 TCP 侦听器中处理异步发送和接收

How to handle asynchronous send and receive in TCP listener

本文关键字:异步 处理 TCP 侦听器      更新时间:2023-10-16

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

我发现这个例子似乎很有用。

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

我看到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 %dn" , 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] = '';   
send(sd , buffer , strlen(buffer) , 0 );                     
}   
}   
}   
}

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

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

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

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

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

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

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

对于单线程服务器来说,围绕对selectpoll的调用有一个"计时器"列表是很常见的 - 必须在特定时间完成的事情。当代码不需要执行任何操作时,请检查计时器列表指示需要执行某些操作的时间,并调用selectpoll将该时间长度指定为超时。

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

  1. 检查计时器列表并计算我们需要做某事的时间。
  2. 调用selectpoll将其指定为超时。
  3. 如果我们发现任何套接字已准备好进行操作,请执行这些操作。
  4. 如果需要执行任何计时器,请执行它们。
  5. 转到步骤 1。

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