gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 16:45:06 25 4
gpt4 key购买 nike

作为一个学校项目,我必须重新编码 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”,则仅命令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 有何优点?

最佳答案

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

关于C socket : Non blocking way to read\n separated commands,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44228440/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com