2017-05-28 110 views
0

作为一个学校项目,我必须重新编码一个IRC服务器,但我坚持一个问题。 我想要做的是接收并执行客户端的命令而不会阻塞(因为我有很多客户端需要服务)。C socket:非阻塞方式读取 n分离的命令

编辑:使用非阻塞套接字和叉()是被禁止的这个项目

关于命令:

  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,这对所有的工作客户端,但只执行来自客户端的第一个命令(例如,如果客户端发送“命令1 \ r \ ncommand2 \ r \ n”个,仅执行命令1)

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有什么优势?

+1

如何使用*非阻塞*套接字和使用例如轮询'select'? –

+1

您的客户机/服务器I/O **是否在低级读写操作(即:对您的项目的要求)中具有非阻塞性,或者您是否必须能够处理多个并发客户机以快速,无序的方式从服务器连接? – DevNull

+0

@Someprogrammerdude我已经在使用select但是非阻塞套接字不允许用于这个项目:/ –

回答

0

既然您使用了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_wait目的,和received_gen是为了避免在没有新消息进入时繁忙循环。

假设你使用你的main thr EAD来处理每个传入的消息,它将有一个循环,用的循环是这样的身体:

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作为生成计数器:如果没有工作要做,我们检查生成计数器是否已更改。如果有的话,可能还有更多的工作要做,所以我们继续进行主循环的下一次迭代。否则,请注意我们仍然保持互斥体,我们等待条件变量的信号。

+0

这真的很酷,我会问是否允许使用线程,以及是否允许是这样的,实现这个;) –