Kolejki wiadomości

Po co nam kolejki wiadomości, skoro mamy już pamięć współdzieloną? Byłoby to z wielu powodów, spróbujmy podzielić to na kilka punktów dla uproszczenia -

  • Jak rozumiem, po odebraniu wiadomości przez proces nie będzie ona już dostępna dla żadnego innego procesu. Podczas gdy w pamięci współdzielonej dane są dostępne dla wielu procesów.

  • Jeśli chcemy komunikować się za pomocą małych formatów wiadomości.

  • Dane w pamięci współdzielonej muszą być chronione przez synchronizację, gdy wiele procesów komunikuje się w tym samym czasie.

  • Częstotliwość zapisywania i odczytywania danych przy użyciu pamięci współdzielonej jest wysoka, wówczas wdrożenie tej funkcjonalności byłoby bardzo skomplikowane. Nie warto ze względu na wykorzystanie w tego typu przypadkach.

  • Co by było, gdyby wszystkie procesy nie miały dostępu do pamięci współdzielonej, ale bardzo niewiele procesów tego potrzebowało, lepiej byłoby wdrożyć je z kolejkami komunikatów.

  • Jeśli chcemy komunikować się z różnymi pakietami danych, powiedzmy, że proces A wysyła komunikat typu 1 do procesu B, komunikat typu 10 do procesu C i komunikat typu 20 do przetwarzania D. W tym przypadku łatwiej jest zaimplementować z kolejkami komunikatów. Aby uprościć podany typ wiadomości jako 1, 10, 20, może to być 0 lub + ve lub –ve, jak omówiono poniżej.

  • Oczywiście kolejność w kolejce komunikatów to FIFO (First In First Out). Pierwsza wiadomość wstawiona do kolejki jest pierwszą odebraną.

Korzystanie z pamięci współdzielonej lub kolejek wiadomości zależy od potrzeb aplikacji i tego, jak efektywnie można ją wykorzystać.

Komunikacja przy użyciu kolejek wiadomości może odbywać się na następujące sposoby -

  • Zapisywanie do pamięci współdzielonej przez jeden proces i czytanie z pamięci współdzielonej przez inny proces. Jak wiemy, czytanie może odbywać się również za pomocą wielu procesów.

  • Zapis do pamięci współdzielonej przez jeden proces z różnymi pakietami danych i odczyt z niej przez wiele procesów, tj. Zgodnie z typem wiadomości.

Po zapoznaniu się z pewnymi informacjami na temat kolejek komunikatów nadszedł czas, aby sprawdzić wywołanie systemowe (System V), które obsługuje kolejki komunikatów.

Aby wykonać komunikację przy użyciu kolejek wiadomości, wykonaj następujące czynności:

Step 1 - Utwórz kolejkę wiadomości lub połącz się z już istniejącą kolejką wiadomości (msgget ())

Step 2 - Zapisz do kolejki wiadomości (msgsnd ())

Step 3 - Odczyt z kolejki wiadomości (msgrcv ())

Step 4 - Wykonaj operacje kontrolne na kolejce komunikatów (msgctl ())

Sprawdźmy teraz składnię i pewne informacje na temat powyższych wywołań.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg)

To wywołanie systemowe tworzy lub przydziela kolejkę komunikatów System V. Należy przekazać następujące argumenty -

  • Pierwszy argument, klucz, rozpoznaje kolejkę komunikatów. Klucz może być dowolną wartością lub taką, która może pochodzić z funkcji bibliotecznej ftok ().

  • Drugi argument, shmflg, określa wymagane flagi kolejki komunikatów, takie jak IPC_CREAT (tworzenie kolejki komunikatów, jeśli nie istnieje) lub IPC_EXCL (używany z IPC_CREAT do tworzenia kolejki komunikatów i wywołanie kończy się niepowodzeniem, jeśli kolejka komunikatów już istnieje). Musisz również przekazać uprawnienia.

Note - Zapoznaj się z wcześniejszymi sekcjami, aby uzyskać szczegółowe informacje na temat uprawnień.

To wywołanie zwróci prawidłowy identyfikator kolejki komunikatów (używany do dalszych wywołań kolejki komunikatów) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().

Różne błędy związane z tym wywołaniem to EACCESS (odmowa uprawnień), EEXIST (kolejka już istnieje, nie można utworzyć), ENOENT (kolejka nie istnieje), ENOMEM (za mało pamięci do utworzenia kolejki) itp.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)

