繁体   English   中英

C socket:以非阻塞方式读取\\ n分隔的命令

[英]C socket : Non blocking way to read \n separated commands

作为学校项目,我必须重新编码IRC服务器,但遇到了问题。 我想做的是在不阻塞的情况下接收并执行客户端的命令(因为我有很多客户端要服务)。

编辑:此项目禁止使用非阻塞套接字和fork()

关于命令:

  1. 它们被“ \\ r \\ n”分隔
  2. 最多512个字符

我的第一个尝试是使用getline循环。 它完美地工作,但仅对一个客户端有效(作为getline块,当他们不希望读取更多内容而不是传递给下一个客户端时,该热线阻止)

bool     recv_cmd(t_hdl *hdl)                                               
{                                                                                             
  char          *raw;                                                                         
  size_t        len;                                                                          
  FILE          *input_stream;                                                                
  ssize_t       nread;                                                                        

  len = 0;                                                                                    
  raw = NULL;                                                                                 
  if ((input_stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)                             
    return (false);                                                                           
  while ((nread = getline(&raw, &len, input_stream)) > 0)                                     
    {                                                                                         
      printf("%lu\n", nread);                                                                 
      parse_cmd(hdl, raw);                                                                    
      exec_cmd(hdl);                                                                                                                                                 
    }                                                                                         
  fclose(input_stream);                                                                       
  return (true);                                                                              
} 

如果像这样从循环中删除getline,它适用于所有客户端,但仅执行来自客户端的第一个命令(例如,如果客户端发送“ command1 \\ r \\ ncommand2 \\ r \\ n”,则仅执行command1 )

bool     recv_cmd(t_hdl *hdl)                                               
{                                                                                             
  char          *raw;                                                                         
  size_t        len;                                                                          
  FILE          *input_stream;                                                                

  len = 0;                                                                                    
  raw = NULL;                                                                                 
  if ((input_stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)                             
    return (false);                                                                           
  if (getline(&raw, &len, input_stream) != -1)                                                
    {                                                                                         
      parse_cmd(hdl, raw);                                                                    
      exec_cmd(hdl);                                                                          
      //free(raw                                                                              
    }                                                                                         
  fclose(input_stream);                                                                       
  return (true);                                                                              
}          

我还尝试删除了fclose(),以便在读取command1时,command2保留在流缓冲区中,但它也不起作用。

该项目的主题还说:“ 使用循环缓冲区以保护和优化正在发送和接收的各种命令和响应。

我该怎么办? 在这种情况下,在我的getline上使用循环缓冲区有什么优势?

因为您使用了getline() ,所以我假设您依赖POSIX.1功能; 在这种情况下,我建议使用专用线程从所有连接的客户端接收消息。

我不只是从每个客户端动态缓冲区中读取其他数据,而是将传入消息放入链中:

#define MAX_INCOMING_LEN  512

struct incoming_message {
    struct incoming_message  *next;
    size_t                    len;
    char                      data[MAX_INCOMING_LEN];
}

客户端结构需要一个至少MAX_INCOMING_LEN字符的临时缓冲区(因为不能保证来自流套接字的recv()read()提供完整的消息,或仅一条消息)。 如果一个单独的线程正在读取消息,那么您还需要锁定以保护消息链免受并发访问:

struct client {
    int                      socketfd;
    char                     received[MAX_INCOMING_LEN];
    size_t                   received_len;

    pthread_mutex_t          incoming_lock;
    struct incoming_message *incoming_next;
    struct incoming_message *incoming_last;
};

接收新消息的函数将它们以伪代码的形式添加到列表中:

Construct and fill in struct incoming_message *msg
Lock incoming_lock mutex
Set msg->next = NULL
If incoming_last != NULL:
    Set incoming_last->next = msg
    Set incoming_last = msg
Else
    Set incoming_next = msg
    Set incoming_last = msg
End If
Unlock incoming_lock mutex

使用两个指针, incoming_nextincoming_last ,意味着我们在追加列表时无需浏览列表。 给定客户端c的伪代码,用于捕获下一个传入消息的函数类似于

Function next_message(struct client *c)
{
    Lock c->incoming_lock mutex
    If c->incoming_next != NULL:
        struct incoming_message *msg = c->incoming_next;
        If msg->next != NULL
            Set incoming_next = msg->next
            Set msg->next = NULL
        Else:
            Set incoming_next = NULL
            Set incoming_last = NULL
        End If
        Unlock c->incoming_lock mutex
        Return msg
    Else:
        Unlock c->incoming_lock mutex
        Return NULL
    End If
}

请注意,对于传出消息,我将使用完全不同的结构,因为您通常会将完全相同的消息发送给许多客户端。 至少有两种完全不同的方法,但是OP并没有提出这些要求,因此我将不去赘述。

传入的数据工作程序或套接字读取器线程是唯一一个与每个客户端的received[]缓冲区接触的线程,因此不需要任何锁定。

假设您具有以下全局变量:

static pthread_mutex_t   received_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t    received_more = PTHREAD_COND_INITIALIZER;
static long              received_gen  = 0L;

在伪代码中,套接字读取器线程在循环中完成以下工作:

Use select() or poll() to find out which clients' sockets have unread data
Lock received_lock mutex
Set have_received = 0
For each client whose socket has unread data:
    Try receiving as much as is free in received[] buffer
    If new data received:
        Increment received_len by the received amount
        Increment have_received by 1
        If a separator exists in received[0..received_len-1]:
            Let N be the offset of the character following the separator
            Grab or allocate a new incoming_message structure
            Copy the first N chars of received[] to the new structure
            Lock the incoming_lock mutex
            Prepend the structure to the singly-linked list
            Unlock the incoming_lock mutex
            If N < received_len:
                memmove(received, received + N, received_len - N)
                received_len -= N
            Else:
                received_len = 0
            End If
        End If
    End If
End If
If have_received > 0:
    Increment received_gen by 1
    Signal on received_more condition variable
End If
Unlock received_lock mutex

received_lockreceived_waitreceived_gen的目的是在没有新消息进入时避免繁忙循环。

假设您使用主线程来处理每条传入的消息,它将有一个循环,循环主体如下所示:

Lock received_lock mutex
before_gen = received_gen
Unlock received_lock mutex

Set msg_count = 0
For each client:
    Lock client->incoming_lock
    If the list is not empty:
        Increment msg_count by 1
        Grab the last message in the list
        Unlock client->incoming_lock

        Process the message

    Else:
        Unlock client->incoming_lock
    End If
End For

If msg_count == 0:
    Lock received_lock mutex
    after_gen = received_gen
    If after_gen == before_gen:
        pthread_cond_wait(received_more, received_lock)
    End if
    Unlock received_lock mutex
End If

我们不希望持有received_lock任何时间长度,因为阻止接收新邮件。 相反,我们将received_gen用作世代计数器:如果没有工作要做,我们检查世代计数器是否已更改。 如果有的话,可能还有更多工作要做,所以我们继续进行主循环的下一个迭代。 否则,请注意我们仍然保持互斥锁,我们等待条件变量上的信号。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM