第17章 高级进程间通信

1. UNIX域套接字

  用于同一台计算机上运行的进程之间的通信。提供流和数据报两种接口,UNIX域数据报服务是可靠的。

  int socketpair(int domain, int type, int protocol, int sockfd[2]); //创建一对无命名的,相互连接的UNIX域套接字

  //借助UNIX域套接字轮询XSI消息队列

<span style="font-size:18px;">#include "apue.h"
#include <poll.h>
#include <pthread.h>
#include <sys/msg.h>
#include <sys/socket.h>

#define NQ 3
#define MAXMSZ 512
#define KEY 0x123

struct threadinfo{
	int qid;
	int fd;
};

struct mymesg{
	long mtype;
	char mtext[MAXMSZ];
};

void *helper(void *arg)
{
	int n;
	struct mymesg m;
	struct threadinfo *tip = arg;
	
	for(;;) {
		memset(&m, 0, sizeof(m));
		if((n = msgrcv(tip->qid, &m, MAXMSZ, 0, MSG_NOERROR)) < 0)
			err_sys("msgrcv error");
		if(write(tip->fd, m.mtext, n) < 0)
			err_sys("write error");
	}
}

int main()
{
	int i, n, err;
	int fd[2];
	int qid[NQ];
	struct pollfd pfd[NQ];
	struct threadinfo ti[NQ];
	pthread_t tid[NQ];
	char buf[MAXMSZ];

	for(i = 0; i < NQ; i++) {
		if((qid[i] = msgget((KEY+i), IPC_CREAT|0666)) < 0)
			err_sys("msgget error");
		
		printf("queue ID %d is %d\n", i, qid[i]);

		if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd) < 0)
			err_sys("socketpair error");
		pfd[i].fd = fd[0];
		pfd[i].events = POLLIN;
		ti[i].qid = qid[i];
		ti[i].fd = fd[1];
		if((err = pthread_create(&tid[i], NULL, helper, &ti[i])) != 0)
			err_exit(err, "pthread_create error");
	}
	for(;;) {
		if(poll(pfd, NQ, -1) < 0)
			err_sys("poll error");
		for(i = 0; i < NQ; i ++) {
			if(pfd[i].revents & POLLIN) {
				if((n = read(pfd[i].fd, buf, sizeof(buf))) < 0)
					err_sys("read error");
				buf[n] = 0;
				printf("queue id %d, message %s\n", qid[i], buf);
			}
		}
	}
	exit(0);
}
</span>


  //运用下面的程序来测试上面程序的消息发送

<span style="font-size:18px;">#include "apue.h"
#include <sys/msg.h>

#define MAXMSZ 512

struct mymesg{
	long mtype;
	char mtext[MAXMSZ];
};

int main(int argc, char *argv[])
{
	key_t key;
	long qid;
	size_t nbytes;
	struct mymesg m;

	if(argc != 3) {
		fprintf(stderr, "usage: sendmsg KEY message\n");
		exit(1);
	}
	key = strtol(argv[1], NULL, 0);
	if((qid = msgget(key, 0)) < 0)
		err_sys("can't open queue key %s", argv[1]);
	memset(&m, 0, sizeof(m));
	strncpy(m.mtext, argv[2], MAXMSZ - 1);
	nbytes = strlen(m.mtext);
	m.mtype = 1;
	if(msgsnd(qid, &m, nbytes, 0) < 0)
		err_sys("can't send message");
	exit(0);
	
}
</span>

  命名UNIX域套接字

  //将地址绑定到UNIX域套接字,如果我们试图绑定一个已经存在的文件,那么bing请求会失败。

<span style="font-size:18px;">#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>

int main(void)
{
	int fd, size;
	struct sockaddr_un un;
	
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, "foo.socket");
	if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 
		err_sys("socket failed");
	size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
	if(bind(fd, (struct sockaddr *)&un, size) < 0)
		err_sys("bind error");
	printf("UNIX domain socket bound\n");
	exit(0);
}
</span>


2. 唯一连接

  //使用以下三个函数,在同一台计算机上的两个无关进程间创建唯一连接。

  int serv_listen(const char *name); //若成功,返回要监听的文件描述符;若出错,返回负值。

  int serv_accept(int listenfd, uid_t *uidptr); //若成功,返回新文件描述符;若出错,返回负值。

  int cli_conn(const char *name); //若成功,返回文件描述符;若出错,返回负值。


  //serv_listen声明它要在一个众所周知的名字上监听客户进程的连接请求

