0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux下進(jìn)程通信的方法

Linux愛(ài)好者 ? 來(lái)源:Linux愛(ài)好者 ? 2023-11-29 14:45 ? 次閱讀

一. 進(jìn)程簡(jiǎn)述:

進(jìn)程是操作系統(tǒng)的概念,每當(dāng)我們執(zhí)行一個(gè)程序時(shí),對(duì)于操作系統(tǒng)來(lái)講就創(chuàng)建了一個(gè)進(jìn)程,在這個(gè)過(guò)程中,伴隨著資源的分配和釋放??梢哉J(rèn)為進(jìn)程是一個(gè)程序的一次執(zhí)行過(guò)程。

1)Linux下進(jìn)程結(jié)構(gòu)

Linux下一個(gè)進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù),就是"代碼段"、"堆棧段"和"數(shù)據(jù)段"。其實(shí)學(xué)過(guò)匯編語(yǔ)言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。

這三個(gè)部分也是構(gòu)成一個(gè)完整的執(zhí)行序列的必要的部分。所以不同的進(jìn)程間,由于linux系統(tǒng)虛擬內(nèi)存地址的管理,這三個(gè)段也是獨(dú)立存在的,所以進(jìn)程間是不能相互訪問(wèn)數(shù)據(jù)的,需要通過(guò)系統(tǒng)提供的特殊方法來(lái)進(jìn)行相互通信。

2)Linux下進(jìn)程通信的方法:

進(jìn)程用戶空間是相互獨(dú)立的,一般而言是不能相互訪問(wèn)的。但很多情況下進(jìn)程間需要互相通信,來(lái)完成系統(tǒng)的某項(xiàng)功能。進(jìn)程通過(guò)與內(nèi)核及其它進(jìn)程之間的互相通信來(lái)協(xié)調(diào)它們的行為。

比較常用的IPC通信方法有:

管道(有名和無(wú)名)、信號(hào)、信號(hào)量、共享內(nèi)存、消息隊(duì)列和套接字socket通信。

image.png

3)進(jìn)程通信使用場(chǎng)景:

(1)數(shù)據(jù)傳輸:進(jìn)程間數(shù)據(jù)傳輸;

(2)通知事件:一個(gè)進(jìn)程向另一個(gè)或一組進(jìn)程發(fā)送消息,通知某個(gè)事件的發(fā)生(如子進(jìn)程終止時(shí)需通知父進(jìn)程);

(3)資源共享:多個(gè)進(jìn)程共享資源,需要內(nèi)核提供同步互斥機(jī)制;

(4)進(jìn)程控制:某進(jìn)程需要控制另一個(gè)進(jìn)程的執(zhí)行(如Debug進(jìn)程),此時(shí)控制進(jìn)程需要攔截另一個(gè)進(jìn)程的所有陷入、異常、狀態(tài)等。

二. 進(jìn)程通信的方法:

1. 有名管道和無(wú)名管道

a. 無(wú)名管道(父子進(jìn)程、兄弟進(jìn)程間通信):

---特點(diǎn):

(1) 半雙工。數(shù)據(jù)同一時(shí)刻只能單向傳輸;

(2) 數(shù)據(jù)從管道一端寫入,另一端讀出;

(3) 寫入管道的數(shù)據(jù)遵循先進(jìn)先出;

(4) 管道非普通文件,不屬于某個(gè)文件系統(tǒng),只存在于內(nèi)存;

(5) 無(wú)名管道只能在具有公共祖先的進(jìn)程(父子進(jìn)程、兄弟進(jìn)程等)之間使用。

---操作步驟:

(1)創(chuàng)建: pipe函數(shù)用來(lái)創(chuàng)建無(wú)名管道

(2)操作: read讀;write

(3)關(guān)閉操作端口: close

例子程序:

#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
char buf[32] = {0};
pid_t pid;

