我正在使用客户端程序实现服务器。通信使用共享内存。为了控制对公共资源的访问,我使用了信号灯。如果客户端是服务器的新手,则服务器会为客户端生成一个ID。客户端将保存其ID,并将在进一步的请求中发送此ID。
我正在使用以下代码在服务器和客户端之间进行通信。此解决方案仅适用于一台服务器和一个客户端。
服务器:
#include <semaphore.h>
sem_t* server = sem_open(SEM_1, O_CREAT | O_EXCL, 0600, 1);
sem_t* client = sem_open(SEM_2, O_CREAT | O_EXCL, 0600, 0);
//other stuff
while (!want_quit)
{
sem_wait(client)
//get id of client from shared memory or generate id for client
//get the command from the client via shared memory
//process command
//write result back to shared memory
sem_post(server)
}
客户:
#include <semaphore.h>
sem_t* s1 = sem_open(SEM_1, 0);
sem_t* s2 = sem_open(SEM_2, 0);
do
{
//wait for the server
sem_wait(s1);
//get result from last command from shared memory
//send new request to server by writing command into shared memory
sem_post(s2);
} while (command from shm != CLOSE);
服务器应比一个客户端更多地工作/管理。我以为我可以通过第三个信号量解决此问题,但是我遇到了死锁问题,或者客户端正在处理其他客户端的结果。
我的第三个信号量的解决方案如下所示:
服务器:
sem_wait(clients);sem_wait(client);sem_post(server);
客户:
sem_wait(s1);sem_post(clients);sem_post(server);
我该如何解决这个挑战?
最佳答案
您对信号量的使用不太正确。信号量旨在保护一个或多个共享资源,通常以信号量的计数代表可用资源的数量。对于共享内存段,您只有一个资源(内存块),因此应使用计数为1的单个信号量来保护它。
该单个信号量可协调所有客户端和服务器。客户端获取信号量,编写其命令,然后释放该信号量。服务器获取该信号量,读取命令并进行所需的任何处理,将结果写回到共享内存中,然后释放该信号量。
但是,正如您所发现的,这并不能协调每个客户端。没有什么可以阻止一个客户端读取另一个客户端的响应。因此,您可以在此处使用另一个信号量,可以将其视为保护服务器的“通信通道”。同样,这是一个资源,因此它的信号量应为1。
因此,您的完整设计将使用2个信号量,如下所示:
从计数为0的共享内存信号量和计数为1的通道信号量开始。
服务器等待共享内存信号量。
一个客户端获取通道信号量,递减为0。这将阻止所有其他客户端。
然后,同一客户端将写入共享内存段,并增加该信号量,从而解除对服务器的阻塞。
客户端和服务器根据需要进行通信,并根据需要获取并释放内存信号量。
客户端完成后,它将释放通道信号,从而解除对另一个客户端与服务器通信的阻塞。
请注意,服务器从不获取通信通道信号。纯粹是为了协调两个或更多的客户。
我还想指出,这种解决方案非常混乱。有很多活动部件,并且有很多可能发生死锁的地方。这是人们将管道,套接字和消息队列之类的东西用于IPC的很大一部分原因。您无需担心锁,因为协调已嵌入到通信通道的设计中(默认情况下是读/写块),并且每个客户端与服务器都有一个单独的通信通道。如果您担心性能,则应查看this SO answer,该信息表明Linux上各种IPC机制的速度都差不多。在其他类型的系统上,您可能会看到不同的结果。