<span style="font-size:18px;">#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define QLEN 10

int serv_listen(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;
	if(strlen(name) >= sizeof(un.sun_path)) {
		errno = ENAMETOOLONG;
		return -1;
	}
	
	//create a UNIX domain stream socket
	if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return -2;
	
	//ensure it's not 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 = -3;
		goto errout;
	}
	
	//tell kernel we're a server
	if(listen(fd, QLEN) < 0) {
		rval = -4;
		goto errout;
	}
	return (fd);

errout:
	err = errno;
	close(fd);
	errno = err;
	return rval;
}	</span>


  //serv_accept函数等待客户进程连接请求的到达

<span style="font-size:18px;">#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <errno.h>

#define STALE 30  //client's name can't be older than this

/******************************************************
Wait for a client connnection to arrive, and accept it.
We also obtain the client's user ID from the pathname
that it must bind before calling us.
Returns new fd if all is OK, < 0 on error
******************************************************/

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

	// allocate enough space for longest name plus terminating null
	if((name = malloc(sizeof(un.sun_path) + 1)) == NULL)
		return -1;
	len = sizeof(un);
	if((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
		free(name);
		return -2;
	}
	
	// obtain the client's uid from its calling address
 	len -= offsetof(struct sockaddr_un, sun_path);
	memcpy(name, un.sun_path, len);
	name[len] = 0;
	if(stat(name, &statbuf) < 0) {
		rval = -3;
		goto errout;
	}

#ifdef S_ISSOCK
	if(S_ISSOCK(statbuf.st_mode) == 0) {
		rval = -4;
		goto errout;
	}
#endif
	
	if((statbuf.st_mode & (S_IRWXG | S_IRWXO)) || (statbuf.st_mode & S_IRWXU) != S_IRWXU) {
		rval = -5;
		goto errout;
	}
	
	staletime = time(NULL) - STALE;
	if(statbuf.st_atime < staletime || statbuf.st_ctime < staletime || statbuf.st_mtime < staletime) {
		rval = -6;
		goto errout;
	}
	
	if(uidptr != NULL)
		*uidptr = statbuf.st_uid;
	unlink(name);
	free(name);
	return clifd;

errout:
	err = errno;
	close(clifd);
	free(name);
	errno = err;
	return rval;
}
</span>


  //cli_conn函数连接至服务器进程

#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define CLI_PATH "/var/tmp"
#define CLI_PERM S_IRWXU

/*********************************************
create a client endpoint and connect to server
return fd if all OK, < 0 on error.
*********************************************/

int cli_conn(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un  un, sun;
	int do_unlink = 0;

	if(strlen(name) >= sizeof(un.sun_path)) {
		errno = ENAMETOOLONG;
		return -1;
	}

	//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%05ld", CLI_PATH, (long)getpid());
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
	
	unlink(un.sun_path);
	if(bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}
	if(chmod(un.sun_path, CLI_PERM) < 0) {
		rval = -3;
		do_unlink = 1;
		goto errout;
	}
	
	//fill socket address structure with server's address
	memset(&sun, 0, sizeof(sun));
	sun.sun_family = AF_UNIX;
	strcpy(sun.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if(connect(fd, (struct sockaddr *)&sun, len) < 0) {
		rval = -4;
		do_unlink = 1;
		goto errout;
	}
	return fd;

errout:
	err = errno;
	close(fd);
	if(do_unlink)
		unlink(un.sun_path);
	errno = err;
	return rval;
}

3. 传送文件描述符

  int send_fd(int fd, int fd_to_send); //使用fd代表的UNIX域套接字发送描述符fd_to_send

  int send_err(int fd, int status, const char *errmsg); //使用fd发送msgerr以及后随的status字节

  int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t)); //客户进程用此来接受描述符


#include "apue.h"

/********************************************************
Used when we had planned to send an fd using send_fd(),
but encountered an error instead. We send the error back
using the send_fd()/recv_fd() protocol.
********************************************************/

int send_err(int fd, int errcode, const char *msg)
{
	int n;
	if((n = strlen(msg)) > 0)
		if(writen(fd, msg, n) != n)
			return -1;
	
	if(errcode >= 0)
		errcode = -1;

	if(send_fd(fd, errcode) < 0)
		return -1;
	
	return 0;
}

  send_fd(P649), recv_fd(P651)。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值