Zacznijmy od tego, że polecenie w zadaniu jest miejscami, delikatnie mówiąc, niemądre: "Niech proces serwer tworzy kolejkę komunikatów, a po zakończeniu działania wszystkich klientów niech ją usuwa". Skąd mamy wiedzieć, że wszyscy klienci się odłączyli? Interfejs nam tego nie umożliwia. Mamy implementować specjalny protokół nad msg z notyfikacjami o podłączaniu/odłączaniu klienta? Ktoś zrobił chyba copy-paste z zadania z TCP.
Opakowywanie msgctl, msgget, msgsnd i msgrcv jest nonsensem moim zdaniem. Po co mamy sobie zduplikować funkcjonalność err(3)? Dodatkowo, pokomplikuje nam to kod, bo rozpoznajemy sytuację errno==EINTR.
Odnośnie reszty, to moim zdaniem takie rozwiązanie jest wystarczająco poprawne:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Można też użyć ftok. */
#define KEY 0xDEADC0DE
#define MAX_LEN 256
#define ROLE_CLIENT 0x00
#define ROLE_SERVER 0x01
/*
* Struktura używana przez nas do komunikacji.
*/
struct comm_msg {
long mtype;
struct msgbody{
pid_t sender;
char txt[MAX_LEN];
} body;
};
/*
* Pusta procedura na potrzeby obsługi SIGINT.
*/
static void
handle_sigint()
{
}
static void
run_client(const int msgid)
{
struct comm_msg msg;
printf("Enter message: ");
while (fgets((char *)&msg.body.txt, sizeof(msg.body.txt), stdin) != NULL) {
msg.mtype = ROLE_SERVER;
msg.body.sender = getpid();
if (msgsnd(msgid, &msg, sizeof(msg.body), 0) == -1)
err(1, "msgrcv(2) failed");
memset((void *)&msg.body.txt, '\0', sizeof(msg.body.txt));
/* Odbieramy komunikaty wyłącznie adresowane do nas. */
if (msgrcv(msgid, &msg, sizeof(msg.body), getpid(), 0) == -1)
err(1, "msgrcv(2) failed");
printf("Got: %sEnter message: ", msg.body.txt);
}
fprintf(stderr, "Client is exiting.\n");
}
static void
run_server(const int msgid)
{
int i;
struct comm_msg msg;
for (;;) {
if (msgrcv(msgid, &msg, sizeof(msg.body), ROLE_SERVER, 0) == -1) {
if (errno != EINTR)
err(1, "msgrcv(2) failed");
else
break;
}
i = 0;
while (i < MAX_LEN && msg.body.txt[i] != '\0') {
msg.body.txt[i] = toupper(msg.body.txt[i]);
i++;
}
msg.mtype = msg.body.sender;
msg.body.sender = ROLE_SERVER;
/* XXX: tutaj też można dostać EINTR. */
if (msgsnd(msgid, &msg, sizeof(msg.body), 0) == -1)
err(1, "msgsnd(2) failed");
}
fprintf(stderr, "Server is exiting.\n");
if (msgctl(msgid, IPC_RMID, NULL) == -1)
err(1, "msgctl(2) failed");
}
int
main(int argc, char **argv)
{
int msgid, role;
msgid = msgget(KEY, 0600 | IPC_CREAT);
if (msgid == -1)
err(1, "msgget(2) failed");
role = ROLE_SERVER;
if (argc > 1) {
if (strcmp(argv[1], "client") == 0) {
role = ROLE_CLIENT;
} else {
fprintf(stderr, "Say what?\n");
exit(1);
}
}
/*
* W sumie to oba procesy powinny mieć ustawiony ten handler. Server
* zareaguje za pomocą EINTR zwróconego z msgrcv. Klient powinien
* reagować na Ctrl+D.
*/
if (signal(SIGINT, handle_sigint) == SIG_ERR)
err(1, "signal(3) failed");
if (role == ROLE_SERVER)
run_server(msgid);
else
run_client(msgid);
return (0);
}