To wywołanie systemowe wysyła / dołącza komunikat do kolejki komunikatów (System V). Należy przekazać następujące argumenty -

  • Pierwszy argument, msgid, rozpoznaje kolejkę komunikatów, tj. Identyfikator kolejki komunikatów. Wartość identyfikatora jest otrzymywana po pomyślnym wykonaniu msgget ()

  • Drugi argument, msgp, jest wskaźnikiem do wiadomości wysyłanej do dzwoniącego, zdefiniowanym w strukturze następującej postaci -

struct msgbuf {
   long mtype;
   char mtext[1];
};

Zmienna mtype jest używana do komunikacji z różnymi typami wiadomości, szczegółowo wyjaśnionymi w wywołaniu msgrcv (). Zmienna mtext jest tablicą lub inną strukturą, której rozmiar jest określony przez msgsz (wartość dodatnia). Jeśli pole mtext nie jest wymienione, jest to traktowane jako komunikat o rozmiarze zerowym, co jest dozwolone.

  • Trzeci argument, msgsz, to rozmiar wiadomości (wiadomość powinna kończyć się znakiem null)

  • Czwarty argument, msgflg, wskazuje pewne flagi, takie jak IPC_NOWAIT (zwraca natychmiast, gdy w kolejce nie zostanie znaleziona żadna wiadomość lub MSG_NOERROR (obcina tekst wiadomości, jeśli jest więcej niż msgsz bajtów)

To wywołanie zwróci 0 w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)

To wywołanie systemowe pobiera komunikat z kolejki komunikatów (System V). Należy przekazać następujące argumenty -

  • Pierwszy argument, msgid, rozpoznaje kolejkę komunikatów, tj. Identyfikator kolejki komunikatów. Wartość identyfikatora jest otrzymywana po pomyślnym wykonaniu msgget ()

  • Drugi argument, msgp, jest wskaźnikiem wiadomości otrzymanej od dzwoniącego. Jest zdefiniowany w strukturze następującej postaci -

struct msgbuf {
   long mtype;
   char mtext[1];
};

Zmienna mtype służy do komunikacji z różnymi typami wiadomości. Zmienna mtext jest tablicą lub inną strukturą, której rozmiar jest określony przez msgsz (wartość dodatnia). Jeśli pole mtext nie jest wymienione, jest to traktowane jako komunikat o rozmiarze zerowym, co jest dozwolone.

  • Trzeci argument, msgsz, to rozmiar otrzymanej wiadomości (wiadomość powinna kończyć się znakiem null)

  • Czwarty argument, msgtype, wskazuje typ wiadomości -

    • If msgtype is 0 - Odczytuje pierwszą otrzymaną wiadomość w kolejce

    • If msgtype is +ve - Odczytuje pierwszą wiadomość w kolejce typu msgtype (jeśli msgtype ma wartość 10, to odczytuje tylko pierwszą wiadomość typu 10, mimo że inne typy mogą znajdować się w kolejce na początku)

    • If msgtype is –ve - Odczytuje pierwszą wiadomość najniższego typu mniejszą lub równą bezwzględnej wartości typu wiadomości (powiedzmy, jeśli msgtype wynosi -5, to czyta pierwszą wiadomość typu mniejszego niż 5, tj. Typ wiadomości od 1 do 5)

  • Piąty argument, msgflg, wskazuje pewne flagi, takie jak IPC_NOWAIT (zwraca natychmiast, gdy w kolejce nie zostanie znaleziona żadna wiadomość lub MSG_NOERROR (obcina tekst wiadomości, jeśli jest więcej niż msgsz bajtów)

To wywołanie zwróci liczbę faktycznie otrzymanych bajtów w tablicy mtext w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msqid_ds *buf)

To wywołanie systemowe wykonuje operacje sterujące kolejką komunikatów (System V). Należy przekazać następujące argumenty -

  • Pierwszy argument, msgid, rozpoznaje kolejkę komunikatów, tj. Identyfikator kolejki komunikatów. Wartość identyfikatora jest otrzymywana po pomyślnym wykonaniu msgget ()

  • Drugi argument, cmd, jest poleceniem wykonującym wymaganą operację sterującą w kolejce komunikatów. Prawidłowe wartości cmd to -

IPC_STAT- Kopiuje informacje o bieżących wartościach każdego elementu struktury msqid_ds do przekazanej struktury wskazywanej przez buf. To polecenie wymaga uprawnień do odczytu w kolejce komunikatów.

