Hàng đợi tin nhắn

Tại sao chúng ta cần hàng đợi tin nhắn khi chúng ta đã có bộ nhớ dùng chung? Sẽ có nhiều lý do, chúng ta hãy cố gắng chia điều này thành nhiều điểm để đơn giản hóa -

  • Như đã hiểu, khi một quá trình nhận được thông báo, nó sẽ không còn khả dụng cho bất kỳ quá trình nào khác. Trong khi trong bộ nhớ dùng chung, dữ liệu có sẵn cho nhiều quá trình truy cập.

  • Nếu chúng ta muốn giao tiếp với các định dạng tin nhắn nhỏ.

  • Dữ liệu bộ nhớ dùng chung cần được bảo vệ bằng đồng bộ hóa khi nhiều tiến trình giao tiếp cùng một lúc.

  • Tần suất ghi và đọc bằng bộ nhớ dùng chung cao, khi đó sẽ rất phức tạp để triển khai chức năng. Không đáng để sử dụng trong những trường hợp này.

  • Điều gì sẽ xảy ra nếu tất cả các tiến trình không cần truy cập bộ nhớ dùng chung nhưng rất ít tiến trình chỉ cần nó, sẽ tốt hơn nếu thực hiện với hàng đợi tin nhắn.

  • Nếu chúng ta muốn giao tiếp với các gói dữ liệu khác nhau, giả sử quy trình A đang gửi thông báo loại 1 đến quy trình B, thông báo loại 10 để xử lý C và thông báo loại 20 để xử lý D. Trong trường hợp này, việc triển khai với hàng đợi thông báo sẽ đơn giản hơn. Để đơn giản hóa loại thông báo đã cho là 1, 10, 20, nó có thể là 0 hoặc + ve hoặc –ve như thảo luận bên dưới.

  • Tất nhiên, thứ tự của hàng đợi tin nhắn là FIFO (First In First Out). Tin nhắn đầu tiên được chèn trong hàng đợi là tin nhắn đầu tiên được truy xuất.

Việc sử dụng Bộ nhớ dùng chung hoặc Hàng đợi tin nhắn tùy thuộc vào nhu cầu của ứng dụng và mức độ hiệu quả của ứng dụng.

Giao tiếp bằng cách sử dụng hàng đợi tin nhắn có thể xảy ra theo những cách sau:

  • Ghi vào bộ nhớ dùng chung bằng một quá trình và đọc từ bộ nhớ dùng chung bằng một quá trình khác. Như chúng ta đã biết, việc đọc cũng có thể được thực hiện với nhiều quá trình.

  • Ghi vào bộ nhớ dùng chung bởi một quá trình với các gói dữ liệu khác nhau và đọc từ đó bằng nhiều quá trình, tức là theo loại thông báo.

Khi đã thấy một số thông tin nhất định về hàng đợi tin nhắn, bây giờ là lúc kiểm tra lệnh gọi hệ thống (Hệ thống V) hỗ trợ hàng đợi tin nhắn.

Để thực hiện liên lạc bằng cách sử dụng hàng đợi tin nhắn, sau đây là các bước:

Step 1 - Tạo một hàng đợi tin nhắn hoặc kết nối với một hàng đợi tin nhắn đã tồn tại (msgget ())

Step 2 - Viết vào hàng đợi tin nhắn (msgsnd ())

Step 3 - Đọc từ hàng đợi tin nhắn (msgrcv ())

Step 4 - Thực hiện các thao tác điều khiển trên hàng đợi tin nhắn (msgctl ())

Bây giờ, chúng ta hãy kiểm tra cú pháp và một số thông tin về các cuộc gọi trên.

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

int msgget(key_t key, int msgflg)

Lệnh gọi hệ thống này tạo hoặc phân bổ hàng đợi thông điệp Hệ thống V. Các đối số sau cần được truyền:

  • Đối số đầu tiên, khóa, nhận dạng hàng đợi tin nhắn. Khóa có thể là một giá trị tùy ý hoặc một giá trị có thể được dẫn xuất từ ​​hàm thư viện ftok ().

  • Đối số thứ hai, shmflg, chỉ định cờ / s hàng đợi tin nhắn bắt buộc như IPC_CREAT (tạo hàng đợi tin nhắn nếu không tồn tại) hoặc IPC_EXCL (Được sử dụng với IPC_CREAT để tạo hàng đợi tin nhắn và cuộc gọi không thành công, nếu hàng đợi tin nhắn đã tồn tại). Cần phải vượt qua các quyền.

