2017-05-28 36 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

這真的很酷,我會問是否允許使用線程,以及是否允許是這樣的,實現這個;) –