gpt4 book ai didi

c - 在两个套接字之间共享信息

转载 作者:行者123 更新时间:2023-11-30 15:57:48 25 4
gpt4 key购买 nike

我正在尝试实现一个基于 UDP 的服务器,它维护两个套接字,一个用于控制(ctrl_sock),另一个用于数据传输(data_sock)。问题是,ctrl_sock 始终是上行链路,data_sock 始终是下行链路。也就是说,客户端将通过 ctrl_sock 请求数据传输/停止,并且数据将通过 data_sock 发送给它们。

现在的问题是,由于该模型是无连接的,服务器必须维护一个注册客户端信息列表(我称之为peers_context),以便它可以“盲目”推送向他们提供数据,直到他们要求停止。在这种盲传过程中,客户端可以通过ctrl_sock异步向服务器发送控制消息。这些信息除了初始请求和停止之外,还可以是例如文件部分的首选项。因此,peers_context 必须异步更新。然而,通过 data_sock 进行的传输依赖于这个 peers_context 结构,因此引发了 ctrl_sockdata_sock 之间的同步问题。我的问题是,我该如何安全地维护这两个 socks 和peers_context结构,以便异步更新peers_context不会造成严重破坏。顺便说一下,peers_context 的更新不会很频繁,这就是为什么我需要避免请求-回复模型。

我最初的实现考虑是,在主线程(监听线程)中维护ctrl_sock,而在另一个线程(工作线程)中维护data_sock的传输。但是,我发现在这种情况下很难同步。例如,如果我在 peers_context 中使用互斥体,每当工作线程锁定 peers_context 时,监听器线程在需要修改 时将无法再访问它peers_context,因为工作线程无休止地工作。另一方面,如果监听器线程持有 peers_context 并向其写入,则工作线程将无法读取 peers_context 并终止。有人可以给我一些建议吗?

顺便说一下,在Linux环境下用C语言实现的。只有监听线程偶尔需要修改peers_context,工作线程只需要读取。衷心感谢!

最佳答案

如果您的 peers_context 存在强烈争用,那么您需要缩短关键部分。您谈到使用互斥体。我假设您已经考虑更改为读取器+写入器锁并拒绝了它,因为您不希望持续的读取器让写入器挨饿。这个怎么样?

创建一个非常小的结构,它是对 peers_context 的间接引用,如下所示:

struct peers_context_handle {
pthread_mutex_t ref_lock;
struct peers_context *the_actual_context;
pthread_mutex_t write_lock;
};

数据包发送者(读取者)和控制请求处理器(写入者)始终通过此间接访问peers_mutex

假设:数据包发送者永远不会修改 peers_context,也不会释放它。

加壳器发送者短暂锁定句柄,获取当前版本的peers_context并将其解锁:

pthread_mutex_lock(&(handle->ref_lock));
peers_context = handle->the_actual_context;
pthread_mutex_unlock(&(handle->ref_lock));

(实际上,如果引入内存屏障,您甚至可以取消锁,因为指针取消引用在 Linux 支持的所有平台上都是原子的,但我不推荐它,因为您必须开始深入研究内存障碍和其他低级的东西,C 和 POSIX 都不能保证它无论如何都能工作。)

请求处理器不会更新peers_context,它们会复制并完全替换它。这就是他们保持临界区较小的方法。他们确实使用write_lock来序列化更新,但更新并不频繁,所以这不是问题。

pthread_mutex_lock(&(handle->write_lock));

/* Short CS to get the old version */
pthread_mutex_lock(&(handle->ref_lock));
old_peers_context = handle->the_actual_context;
pthread_mutex_unlock(&(handle->ref_lock));

new_peers_context = allocate_new_structure();
*new_peers_context = *old_peers_context;

/* Now make the changes that are requested */
new_peers_context->foo = 42;
new_peers_context->bar = 52;

/* Short CS to replace the context */
pthread_mutex_lock(&(handle->ref_lock));
handle->the_actual_context = new_peers_context;
pthread_mutex_unlock(&(handle->ref_lock));

pthread_mutex_unlock(&(handle->write_lock));

magic(old_peers_context);

有什么问题吗?这就是最后一行代码的魔力。您必须释放 peers_context 的旧副本以避免内存泄漏,但您无法这样做,因为可能有数据包发送者仍在使用该副本。

解决方案类似于RCU ,如 Linux 内核内部使用的那样。您必须等待所有数据包发送线程进入静止状态。我将其实现留给您作为练习:-),但以下是指导原则:

  • magic() 函数添加 old_peers_context 以便释放队列(必须由互斥锁保护)。
  • 一个专用线程在循环中释放此列表:
    1. 它锁定待释放列表
    2. 它获得一个指向列表的指针
    3. 它用新的空列表替换了列表
    4. 它会解锁待释放列表
    5. 它清除与每个工作线程关联的标记
    6. 等待所有标记再次设置
    7. 它释放之前获得的待释放列表副本中的每一项
  • 同时,每个工作线程在其事件循环中的空闲点(即不忙于发送任何数据包或保存任何 peer_contexts 时的点)设置自己的标记。

关于c - 在两个套接字之间共享信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10234027/

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