Note - Tham khảo các phần trước để biết chi tiết về các quyền.

Cuộc gọi này sẽ trả về một định danh hàng đợi tin nhắn hợp lệ (được sử dụng cho các cuộc gọi tiếp theo của hàng đợi tin nhắn) khi thành công và -1 trong trường hợp thất bại. Để biết nguyên nhân của lỗi, hãy kiểm tra với biến errno hoặc hàm perror ().

Các lỗi khác nhau liên quan đến lệnh gọi này là EACCESS (quyền bị từ chối), EEXIST (hàng đợi đã tồn tại không thể tạo), ENOENT (hàng đợi không tồn tại), ENOMEM (không đủ bộ nhớ để tạo hàng đợi), v.v.

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

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

Cuộc gọi hệ thống này gửi / nối một tin nhắn vào hàng đợi tin nhắn (Hệ thống V). Các đối số sau cần được truyền:

  • Đối số đầu tiên, msgstr, nhận dạng hàng đợi thông báo, tức là định danh hàng đợi thông báo. Giá trị định danh được nhận khi thành công của msgget ()

  • Đối số thứ hai, msgp, là con trỏ đến thông báo, được gửi đến người gọi, được định nghĩa trong cấu trúc của biểu mẫu sau:

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

Biến mtype được sử dụng để giao tiếp với các kiểu thông báo khác nhau, được giải thích chi tiết trong lệnh gọi msgrcv (). Biến mtext là một mảng hoặc cấu trúc khác có kích thước được chỉ định bởi msgsz (giá trị dương). Nếu trường mtext không được đề cập, thì nó được coi là thông báo kích thước bằng không, được phép.

  • Đối số thứ ba, msgsz, là kích thước của thông báo (thông báo phải kết thúc bằng ký tự rỗng)

  • Đối số thứ tư, msgflg, chỉ ra một số cờ nhất định như IPC_NOWAIT (trả về ngay lập tức khi không tìm thấy thông báo nào trong hàng đợi hoặc MSG_NOERROR (cắt bớt văn bản thông báo, nếu nhiều hơn msgsz byte)

Lệnh gọi này sẽ trả về 0 khi thành công và -1 trong trường hợp thất bại. Để biết nguyên nhân của lỗi, hãy kiểm tra với biến errno hoặc hàm 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)

Lệnh gọi hệ thống này lấy tin nhắn từ hàng đợi tin nhắn (Hệ thống V). Các đối số sau cần được truyền:

  • Đối số đầu tiên, msgstr, nhận dạng hàng đợi thông báo, tức là, định danh hàng đợi thông báo. Giá trị định danh được nhận khi thành công của msgget ()

  • Đối số thứ hai, msgp, là con trỏ của thông báo nhận được từ người gọi. Nó được định nghĩa trong cấu trúc của biểu mẫu sau:

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