//  數(shù)量為 2 個(gè):一個(gè)讀端, 一個(gè)寫端,
int fd[2] = {-1};
// 創(chuàng)建無(wú)名管道
pipe(fd);
printf("fd[0] is %d
", fd[0]);
printf("fd[2] is %d
", fd[1]);

// 創(chuàng)建進(jìn)程
pid = fork();
if (pid < 0)
{
   printf("error
");
} 
if (pid > 0)
{
int status;
close(fd[0]);
write(fd[1], "hello", 5);
close(fd[1]);
wait(&status);
exit(0);
} 
if (pid == 0)
{
   close(fd[1]);
   read(fd[0], buf, 32);
   printf("buf is %s
", buf);
   close(fd[0]);
   exit(0);
}
return 0;
}  

b. 有名管道(允許無(wú)親緣關(guān)系進(jìn)程間的通信)。

----特點(diǎn):

(1) 它可以使互不相關(guān)的兩個(gè)進(jìn)程實(shí)現(xiàn)彼此通信

(2) 該管道可以通過(guò)路徑名來(lái)指出,并且在文件系統(tǒng)中是可見(jiàn)的。在建立管道之后,兩個(gè)進(jìn)程就可以把它當(dāng)作普通文件進(jìn)行讀寫,使用非常方便。

(3) FIFO嚴(yán)格遵循先進(jìn)先出原則,對(duì)管道及FIFO的讀總是從開(kāi)始處返回?cái)?shù)據(jù),對(duì)它們的寫則把數(shù)據(jù)添加到末尾。有名管道不支持如Iseek()等文件的定位操作。

---操作步驟:

(1) 創(chuàng)建有名管道文件: mkfifo即是命令也是函數(shù);mknod也可以創(chuàng)建管道文件;

(2) 打開(kāi)有名管道: open;

(3) 讀/寫: read/write

(4) 關(guān)閉: close

例子程序:

(1)named_pipe_write.c:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[])
{
int ret;
char buf[32] = {0};
int fd;
if (argc < 2)
{
   printf("Usage:%s  
", argv[0]);
   return -1;
} 
if (access(argv[1], F_OK) == -1)
{
///創(chuàng)建有名管道文件
ret = mkfifo(argv[1], 0666);
if (ret == -1)
{
 printf("mkfifo is error 
");
 return -2;
} 
printf("mkfifo is ok 
");
 }
///打開(kāi)有名管道文件
 fd = open(argv[1], O_WRONLY);
 while (1)
 {
sleep(1);
write(fd, "hello", 5);
 } 
 close(fd);
 return 0;
}
 

(2) named_pipe_read.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[])
{
char buf[32] = {0};
int fd;
if (argc < 2)
{
 printf("Usage:%s  
", argv[0]);
 return -1;
} 
///打開(kāi)有名管道,write已經(jīng)創(chuàng)建的文件;
fd = open(argv[1], O_RDONLY);
while (1)
{
 sleep(1);
 read(fd, buf, 32);
 printf("buf is %s
", buf);
 memset(buf, 0, sizeof(buf));
} 
close(fd);
return 0;
}

2.信號(hào)量

1) 概念和原理:

信號(hào)量是一種計(jì)數(shù)器,用于控制對(duì)共享資源的訪問(wèn)。每次進(jìn)程訪問(wèn)共享資源時(shí),都要先獲取一個(gè)信號(hào)量,如果信號(hào)量的值大于0,

則進(jìn)程可以繼續(xù)訪問(wèn),否則進(jìn)程需要等待。訪問(wèn)完成后,進(jìn)程會(huì)釋放信號(hào)量,使其值加1,以便其他進(jìn)程訪問(wèn);

2) 相關(guān)函數(shù):

linux系統(tǒng)提供如下函數(shù)來(lái)對(duì)信號(hào)量值進(jìn)行操作的,包括的頭文件為sys/sem.h。

--semget函數(shù):創(chuàng)建一個(gè)新信號(hào)量或者獲取一個(gè)已有的信號(hào)量的鍵

--semop函數(shù): 對(duì)信號(hào)量進(jìn)行改變,做p或者v操作

--semctl函數(shù):用來(lái)直接控制信號(hào)量信息

--刪除信號(hào)量:ipcrm -s id

3)例子程序:

---增加信號(hào)量的值例子(sempore_add.c )://=============================sempore_add.c==========================
#include 
#include 
#include 
#include 

#define KEY 1234

union semun {
int val;
struct semid_ds *buf;
ushort *array;
};

int main()
{
int semid = semget(KEY, 1, IPC_CREAT | 0666);
if (semid < 0) {
perror("semget error");
return 1;
}

union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) < 0) {
perror("semctl error");
return 1;
}

struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;

if (semop(semid, &buf, 1) < 0) {
perror("semop error");
return 1;
}

printf("Semaphore value: %d
", semctl(semid, 0, GETVAL, arg));
return 0;
}

