详细:linux进程间的通信方式


前言

进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。另外,系统空间是“公共场所”,各进程均可以访问,所以内核也可以提供这样的条件。此外,还有双方都可以访问的外设。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。

一、管道

无名管道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的网络编程看另外一篇文章,每一种进程间通信都有自己的优缺点,根据不同的情况选择不同的方式。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值