[英]C socket : Non blocking way to read \n separated commands
作为学校项目,我必须重新编码IRC服务器,但遇到了问题。 我想做的是在不阻塞的情况下接收并执行客户端的命令(因为我有很多客户端要服务)。
编辑:此项目禁止使用非阻塞套接字和fork()
关于命令:
我的第一个尝试是使用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_next
和incoming_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_lock
, received_wait
和received_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.