Kiểu biến được sử dụng để giao tiếp với các kiểu thông báo khác nhau. Biến mtext là một mảng hoặc cấu trúc khác có kích thước được chỉ định bởi msgsz (giá trị dương). Nếu trường mtext không được đề cập, thì nó được coi là thông báo kích thước bằng không, được phép.

  • Đối số thứ ba, msgsz, là kích thước của thư nhận được (thư phải kết thúc bằng ký tự rỗng)

  • Đối số fouth, msgtype, cho biết loại thông báo -

    • If msgtype is 0 - Đọc tin nhắn nhận được đầu tiên trong hàng đợi

    • If msgtype is +ve - Đọc tin nhắn đầu tiên trong hàng đợi kiểu msgtype (nếu msgtype là 10, thì chỉ đọc tin nhắn đầu tiên của kiểu 10 mặc dù các kiểu khác có thể nằm trong hàng đợi ở đầu)

    • If msgtype is –ve - Đọc thông báo đầu tiên của loại thấp nhất nhỏ hơn hoặc bằng giá trị tuyệt đối của loại thông báo (giả sử, nếu loại tin nhắn là -5, thì nó đọc thông báo đầu tiên của loại nhỏ hơn 5 tức là loại thông báo từ 1 đến 5)

  • Đối số thứ năm, msgflg, chỉ ra một số cờ nhất định như IPC_NOWAIT (trả về ngay lập tức khi không tìm thấy thông báo nào trong hàng đợi hoặc MSG_NOERROR (cắt bớt văn bản thông báo nếu nhiều hơn msgsz byte)

Lệnh gọi này sẽ trả về số byte thực sự nhận được trong mảng mtext khi thành công và -1 trong trường hợp thất bại. Để biết nguyên nhân của lỗi, hãy kiểm tra với biến errno hoặc hàm perror ().

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

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

Lời gọi hệ thống này thực hiện các hoạt động điều khiển hàng đợi tin nhắn (Hệ thống V). Các đối số sau cần được truyền:

  • Đối số đầu tiên, msgstr, nhận dạng hàng đợi thông báo, tức là, định danh hàng đợi thông báo. Giá trị định danh được nhận khi thành công của msgget ()

  • Đối số thứ hai, cmd, là lệnh để thực hiện thao tác điều khiển cần thiết trên hàng đợi tin nhắn. Giá trị hợp lệ cho cmd là -

IPC_STAT- Sao chép thông tin về các giá trị hiện tại của từng thành viên trong struct msqid_ds vào cấu trúc được trỏ bởi buf. Lệnh này yêu cầu quyền đọc trên hàng đợi tin nhắn.

IPC_SET - Đặt ID người dùng, ID nhóm của chủ sở hữu, quyền, v.v. được trỏ đến theo cấu trúc buf.

IPC_RMID - Loại bỏ hàng đợi tin nhắn ngay lập tức.

IPC_INFO - Trả về thông tin về giới hạn hàng đợi thông báo và các tham số trong cấu trúc được trỏ bởi buf, thuộc loại struct msginfo

MSG_INFO - Trả về một cấu trúc msginfo chứa thông tin về tài nguyên hệ thống được tiêu thụ bởi hàng đợi tin nhắn.

  • Đối số thứ ba, buf, là một con trỏ đến cấu trúc hàng đợi thông báo có tên struct msqid_ds. Các giá trị của cấu trúc này sẽ được sử dụng cho cả set hoặc get theo cmd.

Lệnh gọi này sẽ trả về giá trị tùy thuộc vào lệnh được truyền. Thành công của IPC_INFO và MSG_INFO hoặc MSG_STAT trả về chỉ mục hoặc mã định danh của hàng đợi thông báo hoặc 0 cho các hoạt động khác và -1 trong trường hợp thất bại. Để biết nguyên nhân của lỗi, hãy kiểm tra với biến errno hoặc hàm perror ().

Sau khi xem thông tin cơ bản và các cuộc gọi hệ thống liên quan đến hàng đợi tin nhắn, bây giờ đã đến lúc kiểm tra với một chương trình.

Hãy để chúng tôi xem mô tả trước khi xem chương trình -

Step 1 - Tạo hai quy trình, một là để gửi vào hàng đợi tin nhắn (msgq_send.c) và một là để truy xuất từ ​​hàng đợi tin nhắn (msgq_recv.c)

Step 2- Tạo khóa, sử dụng hàm ftok (). Đối với điều này, ban đầu tệp msgq.txt được tạo để nhận một khóa duy nhất.

Step 3 - Quá trình gửi thực hiện như sau.

  • Đọc đầu vào chuỗi từ người dùng

  • Xóa dòng mới, nếu nó tồn tại

  • Gửi vào hàng đợi tin nhắn

  • Lặp lại quy trình cho đến khi kết thúc đầu vào (CTRL + D)

  • Sau khi nhận được kết thúc đầu vào, gửi thông báo “kết thúc” để biểu thị sự kết thúc của quá trình

Step 4 - Trong quá trình nhận, thực hiện như sau.

  • Đọc tin nhắn từ hàng đợi
  • Hiển thị đầu ra
  • Nếu thông báo nhận được là "kết thúc", hãy kết thúc quá trình và thoát

Để đơn giản hóa, chúng tôi không sử dụng loại thông báo cho mẫu này. Ngoài ra, một quá trình đang ghi vào hàng đợi và một quá trình khác đang đọc từ hàng đợi. Điều này có thể được mở rộng khi cần thiết, tức là, lý tưởng nhất là một quá trình sẽ ghi vào hàng đợi và nhiều quá trình được đọc từ hàng đợi.

Bây giờ, chúng ta hãy kiểm tra quá trình (tin nhắn gửi vào hàng đợi) - Tệp: 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;
}

Các bước biên dịch và thực hiện

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.

Sau đây là đoạn mã từ quá trình nhận tin nhắn (lấy tin nhắn từ hàng đợi) - Tệp: 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;
}

Các bước biên dịch và thực hiện

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