CD資料庫程式
現在我們可以使用我們在這一章所瞭解的IPC工具來修改我們的CD資料庫程式。
我們可以使用三種IPC工具的多種不同組,但是因為我們需要傳送的資訊很少,直接使用訊息佇列實現請求的傳遞是一個很明顯的選擇。
如果我們需要傳遞的資料量很大,我們可以考慮使用共用記憶體傳遞實際的資料,並且使用訊號量或是訊息來傳遞一個標記通知其他的進程在共用記憶體中有資料可用。
訊息佇列介面解決我們了在第11章所遇到的問題,即當資料傳遞時我們需要兩個進程使得管道開啟。使用訊息佇列可以使得一個進程將訊息放入隊列,儘管這個進程是當前隊列的唯一使用者。
我們需要考慮的一個重要決定就是將答案返回給客戶。一個簡單的選擇就是使得一個隊列用於伺服器而且每個客戶有一個隊列。如果有大量的並發客戶,由於需要大量的訊息佇列就會引起問題。通過使用訊息中的訊息ID域,我們可以使得所有的使用者使用一個隊列,並且通過在訊息中使用用戶端進程ID來將響應發送給指定的用戶端進程。從而每一個客戶可以接收只屬於他自己的訊息,而將其他客戶的訊息留在隊列中。
要轉換我們的CD程式來使用IPC工具,我們只需要替換pipe_imp.c檔案。在下面的部分,我們將會描述替換檔案ipc_imp.c的原則部分。
實驗--修改伺服器函數
1 首先,我們包含正確的標頭檔,聲明訊息佇列索引值,並且定義一個儲存我們訊息資料的結構:
#include “cd_data.h”
#include “cliserv.h”
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define SERVER_MQUEUE 1234
#define CLIENT_MQUEUE 4321
struct msg_passed {
long int msg_key; /* used for client pid */
message_db_t real_message;
};
2 兩個全域變數儲存由msgget函數所返回的兩個隊列標識符:
static int serv_qid = -1;
static int cli_qid = -1;
3 我們使得伺服器負責建立兩個訊息佇列:
int server_starting()
{
#if DEBUG_TRACE
printf(“%d :- server_starting()/n”, getpid());
#endif
serv_qid = msgget((key_t)SERVER_MQUEUE, 0666 | IPC_CREAT);
if (serv_qid == -1) return(0);
cli_qid = msgget((key_t)CLIENT_MQUEUE, 0666 | IPC_CREAT);
if (cli_qid == -1) return(0);
return(1);
}
4 伺服器同時負責退出時的清理工作。當伺服器結束時,我們設定我們的全域變數為非法值。這就會捕獲伺服器嘗試在調用server_ending之後發送訊息的bug。
void server_ending()
{
#if DEBUG_TRACE
printf(“%d :- server_ending()/n”, getpid());
#endif
(void)msgctl(serv_qid, IPC_RMID, 0);
(void)msgctl(cli_qid, IPC_RMID, 0);
serv_qid = -1;
cli_qid = -1;
}
5 伺服器read函數由隊列中讀取一條任意的訊息,並且返回訊息的資料部分。
int read_request_from_client(message_db_t *rec_ptr)
{
struct msg_passed my_msg;
#if DEBUG_TRACE
printf(“%d :- read_request_from_client()/n”, getpid());
#endif
if (msgrcv(serv_qid, (void *)&my_msg, sizeof(*rec_ptr), 0, 0) == -1) {
return(0);
}
*rec_ptr = my_msg.real_message;
return(1);
}
6 使用儲存在清求中標識訊息的客戶進程ID來發送響應:
int send_resp_to_client(const message_db_t mess_to_send)
{
struct msg_passed my_msg;
#if DEBUG_TRACE
printf(“%d :- send_resp_to_client()/n”, getpid());
#endif
my_msg.real_message = mess_to_send;
my_msg.msg_key = mess_to_send.client_pid;
if (msgsnd(cli_qid, (void *)&my_msg, sizeof(mess_to_send), 0) == -1) {
return(0);
}
return(1);
}
實驗--修改用戶端函數
1 當用戶端啟動時,他需要探索服務器與用戶端隊列標識符。用戶端並不建立隊列。如果伺服器沒有運行,這個函數就會失敗,因為訊息佇列並不存在。
int client_starting()
{
#if DEBUG_TRACE
printf(“%d :- client_starting/n”, getpid());
#endif
serv_qid = msgget((key_t)SERVER_MQUEUE, 0666);
if (serv_qid == -1) return(0);
cli_qid = msgget((key_t)CLIENT_MQUEUE, 0666);
if (cli_qid == -1) return(0);
return(1);
}
2 與伺服器一樣,當用戶端結束時,我們設定我們的全域變數為非法值。這將會捕獲當用戶端嘗試在調用client_ending之後發送訊息的bug。
void client_ending()
{
#if DEBUG_TRACE
printf(“%d :- client_ending()/n”, getpid());
#endif
serv_qid = -1;
cli_qid = -1;
}
3 要向伺服器發送訊息,我們在我們的結構中儲存資料。注意,我們必須設定訊息索引值。因為0作為索引值是非法的,保留索引值未定義就意味著他可以使用一個隨機值,所以如果這個值恰好為0時函數就會失敗。
int send_mess_to_server(message_db_t mess_to_send)
{
struct msg_passed my_msg;
#if DEBUG_TRACE
printf(“%d :- send_mess_to_server()/n”, getpid());
#endif
my_msg.real_message = mess_to_send;
my_msg.msg_key = mess_to_send.client_pid;
if (msgsnd(serv_qid, (void *)&my_msg, sizeof(mess_to_send), 0) == -1) {
perror(“Message send failed”);
return(0);
}
return(1);
}
4 當用戶端伺服器接收訊息時,他會使用其進程ID來接收只發送給他的訊息,而忽略其他進程的訊息。
int read_resp_from_server(message_db_t *rec_ptr)
{
struct msg_passed my_msg;
#if DEBUG_TRACE
printf(“%d :- read_resp_from_server()/n”, getpid());
#endif
if (msgrcv(cli_qid, (void *)&my_msg, sizeof(*rec_ptr), getpid(), 0) == -1) {
return(0);
}
*rec_ptr = my_msg.real_message;
return(1);
}
5 要獲得與pipe_imp.c的完全相容,我們需要定義另外四個函數。然而,在我們的新程式中,這個函數是空的。當使用管道時他們所實現的操作也不再需要了。
int start_resp_to_client(const message_db_t mess_to_send)
{
return(1);
}
void end_resp_to_client(void)
{
}
int start_resp_from_server(void)
{
return(1);
}
void end_resp_from_server(void)
{
}
此程式到訊息佇列的轉換示範了IPC訊息佇列的強大。我們需要更少的函數,而我們所需要要比以前的實現少得多。
IPC狀態函數
儘管X/Open並沒有要求,大多數的Linux提供了一個命令集合來允許命令列訪問IPC資訊,並且清理無關聯的IPC工具。這就是ipcs與ipcrm命令,當我們開發程式時,這是非常有用的。
編寫糟糕的程式或是因為某些原因失敗的程式會遺留其IPC資源。這會使得新的程式調用失敗,因為程式期望以一個乾淨的系統開始,但是卻發現一些遺留的資源。狀態(ipcs)與清除(ipcrm)命令提供了一個檢測與清除IPC遺留資源的一種方法。
訊號量
要檢測系統中資訊量的狀態,可以使用ipcs -s命令。如果存在一些訊號量,輸出就會有如下的形式:
$ ./ipcs -s
——— Semaphore Arrays ————
semid owner perms nsems status
768 rick 666 1
我們可以使用ipcrm命令來移除由程式偶然留下的訊號量。要刪除前面的訊號量,可以使用下面的命令:
$ ./ipcrm -s 768
一些較老的Linux系統使用一些略微不同的文法:
$ ./ipcrm sem 768
但是這種風格不再推薦使用。查看我們系統的手冊頁來確定在我們的系統上是哪種格式。
共用記憶體
與訊號量類似,許多系統提供了命令列程式用於訪問共用記憶體的詳細資料。命令為ipcs -m與ipcrm -m <id>。
如下面的例子輸出:
$ ipcs -m
——— Shared Memory Segments ————
shmid owner perms bytes nattch status
384 rick 666 4096 2
這顯示一個4KB的共用記憶體段與兩個進程相關聯。
ipcrm -m <id>命令可以移除共用記憶體。當一個程式清理共用記憶體失敗時,這會十分有用。
訊息佇列
對於訊息佇列的命令為ipcs -q與ipcrm -q <id>。
如下面的例子輸出:
$ ipcs -q
——— Message Queues ————
msqid owner perms used-bytes messages
384 rick 666 2048 2
這顯示了在訊息佇列中有兩個訊息,共計2048位元組。
ipcrm -q <id>命令可以移除訊息佇列。
總結
在這一章,我們瞭解了首次在UNIX Systme V.2中廣泛使用並且在Linux中可用的三種進程間互動工具。他們是訊號量,共用記憶體與訊息佇列。我們瞭解了他們所提供的進階功能以及如何提供這些功能,一旦理解了這些函數,他們就會為需要處理序間通訊的程式提供強大的解決方案。