IPC_SET - Ustawia identyfikator użytkownika, identyfikator grupy właściciela, uprawnienia itp. Wskazywane przez strukturę buf.

IPC_RMID - Natychmiast usuwa kolejkę komunikatów.

IPC_INFO - Zwraca informacje o limitach i parametrach kolejki wiadomości w strukturze wskazywanej przez buf, która jest typu struct msginfo

MSG_INFO - Zwraca strukturę msginfo zawierającą informacje o zużytych zasobach systemowych przez kolejkę komunikatów.

  • Trzeci argument, buf, jest wskaźnikiem do struktury kolejki komunikatów o nazwie struct msqid_ds. Wartości tej struktury będą używane do ustawiania lub pobierania zgodnie z cmd.

To wywołanie zwróci wartość w zależności od przekazanego polecenia. Sukces IPC_INFO i MSG_INFO lub MSG_STAT zwraca indeks lub identyfikator kolejki komunikatów lub 0 dla innych operacji i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().

Po zapoznaniu się z podstawowymi informacjami i wywołaniami systemowymi dotyczącymi kolejek komunikatów, przyszedł czas na sprawdzenie w programie.

Zobaczmy opis, zanim spojrzymy na program -

Step 1 - Utwórz dwa procesy, jeden służy do wysyłania do kolejki komunikatów (msgq_send.c), a drugi do pobierania z kolejki komunikatów (msgq_recv.c)

Step 2- Tworzenie klucza za pomocą funkcji ftok (). W tym celu początkowo tworzony jest plik msgq.txt, aby uzyskać unikalny klucz.

Step 3 - Proces wysyłania wykonuje następujące czynności.

  • Odczytuje ciąg znaków wejściowych od użytkownika

  • Usuwa nową linię, jeśli istnieje

  • Wysyła do kolejki wiadomości

  • Powtarza proces do końca wprowadzenia (CTRL + D)

  • Po odebraniu końca danych wejściowych wysyła komunikat „koniec” oznaczający koniec procesu

Step 4 - W trakcie odbierania wykonuje następujące czynności.

  • Odczytuje wiadomość z kolejki
  • Wyświetla dane wyjściowe
  • Jeśli otrzymana wiadomość to „koniec”, kończy proces i wychodzi

Aby uprościć, w tym przykładzie nie używamy typu wiadomości. Ponadto jeden proces zapisuje w kolejce, a inny czyta z kolejki. Można to w razie potrzeby rozszerzyć, tj. Idealnie byłoby, gdyby jeden proces zapisywał do kolejki, a wiele procesów odczytywało z kolejki.

Teraz sprawdźmy proces (wysyłanie wiadomości do kolejki) - Plik: msgq_send.c

/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int len;
   key_t key;
   system("touch msgq.txt");
   
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   
   if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to send messages.\n");
   printf("Enter lines of text, ^D to quit:\n");
   buf.mtype = 1; /* we don't really care in this case */
   
   while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
      len = strlen(buf.mtext);
      /* remove newline at end, if it exists */
      if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
      if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
      perror("msgsnd");
   }
   strcpy(buf.mtext, "end");
   len = strlen(buf.mtext);
   if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
   perror("msgsnd");
   
   if (msgctl(msqid, IPC_RMID, NULL) == -1) {
      perror("msgctl");
      exit(1);
   }
   printf("message queue: done sending messages.\n");
   return 0;
}

Kroki kompilacji i wykonywania

message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.

Poniżej znajduje się kod z procesu odbierania wiadomości (pobierania wiadomości z kolejki) - Plik: msgq_recv.c

/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define PERMS 0644
struct my_msgbuf {
   long mtype;
   char mtext[200];
};

int main(void) {
   struct my_msgbuf buf;
   int msqid;
   int toend;
   key_t key;
   
   if ((key = ftok("msgq.txt", 'B')) == -1) {
      perror("ftok");
      exit(1);
   }
   
   if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
      perror("msgget");
      exit(1);
   }
   printf("message queue: ready to receive messages.\n");
   
   for(;;) { /* normally receiving never ends but just to make conclusion 
             /* this program ends wuth string of end */
      if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
         perror("msgrcv");
         exit(1);
      }
      printf("recvd: \"%s\"\n", buf.mtext);
      toend = strcmp(buf.mtext,"end");
      if (toend == 0)
      break;
   }
   printf("message queue: done receiving messages.\n");
   system("rm msgq.txt");
   return 0;
}

Kroki kompilacji i wykonywania

message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.