---減少信號(hào)量的值例子(sempore_sub.c):

//=============================sempore_sub.c==========================

#include 
#include 
#include 
#include 

#define KEY 1234

union semun {
int val;
struct semid_ds *buf;
ushort *array;
};

int main()
{
int semid = semget(KEY, 1, IPC_CREAT | 0666);
if (semid < 0) {
perror("semget error");
return 1;
}

union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) < 0) {
perror("semctl error");
return 1;
}

struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;

if (semop(semid, &buf, 1) < 0) {
perror("semop error");
return 1;
}

printf("Semaphore value: %d
", semctl(semid, 0, GETVAL, arg));


////銷毀信號(hào)量  
if (semctl(semid, 0, IPC_RMID, 0) < 0) {
perror("semctl error");
return 1;
}

printf("Semaphore destroyed
");
return 0;
} 

3.信號(hào)

(1)信號(hào)概念和原理:

信號(hào)是一種異步通信方式,當(dāng)進(jìn)程接收到一個(gè)信號(hào)時(shí),會(huì)打斷當(dāng)前的執(zhí)行,轉(zhuǎn)而執(zhí)行與該信號(hào)相關(guān)聯(lián)的信號(hào)處理函數(shù)。 比較類似軟中斷。但信號(hào)和中斷有所不同,中斷的響應(yīng)和處理都發(fā)生在內(nèi)核空間,而信號(hào)的響應(yīng)發(fā)生在內(nèi)核空間, 信號(hào)處理程序的執(zhí)行卻發(fā)生在用戶空間。

Linux提供了許多信號(hào),如SIGINT、SIGTERM、SIGKILL等,可以在linux系統(tǒng) Shell 中查看所有信號(hào)和對(duì)應(yīng)的編號(hào):kill -l

(2)主要函數(shù):

a. void (*signal(int sig,void (*func)(int)))(int);

說(shuō)明:綁定收到某個(gè)信號(hào)后 的回調(diào)函數(shù).第一個(gè)參數(shù)為信號(hào),第二個(gè)參數(shù)為對(duì)此信號(hào)掛接用戶自己的處理函數(shù)指針。 返回值為以前信號(hào)處理程序的指針。例子:int ret = signal(SIGSTOP, sig_handle);

b. int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

說(shuō)明:由于 signal 不夠健壯,推薦使用 sigaction 函數(shù),sigaction 函數(shù)重新實(shí)現(xiàn)了 signal 函數(shù)

c. int kill(pid_t pid,int sig);

說(shuō)明:kill函數(shù)向進(jìn)程號(hào)為pid的進(jìn)程發(fā)送信號(hào),信號(hào)值為sig。當(dāng)pid為0時(shí),向當(dāng)前系統(tǒng)的所有進(jìn)程發(fā)送信號(hào)sig。

kill的pid參數(shù)有四種情況:

---pid>0, 則發(fā)送信號(hào)sig給進(jìn)程號(hào)為pid的進(jìn)程

---pid=0,則發(fā)送信號(hào)sig給當(dāng)前進(jìn)程所屬組中的所有進(jìn)程

---pid=-1,則發(fā)送信號(hào)sig給除1號(hào)進(jìn)程與當(dāng)前進(jìn)程外的所有進(jìn)程

---pid<-1,則發(fā)送信號(hào)sig給屬于進(jìn)程組pid的所有進(jìn)程

例子:結(jié)束父進(jìn)程 kill(getppid(), SIGKILL);

d. int raise(int sig);

說(shuō)明: 向當(dāng)前進(jìn)程中自舉一個(gè)信號(hào)sig, 即向當(dāng)前進(jìn)程發(fā)送信號(hào)。相當(dāng)于 kill(getpid(),sig);

e. unsigned int alarm(unsigned int seconds);

說(shuō)明: 用來(lái)設(shè)置信號(hào)SIGALRM在經(jīng)過(guò)參數(shù)seconds指定的秒數(shù)后傳送給目前的進(jìn)程. 如果參數(shù)seconds為0,則之前設(shè)置的鬧鐘會(huì)被取消,并將剩下的時(shí)間返回

f. int sigqueue(pid_t pid, int sig, const union sigval value);

說(shuō)明:用于向指定的進(jìn)程發(fā)送特定的信號(hào),并且可以傳遞一個(gè)額外的數(shù)據(jù)值。它提供了比 kill 函數(shù)更豐富的功能,可以用于進(jìn)程間的高級(jí)通信。

(3)例子程序:

-----------------signal_receiver.c ------------------------------
#include 
#include 
#include 
#include 
#include 
#include 

void signal_Handle(int sig, siginfo_t* info, void* ucontext)
{
printf("handler : sig = %d
", sig);
printf("handler : info->si_signo = %d
", info->si_signo);
printf("handler : info->si_code = %d
", info->si_code);
printf("handler : info->si_pid = %d
", info->si_pid);
printf("handler : info->si_value = %d
", info->si_value.sival_int);
}

int main(int argc, char** argv)
{
printf("pid :%d
", getpid());

struct sigaction act = {0};

act.sa_sigaction = signal_Handle;
act.sa_flags = SA_RESTART | SA_SIGINFO;

/* 添加信號(hào)屏蔽字 */
/* 下面信號(hào)在信號(hào)處理程序執(zhí)行時(shí)會(huì)被暫時(shí)阻塞 */
sigaddset(&act.sa_mask, 40);
sigaddset(&act.sa_mask, SIGINT);

/* 設(shè)置信號(hào)的處理行為,設(shè)置后40和SIGINT信號(hào)將由act里面的信號(hào)處理函數(shù)處理 */
sigaction(40, &act, NULL);
sigaction(SIGINT, &act, NULL);

while(1)
{
sleep(1);
}

return 0;
}
-----------------signal_sender.c ------------------------------
#include 
#include 
#include 
#include 
#include 


int main(int argc, char** argv)
{
pid_t pid = atoi(argv[1]);

union sigval sv = {123456};

//向指定pid發(fā)送信號(hào);
sigqueue(pid, 40, sv);

raise(SIGINT); 

return 0;
}

4.消息隊(duì)列

(1)概念和原理:

消息隊(duì)列是一種進(jìn)程間通信的方式,允許一個(gè)進(jìn)程向另一個(gè)進(jìn)程發(fā)送消息。它是一種異步通信方式,發(fā)送方發(fā)送消息后即可繼續(xù)執(zhí)行,不必等待接收方的響應(yīng)。

原理如下圖所示:

image.png

(2)特點(diǎn):

---消息隊(duì)列是消息的鏈表,存放于內(nèi)存中,內(nèi)核維護(hù)消息隊(duì)列;

---消息隊(duì)列中的消息是有類型和格式的;

---消息隊(duì)列可實(shí)現(xiàn)消息隨機(jī)查詢,不一定要遵循先進(jìn)先出的順序,而是每個(gè)進(jìn)程可以按照自定義的類型進(jìn)行讀取;

---與管道相同,讀出數(shù)據(jù)后,消息隊(duì)列對(duì)應(yīng)數(shù)據(jù)會(huì)被刪除;

---每個(gè)管道都有消息隊(duì)列標(biāo)識(shí)符,在整個(gè)系統(tǒng)中是唯一的;

---消息隊(duì)列允許一個(gè)或者多個(gè)進(jìn)程向它寫入或者讀取數(shù)據(jù);

---內(nèi)核重啟或者人為刪除才會(huì)刪除消息隊(duì)列,否則會(huì)一直存在與系統(tǒng)中

(3) 相關(guān)函數(shù):

a. key_t ftok(const char *pathname, int proj_id);

說(shuō)明:獲取系統(tǒng)唯一Key值(IPC鍵值),系統(tǒng)中可能會(huì)存在許多的消息隊(duì)列,通過(guò)Key這個(gè)系統(tǒng)唯一值,可以選擇想要進(jìn)入的消息隊(duì)列;

b. int msgget(key_t key, int msgflg);

說(shuō)明:創(chuàng)建或者打開(kāi)一個(gè)新的消息隊(duì)列。即使進(jìn)程不同,但是如果key值是相同的,那么也可以進(jìn)入相同的消息隊(duì)列,返回相同的消息隊(duì)列標(biāo)識(shí)符,

c. 查看消息隊(duì)列的一些Linux命令:

ipcs -q : 查看當(dāng)前進(jìn)程間通信之消息隊(duì)列

ipcrm -q 隊(duì)列號(hào): 刪除指定的消息隊(duì)列;

d. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

說(shuō)明: 將新消息添加到消息隊(duì)列;

e. ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

說(shuō)明: 從指定的消息隊(duì)列標(biāo)識(shí)符中接受信息,同時(shí)一旦接受成功,從消息隊(duì)列中刪除該信息。

f. int msgctl(int msqid, int cmd, struct msqid_ds *buf);

說(shuō)明:對(duì)消息隊(duì)列進(jìn)行修改,修改屬性或者刪除消息隊(duì)列等

image.png

(3)例子程序:

-----------------message_sender.c ------------------------------
#include
#include
#include
#include
#include
//定義消息
struct mess
{
long type;
char data[128];
};
int main()
{
key_t  key;

///創(chuàng)建生成唯一的key; 
if ((key = ftok("/home/tmp", 'a')) == -1) {
perror("ftok");
exit(1);
} 


int msgid=msgget((key_t)key,IPC_CREAT|0600); 
if(msgid==-1)
{
exit(0);
}
struct mess dt;
dt.type=1;
strcpy(dt.data,"hello1");

//1號(hào)消息內(nèi)容hello1
msgsnd(msgid,(void*)&dt,128,0);//標(biāo)志位0 
}
 

-----------------message_reader.c ------------------------------
#include
#include
#include
#include
 
struct mess
{
long type;
char data[128];
};
int main()
{
int msgid=msgget((key_t)1235,IPC_CREAT|0600);
if(msgid==-1)
{
exit(0);
}
struct mess dt;
msgrcv(msgid,(void*)&dt,128,1,0);
printf("%s",dt.data);

///刪除隊(duì)列
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
}

5.共享內(nèi)存

(1)概念:

共享內(nèi)存是一種高效的IPC機(jī)制,是最快的進(jìn)程間通信方式,很多追求效率的程序之間進(jìn)行通信的時(shí)候都會(huì)選擇它;它允許多個(gè)進(jìn)程共享同一個(gè)物理內(nèi)存區(qū)域,從而避免了數(shù)據(jù)拷貝和進(jìn)程切換的開(kāi)銷。

原理如下圖所示:

image.png

(2)共享內(nèi)存的建立與釋放:

---共享內(nèi)存的建立大致包括以下兩個(gè)過(guò)程:

a.在物理內(nèi)存當(dāng)中申請(qǐng)共享內(nèi)存空間;

b.將申請(qǐng)到的共享內(nèi)存掛接到地址空間,即建立映射關(guān)系;

---共享內(nèi)存的釋放大致包括以下兩個(gè)過(guò)程:

a. 將共享內(nèi)存與地址空間去關(guān)聯(lián),即取消映射關(guān)系。

b. 釋放共享內(nèi)存空間,即將物理內(nèi)存歸還給系統(tǒng)。

(3)相關(guān)函數(shù):

a.key_t ftok(const char *pathname, int proj_id);

說(shuō)明:這個(gè)返回的key值可以傳給共享內(nèi)存參數(shù),作為struct ipc_perm中唯一標(biāo)識(shí)共享內(nèi)存的key;

b. int shmget(key_t key, size_t size, int shmflg);

說(shuō)明:共享內(nèi)存的創(chuàng)建;

c. void *shmat(int shmid, const void *shmaddr, int shmflg)

說(shuō)明:將共享內(nèi)存連接到進(jìn)程地址空間,shmat函數(shù)的第三個(gè)參數(shù)shmflg有以下三個(gè)選項(xiàng):

SHM_RDONLY: 關(guān)聯(lián)共享內(nèi)存后只進(jìn)行讀取操作

SHM_RND:若shmaddr不為NULL,則關(guān)聯(lián)地址自動(dòng)向下調(diào)整為SHMLBA的整數(shù)倍。

0: 默認(rèn)為讀寫權(quán)限

d. int shmctl(int shmid, int cmd, struct shmid_ds *buf);

說(shuō)明:控制共享內(nèi)存,cmd如下:

IPC_STAT: 獲取共享內(nèi)存的當(dāng)前關(guān)聯(lián)值,此時(shí)參數(shù)buf作為輸出型參數(shù)

IPC_SET: 在進(jìn)程有足夠權(quán)限的前提下,將共享內(nèi)存的當(dāng)前關(guān)聯(lián)值設(shè)置為buf所指的數(shù)據(jù)結(jié)構(gòu)中的值

IPC_RMID: 刪除共享內(nèi)存段;

e. int shmdt(const void *shmaddr)

說(shuō)明:取消共享內(nèi)存與進(jìn)程地址空間之間的關(guān)聯(lián)

(3)內(nèi)存共存相關(guān)的系統(tǒng)命令:

---查看共享內(nèi)存命令:ipcs -m

---刪除共享內(nèi)存命令:ipcrm -m

(4)例子程序:共享內(nèi)存數(shù)據(jù)讀;

  //shm_server.c
  -----------------shm_server.c ------------------------------
#include 
#include 
#include 
#include 
#include 
#include 
 
#define PATHNAME "/home/IPC/shm/server.c" //路徑名 
#define PROJ_ID 0x6666 //整數(shù)標(biāo)識(shí)符
 
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID); //獲取key值
if (key < 0){
perror("ftok");
return 1;
}
 
int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //創(chuàng)建新的共享內(nèi)存
if (shm < 0){
perror("shmget");
return 2;
}

printf("key: %x
", key); //打印key值
printf("shm: %d
", shm); //打印共享內(nèi)存用戶層id
 
char* mem = shmat(shm, NULL, 0); //關(guān)聯(lián)共享內(nèi)存
 
while (1)
{ 
//服務(wù)端不斷讀取共享內(nèi)存當(dāng)中的數(shù)據(jù)并輸出
while (1)
{
printf("client# %s
", mem);
sleep(1);
}
 
}
 
shmdt(mem); //共享內(nèi)存去關(guān)聯(lián)
 
shmctl(shm, IPC_RMID, NULL); //釋放共享內(nèi)存
return 0;
}

客戶端: 共享內(nèi)存數(shù)據(jù)寫;

-----------------shm_client.c ------------------------------

#include 
#include 
#include 
#include 
#include 
#include 
 
#define PATHNAME "/home/IPC/shm/server.c" //路徑名 
#define PROJ_ID 0x6666 //整數(shù)標(biāo)識(shí)符
 
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID); //獲取與server進(jìn)程相同的key值
if (key < 0){
perror("ftok");
return 1;
}
int shm = shmget(key, SIZE, IPC_CREAT); //獲取server進(jìn)程創(chuàng)建的共享內(nèi)存的用戶層id
if (shm < 0){
perror("shmget");
return 2;
}
 
printf("key: %x
", key); //打印key值
printf("shm: %d
", shm); //打印共享內(nèi)存用戶層id
 
char* mem = shmat(shm, NULL, 0); //關(guān)聯(lián)共享內(nèi)存
 
int i = 0;
while (1)
{
//客戶端不斷向共享內(nèi)存寫入數(shù)據(jù)
int i = 0;
while (1)
{
mem[i] = 'A' + i;
i++;
mem[i] = '?';
sleep(1);
} 
}
 
shmdt(mem); //共享內(nèi)存去關(guān)聯(lián)
return 0;
}

6.套接字

(1)概念和原理:

套接字是一種用于網(wǎng)絡(luò)通信編程接口, 也是一種特殊的IPC通信機(jī)制,一般分為兩種角色:客戶端和服務(wù)器 ,既可以在本機(jī)不同進(jìn)程間通信,也可以在跨網(wǎng)絡(luò)不同的多臺(tái)主機(jī)間通信,可以一對(duì)多。

流程如下圖:

image.png

(2)相關(guān)函數(shù):

a. int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

說(shuō)明:創(chuàng)建Socket函數(shù)創(chuàng)建一個(gè)Socket對(duì)象,并指定通信協(xié)議和類型(流式或數(shù)據(jù)報(bào)式)。 IPPROTO_TCP表示TCP協(xié)議

b. int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux

說(shuō)明:綁定地址,使用 bind 函數(shù)將 Socket 綁定到一個(gè)特定的IP地址和端口號(hào)上。

c. listen;

說(shuō)明:設(shè)置監(jiān)聽(tīng),等待接收client端連接請(qǐng)求;

d. int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

說(shuō)明:接受連接,對(duì)于流式 Socket,使用 accept 函數(shù)接受客戶端的連接請(qǐng)求,并返回一個(gè)新的Socket對(duì)象用于與客戶端進(jìn)行通信。 對(duì)于數(shù)據(jù)報(bào)式Socket,可以省略此步驟。

e. int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

說(shuō)明:連接指定IP和端口的服務(wù)器

f. ssize_t send(int sockfd, const void *buf, size_t len, int flags);

或ssize_t write(int fd, const void *buf, size_t nbytes);

說(shuō)明:數(shù)據(jù)發(fā)送;

g. ssize_t recv(int sockfd, void *buf, size_t len, int flags);

或 ssize_t read(int fd, void *buf, size_t nbytes);

說(shuō)明:數(shù)據(jù)接收;

h. int close(int fd) ;

說(shuō)明:關(guān)閉連接,使用 close 函數(shù)關(guān)閉Socket連接。

(3)例子程序:

本地socket 通信 服務(wù)端程序:

----------------local_socket_server.c ------------------------------
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define QLEN 10
#define IPC_SOCKET_PATH "ipctest.socket"

int serv_listen(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;

/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
/* in case it already exists */
unlink(name); 

/* fill in socket address structure */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
rval = -3;
goto errout;
}
return(fd);

errout:
err = errno;
close(fd);
errno = err;
return(rval);
}

int serv_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, err, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;

len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return(-1); /* often errno=EINTR, if signal caught */

/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
un.sun_path[len] = 0; /* null terminate */

if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}
if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3; /* not a socket */
goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
/* we're done with pathname now */
unlink(un.sun_path); 
return(clifd);

errout:
err = errno;
close(clifd);
errno = err;
return(rval);
}

///////////////////////////main ////////////////////////////////
int main(void)
{
int lfd, cfd, n, i;
uid_t cuid;
char buf[1024];
lfd = serv_listen(IPC_SOCKET_PATH);

if (lfd < 0) {
switch (lfd) {
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}
cfd = serv_accept(lfd, &cuid);
if (cfd < 0) {
switch (cfd) {
case -3:perror("not a socket"); break;
case -2:perror("a bad filename"); break;
case -1:perror("accept"); break;
}
exit(-1);
}
while (1) 
{
n = read(cfd, buf, 1024);

if (n == -1) {
if (errno == EINTR)
break;
}
else if (n == 0) {
printf("the other side has been closed.
");
break;
}

////send back data to client
for (i = 0; i < n; i++)
{buf[i] = toupper(buf[i]);
write(cfd, buf, n);
}
}

close(cfd);
close(lfd);
return 0;
}
 

本地socket 通信 客戶端程序:

----------------local_socket_client.c ------------------------------

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
#define IPC_SOCKET_PATH "ipctest.socket"

/*
* Create a client endpoint and connect to a server.
* Returns fd if all OK, <0 on error.
*/
int cli_conn(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;

/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);

/* fill socket address structure with our address */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

/* in case it already exists */
unlink(un.sun_path); 
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}

/* fill socket address structure with server's address */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (connect(fd, (struct sockaddr *)&un, len) < 0) {
rval = -4;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}

///////////////////////////main ////////////////////////////////
int main(void)
{
int fd, n;
char buf[1024];

fd = cli_conn(IPC_SOCKET_PATH);
if (fd < 0) {
switch (fd) {
case -4:perror("connect"); break;
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}
while (fgets(buf, sizeof(buf), stdin) != NULL) {
write(fd, buf, strlen(buf));
n = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
}
close(fd);
return 0;
}

不同主機(jī)端的套接字IPC通信屬于網(wǎng)絡(luò)通信話題,這里就不再詳細(xì)論述了。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 通信
    +關(guān)注

    關(guān)注

    18

    文章

    6032

    瀏覽量

    135993
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11304

    瀏覽量

    209499
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6825

    瀏覽量

    123331
  • 進(jìn)程
    +關(guān)注

    關(guān)注

    0

    文章

    203

    瀏覽量

    13961

原文標(biāo)題:6.套接字

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux開(kāi)發(fā)_Linux進(jìn)程編程

    介紹Linux進(jìn)程概念、進(jìn)程信號(hào)捕獲、進(jìn)程管理相關(guān)的命令的使用等知識(shí)點(diǎn)。
    的頭像 發(fā)表于 09-17 15:38 ?1364次閱讀
    <b class='flag-5'>Linux</b>開(kāi)發(fā)_<b class='flag-5'>Linux</b><b class='flag-5'>下</b><b class='flag-5'>進(jìn)程</b>編程

    Linux系統(tǒng)進(jìn)程的幾種狀態(tài)介紹

    文章對(duì) Linux 系統(tǒng)進(jìn)程的幾種狀態(tài)進(jìn)行介紹,并對(duì)系統(tǒng)出現(xiàn)大量僵尸進(jìn)程和不可中斷進(jìn)程的場(chǎng)景進(jìn)行分析,使用常用的幾種工具進(jìn)行問(wèn)題分析定位。
    發(fā)表于 11-24 16:15 ?1.3w次閱讀
    <b class='flag-5'>Linux</b>系統(tǒng)<b class='flag-5'>下</b><b class='flag-5'>進(jìn)程</b>的幾種狀態(tài)介紹

    Linux進(jìn)程間如何實(shí)現(xiàn)共享內(nèi)存通信

    這次我們來(lái)講一Linux進(jìn)程通信中重要的通信方式:共享內(nèi)存作為Linux軟件開(kāi)發(fā)攻城獅,
    發(fā)表于 04-26 17:14 ?694次閱讀

    linux查詢進(jìn)程占用的內(nèi)存方法有哪些?

    linux查詢進(jìn)程占用的內(nèi)存方法
    發(fā)表于 04-08 06:03

    Linux進(jìn)程通信視頻教程

    Linux進(jìn)程通信視頻教程易懂易學(xué)的資料!Linux進(jìn)程通信.rar
    發(fā)表于 12-22 15:15

    linux操作系統(tǒng)進(jìn)程通信設(shè)計(jì)

    linux進(jìn)程通信手段基本上是從Unix平臺(tái)上的進(jìn)程通信手段繼承而來(lái)的。而對(duì)Unix發(fā)展做出
    發(fā)表于 04-16 09:17

    Linux進(jìn)程結(jié)構(gòu)

    `#嵌入式培訓(xùn)#華清遠(yuǎn)見(jiàn)嵌入式linux學(xué)習(xí)資料《Linux進(jìn)程結(jié)構(gòu)》,進(jìn)程不但包括程序的指令和數(shù)據(jù),而且包括程序計(jì)數(shù)器和處理器的所有寄
    發(fā)表于 08-05 11:05

    Linux進(jìn)程通信方式-管道

    Linux進(jìn)程通信方式-管道分享到: 本文關(guān)鍵字: linux 管道通信,
    發(fā)表于 08-29 15:29

    Linux進(jìn)程通信

    華清遠(yuǎn)見(jiàn)嵌入式linux學(xué)習(xí)資料《Linux進(jìn)程通信》,通過(guò)前面的學(xué)習(xí),讀者已經(jīng)知道了進(jìn)程
    發(fā)表于 09-04 10:07

    Linux學(xué)習(xí)雜談】之進(jìn)程通信

    進(jìn)程通信是在Linux應(yīng)用編程當(dāng)中比較重要的一個(gè)部分,我們需要認(rèn)真的研究這部分的內(nèi)容。那么Linux早期的時(shí)候分成了兩個(gè)幫派,一個(gè)是BS
    發(fā)表于 10-15 14:45

    哪些方式可以實(shí)現(xiàn)Linux系統(tǒng)進(jìn)程通信

    哪些方式可以實(shí)現(xiàn)Linux系統(tǒng)進(jìn)程通信?進(jìn)程與線程有哪些不同之處呢?
    發(fā)表于 12-24 06:38

    linux操作系統(tǒng)進(jìn)程通信設(shè)計(jì)

    linux進(jìn)程通信手段基本上是從Unix平臺(tái)上的進(jìn)程通信手段繼承而來(lái)的。而對(duì)Unix發(fā)展做出
    發(fā)表于 11-24 10:53 ?628次閱讀

    進(jìn)程通信Linux進(jìn)程通信概述

    人們現(xiàn)在廣泛使用的手機(jī)等方式。本章就是講述如何建立這些不同的通話方式,就像人們有多種通信方式一樣。 Linux進(jìn)程通信手段基本上是從UN
    發(fā)表于 10-18 16:21 ?0次下載

    linux操作系統(tǒng)進(jìn)程通信

    linux進(jìn)程通信手段基本上是從Unix平臺(tái)上的進(jìn)程通信手段繼承而來(lái)的。而對(duì)Unix發(fā)展做出
    發(fā)表于 10-31 11:15 ?0次下載

    Linux進(jìn)程通信方法之管道

    上文中我們介紹了進(jìn)程通信方法之一:信號(hào),本文將繼續(xù)介紹另一種進(jìn)程通信方法,即管道。管道是
    的頭像 發(fā)表于 05-14 15:47 ?1964次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>進(jìn)程</b>間<b class='flag-5'>通信</b><b class='flag-5'>方法</b>之管道