詳細:linux程序間的通訊方式
**
程序間的通訊
文章目錄
前言
程序間通訊就是在不同程序之間傳播或交換資訊,那麼不同程序之間存在著什麼雙方都可以訪問的介質呢?程序的使用者空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享記憶體區。另外,系統空間是“公共場所”,各程序均可以訪問,所以核心也可以提供這樣的條件。此外,還有雙方都可以訪問的外設。在這個意義上,兩個程序當然也可以通過磁碟上的普通檔案交換資訊,或者通過“登錄檔”或其它資料庫中的某些表項和記錄交換資訊。廣義上這也是程序間通訊的手段,但是一般都不把這算作“程序間通訊”。
一、管道
無名管道pipe
特點:
- 單工通訊模式,具有固定的讀端和寫端;
- 只能用於具有親緣關係的程序之間的通訊;管道沒有名稱,只能通過pipe函式得到管道的檔案描述符,需要通過複製的方式實現檔案描述符的傳遞。
- 是一個特殊檔案,可以使用IO操作read/write來實現資料的接收和傳送;
- 資料是以位元組為單位進行傳輸。
管道到建立:
#include <unistd.h>
int pipe(int pipefd[2]);
引數:
pipefd是一個由兩個元素的陣列首元素地址,用來儲存管道的讀檔案描述符和寫檔案描述符;
pipefd[0]表示的是管道的讀端,pipefd[1]表示的是管道的寫端;
成功返回0;
失敗返回-1,且修改errno的值;
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int fd[2]; // 兩個檔案描述符
7 pid_t pid;
8 char buff[20];
9
10 if(pipe(fd) < 0) // 建立管道
11 printf("Create Pipe Error!\n");
12
13 if((pid = fork()) < 0) // 建立子程序
14 printf("Fork Error!\n");
15 else if(pid > 0) // 父程序
16 {
17 close(fd[0]); // 關閉讀端
18 write(fd[1], "hello world\n", 12);
19 }
20 else
21 {
22 close(fd[1]); // 關閉寫端
23 read(fd[0], buff, 20);
24 printf("%s", buff);
25 }
26
27 return 0;
28 }
有名管道fifo
在核心中建立的管道,同時在檔案系統中建立管道的名稱,不同的程序可以通過名稱去訪問同一個管道,從而實現任意程序之間的通訊。
特點:
- 實現任意程序之間的通訊;
- 雙工通訊模式,
- 是一個特殊檔案,可以使用IO操作read/write來實現資料的接收和傳送;
- 資料是以位元組為單位進行傳輸。
管道建立:
- 命令建立
mkfifo 管道名稱 - 函式建立
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
引數:
引數1:pathname表示所建立管道的名稱(絕對路徑/相對路徑)
引數2:mode表示的管道的訪問許可權
返回值:
成功返回0;
失敗返回-1,且修改errno的值;
PS:
- 在建立管道的時候,不能在掛載目錄中去建立;
- 有名管道不存在的時候建立,存在的時候返回錯誤資訊;
有名管道通訊實現:
通過檔案IO的方式,對管道檔案進行資料的讀寫
傳送資料:向管道中寫資料;
接收資料:從管道中讀取資料。
read.c
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<errno.h>
4 #include<fcntl.h>
5 #include<sys/stat.h>
6
7 int main()
8 {
9 int fd;
10 int len;
11 char buf[1024];
12
13 if(mkfifo("myfifo", 0666) < 0 && errno!=EEXIST) // 建立FIFO管道 後面的判斷是判斷管道是否已經存在,如果已經存在就跳過建立
14 perror("Create FIFO Failed");
15
16 if((fd = open("myfifo", O_RDONLY)) < 0) // 以只讀開啟FIFO檔案
17 {
18 perror("Open FIFO Failed"); //判斷是否開啟成功
19 exit(1);
20 }
21
22 while((len = read(fd, buf, 1024)) > 0) // 讀取FIFO管道
23 printf("Read message: %s", buf);
24
25 close(fd); // 關閉FIFO檔案
26 return 0;
27 }
write.c
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<fcntl.h>
4 #include<sys/stat.h>
5 #include<time.h> // time
6
7 int main()
8 {
9 int fd;
10 int n, i;
11 char buf[1024];
12 time_t tp;
13
14 printf("I am %d process.\n", getpid()); // 說明程序ID
15
16 if((fd = open("myfifo", O_WRONLY)) < 0) // 以寫開啟一個FIFO
17 {
18 perror("Open FIFO Failed");
19 exit(1);
20 }
21
22 for(i=0; i<5; ++i)
23 {
24 time(&tp); // 取系統當前時間
25 n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
26 printf("Send message: %s", buf); // 列印
27 if(write(fd, buf, n+1) < 0) // 寫入到FIFO中
28 {
29 perror("Write FIFO Failed");
30 close(fd);
31 exit(1);
32 }
33 sleep(1); // 休眠1秒
34 }
35
36 close(fd); // 關閉FIFO檔案
37 return 0;
38 }
同時編譯執行兩個程式碼,結果如下圖所示
二、訊號signal
特徵:
- 訊號是軟體層次上對終端模擬;
- 訊號不能實現實際資料的互動,用於請求的動作或者事件的傳送;
- 如果訊號所對應的程序阻塞,訊號不會發送,而是由核心儲存,直到程序執行,訊號才會傳送。
訊號的處理:
過程:- 訊號的註冊:訊號發生後的執行方式
忽略訊號:不做任何處理(SIGKILL、SIGSTOP)
預設處理(預設的方式):
捕捉訊號:按照自定義訊號處理函式去處理。 - 訊號產生 --> 根據訊號的處理方式處理訊號 --> 訊號銷燬
訊號的傳送:
通過shell命令kill傳送訊號:
kill -signal pid 給程序pid傳送訊號signal
通過kill函式來給任意程序傳送訊號;
- 訊號的註冊:訊號發生後的執行方式
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
引數:
引數1:pid表示訊號接收程序的程序id;
引數2:sig表示傳送的訊號編號
返回值:
成功返回0;
失敗返回-1,且修改errno的值;
通過raise給呼叫者程序傳送訊號
#include <signal.h>
int raise(int sig);
引數:sig表示傳送的訊號編號
通過函式alarm給呼叫者傳送SIGALRM訊號;
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
引數:seconds表示的是定時器的初始值(以秒為單位);
返回值:
成功:如果程序沒有設定鬧鐘,則返回0;
如果之前已經設定過鬧鐘,返回的是上一次鬧鐘的剩餘時間;
失敗返回-1,且修改errno的值;
訊號處理:
通過signal函式實現訊號的註冊:將訊號和訊號處理函式關聯(當訊號產生,就會去執行相應的訊號處理函式)
void (*signal(int signum, void (*handler)(int)))(int);
採用的原則:右左原則,
signum:int型別的變數;
handler:指標 函式(引數是int,返回值void) ==> 引數為int,返回值為void的函式指標;
signal:函式(引數有兩個(int 函式指標), 返回值:指標 函式(引數為int ,返回值void))
typedef void(* handler_t)(int)
hander_t signal(int signum, handler_t handler);
引數:
引數1:signum表示需要註冊的訊號編號
引數2: handler表示訊號的處理方式:
SIG_IGN:訊號忽略;
SIG_DFL:預設處理方式;
自定義訊號處理函式,當訊號產生,就會去執行訊號處理函式
三、system V IPC 物件機制:
思路:
key --> IPC物件 --> 操作IPC物件
key == 0: 每次建立都會得到一個新的IPC物件;
key != 0: 在建立的時候,如果IPC物件不存在,則建立新的IPC物件,如果IPC物件存在,則訪問原有的IPC物件
key的獲取:
手動設定:使用大於等於0的整數;
自動生成:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
引數:
引數1:pathname表示的是程式的執行路徑(相對路徑/絕對路徑);
引數2:proj_id表示的是程式的專案編號(只需要使用低8位的值);
返回值:
成功返回key
失敗返回-1,且修改errno的值。
1.共享記憶體
特點:
- 使用者空間可以直接操作核心空間,效率最高;
- 當個任務訪問同一核心空間的時候,會出現資源的互斥。需要採用同步和互斥的機制來優化處理;
實現流程:
- 建立或者開啟共享記憶體
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
引數:
引數1:key用來申請共享記憶體
引數2:size表示申請共享記憶體空間的大小;
引數3: shmflg表示共享記憶體的訪問許可權和操作方式,需要建立設定IPC_CREAT
返回值:
成功返回共享記憶體的id;
失敗返回-1,且修改errno的值。
- 對映共享記憶體:將核心空間的地址對映給使用者空間,使用者空間可以之間訪問核心;
void *shmat(int shmid, const void *shmaddr, int shmflg);
引數:
引數1:shmid表示需要對映共享記憶體的id號;
引數2:shmaddr表示對映空間的起始地址,
手動設定:給定一個使用者空間地址,必須保證足夠的空間未被使用,在去完成對映,對映成功的地址就是傳遞的地址;
自動對映:由系統自動進行分配,使用者只需要使用NULL填充;
引數3:shmflg表示對映後對於共享記憶體的訪問許可權
shmflg = 0, 表示使用預設屬性,可以進行讀寫訪問;
shmflg = SHM_RDONLY, 表示設定為只讀訪問;
返回值:
成功返回對映後的起始地址;
失敗返回(void *) -1,且修改errno的值。
-
共享記憶體的讀寫,就是直接對指標的操作
-
共享記憶體解對映:使用者空間不能再去訪問核心空間的地址
int shmdt(const void *shmaddr);
引數:shmaddr表示解除對映空間的起始地址
返回值:
成功返回0;
失敗返回-1,且修改errno的值。
- 操控共享記憶體:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
引數:
引數1:shmid表示需要操控的共享記憶體的id號;
引數2:cmd操控請求命令;
IPC_STAT:獲取共享記憶體的屬性,屬性值使用buf來接受;
IPC_SET:設定共享的屬性,屬性值由buf傳遞;
IPC_RMID:刪除共享記憶體,引數3無意義使用NULL填充;
引數3:buf表示命令的傳輸資料
返回值:
成功返回0;
失敗返回-1,且修改errno的值。
示例程式碼:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
/*初始化共享記憶體*/
int shm_init(void **shmaddr,size_t size)
{
int ret,shmid;
key_t key;
key = ftok(".",'a');
if(key == -1)
{
perror("ftok");
return -1;
}
/*建立共享記憶體*/
shmid = shmget(key,size,IPC_CREAT | 0777);
if(shmid == -1)
{
perror("shmget");
return -1;
}
/*對映共享記憶體*/
*shmaddr = shmat(shmid,NULL,0);
if(*shmaddr == (void *)-1)
{
perror("shmat");
return -1;
}
return shmid;
}
/*取消共享記憶體*/
int shm_uninit(int shmid,void **shmaddr)
{
int ret;
/*分離共享記憶體*/
ret = shmdt(*shmaddr);
if(ret == -1)
{
perror("shmdt");
return -1;
}
/*刪除共享記憶體 id號*/
ret = shmctl(shmid,IPC_RMID,NULL);
if(ret == -1)
{
perror("shmctl");
return -1;
}
return 0;
}
int main(void)
{
void *shmaddr; //共享記憶體的首地址
int ret;
int shmid;
pid_t pid;
shmid = shm_init(&shmaddr,128);
if(shmid == -1)
{
return -1;
}
pid = fork();
if(pid == -1)
{
perror("fork");
return -1;
}
else if(pid == 0) //子程序輸入
{
while(1)
{
fgets(shmaddr,128,stdin);
}
exit(EXIT_SUCCESS);
}
while(1) //父程序列印
{
printf("shmaddr:%s\n",(char *)shmaddr);
}
ret = shm_uninit(shmid,&shmaddr);
return 0;
}
以上在父子程序中來展示共享記憶體,當然也可以任意程序之間
2.訊息佇列
特點:
- 任意程序間通訊;
- 以整個資料訊息傳送,可以保證資料的完整性操作;
- 可以通過型別傳送和接收,可以考慮訊息的優先順序和多個程序之間資料的幾乎比較簡單;
實現流程:
- 建立或者開啟訊息佇列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
引數:
引數1:key用來申請訊息佇列
IPC_PRIVATE:(key_t)0,在每次呼叫msgget都會得到一個新的訊息佇列
非IPC_PRIVATE:在建立的時候,如果IPC物件不存在,則建立新的IPC物件,如果IPC物件存在,則訪問原有的IPC物件
引數2:msgflg表示訊息佇列的許可權和操作方式
如果要建立訊息佇列,使用IPC_CREAT
返回值:
成功返回訊息佇列的id號;
失敗返回-1,且修改errno的值。
- 傳送訊息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
引數:
引數1:msqid表示傳送訊息的訊息佇列id
引數2:msgq結構體指標,表示傳送訊息的儲存空間的起始地址
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data,訊息的儲存空間,使用者需要根據訊息的位元組數來自定義大小,需要小於等於MSGMNB */
};
引數3: msgsz表示的訊息內容陣列空間的大小;
引數4:msgflg表示訊息傳送的方式:
0:表示阻塞方式傳送,只有當訊息傳送完成才會返回;
IPC_NOWAIT:非阻塞方式傳送,不管訊息是否傳送完成,都會立即返回;
返回值:
成功返回0;
失敗返回-1,且修改errno的值。
- 接收訊息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
引數:
引數1:msqid表示接收訊息的訊息佇列id
引數2:msgq結構體指標,表示接收訊息的儲存空間的起始地址;
引數3:msgsz表示的訊息內容陣列空間的大小;
引數4:msgtyp接收訊息的型別:
msgtyp = 0, 接收訊息對列中的第一條訊息;
msgtyp > 0, 接收訊息佇列中型別為msgtyp的第一條訊息;
msgtyp < 0, 接收訊息佇列中型別<=|msgtyp|中型別最小的第一條訊息;
引數5:msgflg表示訊息傳送的方式:
0:表示阻塞方式接收,只有當訊息接收完成才會返回;
IPC_NOWAIT:非阻塞方式接收,不管訊息是否接收完成,都會立即返回;
- 操控訊息佇列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
引數:
引數1:msqid表示需要操控的訊息佇列的id號;
引數2:cmd操控請求命令;
IPC_STAT:獲取訊息佇列的屬性,屬性值使用buf來接受;
IPC_SET:設定訊息佇列的屬性,屬性值由buf傳遞;
IPC_RMID:刪除訊息佇列,引數3無效可以是NULL填充;
引數3:buf表示命令的傳輸資料
返回值:
成功返回0;
失敗返回-1,且修改errno的值。
示例程式碼:
msgrcv.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/*mtype 對應才能相互接收發送*/
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
/*初始化訊息佇列*/
int msg_init(void)
{
/*建立IPV key值*/
key_t key=ftok(".", 'b');
if(key == -1)
{
perror("ftok");
return -1;
}
/*建立訊息佇列*/
int msgid=msgget(key, IPC_CREAT | 0777);
if(msgid == -1)
{
perror("msgget");
return -1;
}
return msgid;
}
/*刪除訊息佇列*/
int msg_uninit(int msgid)
{
int ret = msgctl(msgid,IPC_RMID,NULL);
if(ret == -1)
{
perror("msgctl->IPC_RMTD");
return -1;
}
return 0;
}
int main(void)
{
struct msgbuf buf;
int msgid, ret;
/*初始化訊息佇列*/
msgid = msg_init();
if(msgid == -1)
{
exit(EXIT_FAILURE);
}
buf.mtype=2;
while(1)
{
ret = msgrcv(msgid,(void *)&buf, 128,buf.mtype,0);
if(ret == -1)
{
perror("msgsnd");
return -1;
}
printf("rcv:%s",buf.mtext);
}
/*刪除訊息佇列ID*/
ret = msg_uninit(msgid);
if(ret == -1)
{
exit(EXIT_FAILURE);
}
return 0;
}
msgsnd.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
/*初始化訊息佇列*/
int msg_init(void)
{
/*建立IPV key值*/
key_t key=ftok(".", 'b');
if(key == -1)
{
perror("ftok");
return -1;
}
/*建立訊息佇列*/
int msgid=msgget(key, IPC_CREAT | 0777);
if(msgid == -1)
{
perror("msgget");
return -1;
}
return msgid;
}
/*刪除訊息佇列*/
int msg_uninit(int msgid)
{
int ret = msgctl(msgid,IPC_RMID,NULL);
if(ret == -1)
{
perror("msgctl->IPC_RMTD");
return -1;
}
return 0;
}
int main(void)
{
struct msgbuf buf;
int msgid, ret;
/*初始化訊息佇列*/
msgid = msg_init();
if(msgid == -1)
{
exit(EXIT_FAILURE);
}
while(1)
{
scanf("%ld",&(buf.mtype));
getchar();
fgets(buf.mtext,128,stdin);
ret = msgsnd(msgid, (void*)&buf, 128, 0);
if(ret == -1)
{
perror("msgsnd");
return -1;
}
}
/*刪除訊息佇列ID*/
ret = msg_uninit(msgid);
if(ret == -1)
{
exit(EXIT_FAILURE);
}
return 0;
}
3.訊號量
實現流程:
- 獲取訊號燈集合
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
引數:
引數1:key用來申請訊號燈集
引數2:nsems表示訊號燈集合中訊號燈的數量
引數3:semflg表示訊號燈集的操作方式和訪問許可權
返回值:
成功返回訊號燈集的id
失敗返回-1,且修改errno的值。
- 操控訊號燈集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...)
引數:
引數1:semid表示所要操控的訊號燈集id;
引數2:semnum表示訊號燈集中訊號燈的序號(0.1.2.3 .......)
引數3:cmd操控請求命令;
IPC_RMID: 刪除訊號燈
SETVAL:設定訊號燈的值,將值儲存到共用體semun中
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};
GETVAL:獲取訊號燈的值。
返回值:
失敗返回-1,且修改errno的值。
四、socket套接字
使用套接字除了可以實現網路間不同主機間的通訊外,還可以實現同一主機的不同程序間的通訊,且建立的通訊是雙向的通訊。socket程序通訊與網路通訊使用的是統一套介面,只是地址結構與某些引數不同。
一、建立socket流程
(1)建立socket,型別為AF_LOCAL或AF_UNIX,表示用於程序通訊:
建立套接字需要使用 socket 系統呼叫,其原型如下:
int socket(int domain, int type, int protocol);
其中,domain 引數指定協議族,對於本地套接字來說,其值須被置為 AF_UNIX 列舉值;type 引數指定套接字型別,protocol 引數指定具體協議;type 引數可被設定為 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(資料報式套接字),protocol 欄位應被設定為 0;其返回值為生成的套接字描述符。
對於本地套接字來說,流式套接字(SOCK_STREAM)是一個有順序的、可靠的雙向位元組流,相當於在本地程序之間建立起一條資料通道;資料報式套接字(SOCK_DGRAM)相當於單純的傳送訊息,在程序通訊過程中,理論上可能會有資訊丟失、複製或者不按先後次序到達的情況,但由於其在本地通訊,不通過外界網路,這些情況出現的概率很小。
二、命名socket。
SOCK_STREAM 式本地套接字的通訊雙方均需要具有本地地址,其中伺服器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 型別的變數。
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX /
char sun_path[UNIX_PATH_MAX]; / 路徑名 */
};
這裡面有一個很關鍵的東西,socket程序通訊命名方式有兩種。一是普通的命名,socket會根據此命名建立一個同名的socket檔案,客戶端連線的時候通過讀取該socket檔案連線到socket服務端。這種方式的弊端是服務端必須對socket檔案的路徑具備寫許可權,客戶端必須知道socket檔案路徑,且必須對該路徑有讀許可權。
另外一種命名方式是抽象名稱空間,這種方式不需要建立socket檔案,只需要命名一個全域性名字,即可讓客戶端根據此名字進行連線。後者的實現過程與前者的差別是,後者在對地址結構成員sun_path陣列賦值的時候,必須把第一個位元組置0,即sun_path[0] = 0,
其中,offsetof函式在#include 標頭檔案中定義。因第二種方式的首位元組置0,
提示:客戶端連線伺服器的時候,必須與服務端的命名方式相同,即如果服務端是普通命名方式,客戶端的地址也必須是普通命名方式;如果服務端是抽象命名方式,客戶端的地址也必須是抽象命名方式。
三、繫結
SOCK_STREAM 式本地套接字的通訊雙方均需要具有本地地址,其中伺服器端的本地地址需要明確指定,指定方法是使用 struct sockaddr_un 型別的變數,將相應欄位賦值,再將其繫結在建立的伺服器套接字上,繫結要使用 bind 系統呼叫,其原形如下:
int bind(int socket, const struct sockaddr *address, size_t address_len);
其中 socket表示伺服器端的套接字描述符,address 表示需要繫結的本地地址,是一個 struct sockaddr_un 型別的變數,address_len 表示該本地地址的位元組長度。實現伺服器端地址指定功能的程式碼如下(假設伺服器端已經通過上文所述的 socket 系統呼叫建立了套接字,server_sockfd 為其套接字描述符):
struct sockaddr_un server_address;
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, “Server Socket”);
bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));
客戶端的本地地址不用顯式指定,只需能連線到伺服器端即可,因此,客戶端的 struct sockaddr_un 型別變數需要根據伺服器的設定情況來設定,程式碼如下(假設客戶端已經通過上文所述的 socket 系統呼叫建立了套接字,client_sockfd 為其套接字描述符):
struct sockaddr_un client_address;
client_address.sun_family = AF_UNIX;
strcpy(client_address.sun_path, “Server Socket”);
四、監聽
伺服器端套接字建立完畢並賦予本地地址值(名稱,本例中為Server Socket)後,需要進行監聽,等待客戶端連線並處理請求,監聽使用 listen 系統呼叫,接受客戶端連線使用accept系統呼叫,它們的原形如下:
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t address_len);
其中 socket 表示伺服器端的套接字描述符;backlog 表示排隊連線佇列的長度(若有多個客戶端同時連線,則需要進行排隊);address 表示當前連線客戶端的本地地址,該引數為輸出引數,是客戶端傳遞過來的關於自身的資訊;address_len 表示當前連線客戶端本地地址的位元組長度,這個引數既是輸入引數,又是輸出引數。實現監聽、接受和處理的程式碼如下:
#define MAX_CONNECTION_NUMBER 10
int server_client_length, server_client_sockfd;
struct sockaddr_un server_client_address;
listen(server_sockfd, MAX_CONNECTION_NUMBER);
while(1)
{
// … (some process code)
server_client_length = sizeof(server_client_address);
server_client_sockfd = accept(server_sockfd, (struct sockaddr)&server_client_address, &server_client_length);
// … (some process code)
}
這裡使用死迴圈的原因是伺服器是一個不斷提供服務的實體,它需要不間斷的進行監聽、接受並處理連線,本例中,每個連線只能進行序列處理,即一個連線處理完後,才能進行後續連線的處理。如果想要多個連線併發處理,則需要建立執行緒,將每個連線交給相應的執行緒併發處理。
客戶端套接字建立完畢並賦予本地地址值後,需要連線到伺服器端進行通訊,讓伺服器端為其提供處理服務。對於 SOCK_STREAM 型別的流式套接字,需要客戶端與伺服器之間進行連線方可使用。連線要使用 connect 系統呼叫,其原形為
int connect(int socket, const struct sockaddr *address, size_t address_len);
其中socket為客戶端的套接字描述符,address表示當前客戶端的本地地址,是一個 struct sockaddr_un 型別的變數,address_len 表示本地地址的位元組長度。實現連線的程式碼如下:
connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));
無論客戶端還是伺服器,都要和對方進行資料上的互動,這種互動也正是我們程序通訊的主題。一個程序扮演客戶端的角色,另外一個程序扮演伺服器的角色,兩個程序之間相互發送接收資料,這就是基於本地套接字的程序通訊。傳送和接收資料要使用 write 和 read 系統呼叫,它們的原形為:
int read(int socket, char *buffer, size_t len);
int write(int socket, char *buffer, size_t len);
其中 socket 為套接字描述符;len 為需要傳送或需要接收的資料長度;對於 read 系統呼叫,buffer 是用來存放接收資料的緩衝區,即接收來的資料存入其中,是一個輸出引數;對於 write 系統呼叫,buffer 用來存放需要傳送出去的資料,即 buffer 內的資料被髮送出去,是一個輸入引數;返回值為已經發送或接收的資料長度。例如客戶端要傳送一個 “Hello” 字串給伺服器,則程式碼如下:
char buffer[10] = “Hello”;
write(client_sockfd, buffer, strlen(buffer));
互動完成後,需要將連線斷開以節省資源,使用close系統呼叫,其原形為:
int close(int socket);
總結
socket本地通訊不怎麼使用,一般在網路程式設計中使用socket比較多,socket的網路程式設計看另外一篇文章,每一種程序間通訊都有自己的優缺點,根據不同的情況選擇不同的方式。