1. 程式人生 > 其它 >詳細:linux程序間的通訊方式

詳細:linux程序間的通訊方式

技術標籤:程序執行緒Linuxlinuxc語言socket

**

程序間的通訊


文章目錄


前言

程序間通訊就是在不同程序之間傳播或交換資訊,那麼不同程序之間存在著什麼雙方都可以訪問的介質呢?程序的使用者空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享記憶體區。另外,系統空間是“公共場所”,各程序均可以訪問,所以核心也可以提供這樣的條件。此外,還有雙方都可以訪問的外設。在這個意義上,兩個程序當然也可以通過磁碟上的普通檔案交換資訊,或者通過“登錄檔”或其它資料庫中的某些表項和記錄交換資訊。廣義上這也是程序間通訊的手段,但是一般都不把這算作“程序間通訊”。

一、管道

無名管道pipe

特點:

  1. 單工通訊模式,具有固定的讀端和寫端;
  2. 只能用於具有親緣關係的程序之間的通訊;管道沒有名稱,只能通過pipe函式得到管道的檔案描述符,需要通過複製的方式實現檔案描述符的傳遞。
  3. 是一個特殊檔案,可以使用IO操作read/write來實現資料的接收和傳送;
  4. 資料是以位元組為單位進行傳輸。

管道到建立:

	#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

在核心中建立的管道,同時在檔案系統中建立管道的名稱,不同的程序可以通過名稱去訪問同一個管道,從而實現任意程序之間的通訊。
特點:

  1. 實現任意程序之間的通訊;
  2. 雙工通訊模式,
  3. 是一個特殊檔案,可以使用IO操作read/write來實現資料的接收和傳送;
  4. 資料是以位元組為單位進行傳輸。

管道建立:

  1. 命令建立
    mkfifo 管道名稱
  2. 函式建立
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

引數:
引數1:pathname表示所建立管道的名稱(絕對路徑/相對路徑)
引數2:mode表示的管道的訪問許可權
返回值:
成功返回0;
失敗返回-1,且修改errno的值;

PS:

  1. 在建立管道的時候,不能在掛載目錄中去建立;
  2. 有名管道不存在的時候建立,存在的時候返回錯誤資訊;
    有名管道通訊實現:
    通過檔案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

特徵:

  1. 訊號是軟體層次上對終端模擬;
  2. 訊號不能實現實際資料的互動,用於請求的動作或者事件的傳送;
  3. 如果訊號所對應的程序阻塞,訊號不會發送,而是由核心儲存,直到程序執行,訊號才會傳送。
    訊號的處理:
    過程:
    1. 訊號的註冊:訊號發生後的執行方式
      忽略訊號:不做任何處理(SIGKILL、SIGSTOP)
      預設處理(預設的方式):
      捕捉訊號:按照自定義訊號處理函式去處理。
    2. 訊號產生 --> 根據訊號的處理方式處理訊號 --> 訊號銷燬
      訊號的傳送:
      通過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.共享記憶體

特點:

  1. 使用者空間可以直接操作核心空間,效率最高;
  2. 當個任務訪問同一核心空間的時候,會出現資源的互斥。需要採用同步和互斥的機制來優化處理;

實現流程:

  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的值。
  1. 對映共享記憶體:將核心空間的地址對映給使用者空間,使用者空間可以之間訪問核心;
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的值。
  1. 共享記憶體的讀寫,就是直接對指標的操作

  2. 共享記憶體解對映:使用者空間不能再去訪問核心空間的地址

int shmdt(const void *shmaddr);
引數:shmaddr表示解除對映空間的起始地址
返回值:
	成功返回0;
	失敗返回-1,且修改errno的值。
  1. 操控共享記憶體:
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.訊息佇列

特點:

  1. 任意程序間通訊;
  2. 以整個資料訊息傳送,可以保證資料的完整性操作;
  3. 可以通過型別傳送和接收,可以考慮訊息的優先順序和多個程序之間資料的幾乎比較簡單;

實現流程:

  1. 建立或者開啟訊息佇列
#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的值。
  1. 傳送訊息
#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的值。
  1. 接收訊息
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:非阻塞方式接收,不管訊息是否接收完成,都會立即返回;
  1. 操控訊息佇列
#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.訊號量

實現流程:

  1. 獲取訊號燈集合
#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的值。
  1. 操控訊號燈集
#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的網路程式設計看另外一篇文章,每一種程序間通訊都有自己的優缺點,根據不同的情況選擇不同的方式。