APUE学习笔记(十七)高级进程间通信

17.1 Unix域套接字

UNIX 域套接字用于在同一台计算机上运行的进程之间的通信。UNIX 域套接字就像是套接字和管道的混合。一对相互连接的UNIX域套接字可以起到全双工管道的作用。

和网络套接字相比:

  • 在同一计算机上使用,效率更高;
  • 仅复制数据,不进行协议处理;
  • 域套接字提供流和数据报两种接口,数据报服务是可靠的;

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

创建消息队列,监听消息

#include "apue.h"
#include <netdb.h>
#include <sys/msg.h>
#include <poll.h>
#include <pthread.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)  //创建连接,fd[0], fd[1]通过匿名套接字双向连接
      err_sys("socketpair error");

    pfd[i].fd = fd[0]; //访问读端
    pfd[i].events = POLLIN;  //关注的事件是IN
    ti[i].qid = qid[i];  //每个消息队列一个线程
    ti[i].fd = fd[1];   //消息队列使用写端
    if ((err = pthread_create(&tid[i], NULL, helper, &ti[i])) != 0)  //创建线程,并执行helper
      err_exit(err, "pthread_create error");
  }

  for(;;) {
    if (poll(pfd, NQ, -1) < 0)  //poll监听事件,看读端是否可以读数据
      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)  //从对应fd读取数据到buf
          err_sys("read error");
        buf[n] = 0; //添加结尾\0
        printf("queue id %d, message %s\n", qid[i], buf);
      }
    }
  }

  exit(0);
}

发送消息到消息队列

#include "apue.h"
#include <sys/msg.h>

#define MAXMSZ 512

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

int main(int argc, char *argv[]) {
  key_t key;
  long qid;
  size_t nbytes;
  struct mymsg 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)  //创建或打开一个消息队列,返回队列id
    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);
}

命名UNIX域套接字

socketpair函数创建一对相互连接的套接字,但是每一个套接字都没有名字。可以把域套接字绑定到一个路径地址,系统会用该路径名创建一个S_IFSOCK类型的文件。

例子

#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_DGRAM, 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 failed");
  printf("UNIX domain socket bound\n");
  exit(0);
}

17.2 唯一连接

基于命名UNIX域套接字,可以类似网络套接字一样在同一计算机内的两个进程间建立唯一连接。不同的是UNIX域套接字的连接命名是基于文件的,通过文件名标识一个套接字地址。

例子,serv_listen,serv_accept,cli_conn的实现在apue/lib库文件中。

服务端  socket->bind->listen->accept
客户端  socket[->bind]->connect

服务器端

#include "apue.h"

int main(int argc, char *argv[]) {
  int fd, fd2;
  uid_t uid;

  if (argc != 2)
    err_sys("usage server name");
  fd = serv_listen(argv[1]);
  uid = 123;
  for(;;) {
    fd2 = serv_accept(fd, &uid);
    printf("accept %d\n", fd2);
    uid = uid + 1;
  }

  exit(0);
}

客户端

#include "apue.h"

int main(int argc, char *argv[]) {
  if (argc != 2)
    err_sys("usage: client name");
   cli_conn(argv[1]);
}

17.3 传送文件描述符

当一个进程向另一个进程传送一个打开文件描述符时,可以让发送进程和接收进程共享同一文件表项。接收进程分配第一个可用的描述符项,因此发送进程和接收进程中的描述符编号通常是不同的。

当一个进程(通常是服务器进程)想将一个描述符传送给另一个进程时,可以调用send_fd或send_err。 
等待接收描述符的进程(客户进程)调用recv_fd。

下面看一下代码具体实现。这个实现是自定义协议的实现,只是示例。

send_fd

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

/* size of control buffer to send/recv one file descriptor */
#define	CONTROLLEN	CMSG_LEN(sizeof(int))

static struct cmsghdr	*cmptr = NULL;	/* malloc'ed first time */

/*
 * Pass a file descriptor to another process.
 * If fd<0, then -fd is sent back instead as the error status.
 */
int
send_fd(int fd, int fd_to_send)
{
	struct iovec	iov[1];   //iovec表示io缓存buf
	struct msghdr	msg;   //包含了要发送或接受的消息
	char			buf[2];	/* send_fd()/recv_fd() 2-byte protocol */

	iov[0].iov_base = buf;   //在2字节0之后开始
	iov[0].iov_len  = 2;   //长度为2字节
	msg.msg_iov     = iov;
	msg.msg_iovlen  = 1;  //数组长度1
	msg.msg_name    = NULL;
	msg.msg_namelen = 0;

	if (fd_to_send < 0) {  //如果fd为负数
		msg.msg_control    = NULL;
		msg.msg_controllen = 0;
		buf[1] = -fd_to_send;	/* nonzero status means error */
		if (buf[1] == 0)
			buf[1] = 1;	/* -256, etc. would screw up protocol */
	} else {
		if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
			return(-1);
		cmptr->cmsg_level  = SOL_SOCKET;  //控制级别为套接字
		cmptr->cmsg_type   = SCM_RIGHTS;  //传送访问权
		cmptr->cmsg_len    = CONTROLLEN;
		msg.msg_control    = cmptr;  //控制信息头
		msg.msg_controllen = CONTROLLEN;
		*(int *)CMSG_DATA(cmptr) = fd_to_send;		/* the fd to pass */
		buf[1] = 0;		/* zero status means OK */
	}

	buf[0] = 0;			/* null byte flag to recv_fd() */
	if (sendmsg(fd, &msg, 0) != 2)   //发送消息,fd是建好连接的套接字
		return(-1);
	return(0);
}

17.4 open服务器进程

利用文件描述符传输技术开发一个服务,实现服务器进程从客户进程到服务器进程传送文件名和打开模式,而从服务器进程到客户进程返回描述符。 文件内容不需通过IPC交换。

服务器进程有两种执行方式,一是由客户端进程执行生成;一是作为守护进程执行。

客户端fork生成

#include	"open.h"
#include	<fcntl.h>

#define	BUFFSIZE	8192

/*
 *  客户端程序 1
 */
int main(int argc, char *argv[])
{
	int		n, fd;
	char	buf[BUFFSIZE];
	char	line[MAXLINE];

	/* read filename to cat from stdin */
	while (fgets(line, MAXLINE, stdin) != NULL) {
		if (line[strlen(line) - 1] == '\n')
			line[strlen(line) - 1] = 0; /* replace newline with null */

		/* open the file */
		if ((fd = csopen(line, O_RDONLY)) < 0)  //fork子进程执行服务端功能
			continue;	/* csopen() prints error from server */

		/* and cat to stdout */
		while ((n = read(fd, buf, BUFFSIZE)) > 0)
			if (write(STDOUT_FILENO, buf, n) != n)
				err_sys("write error");
		if (n < 0)
			err_sys("read error");
		close(fd);
	}

	exit(0);
}

守护进程

#include	"opend.h"
#include	<syslog.h>

int		 debug, oflag, client_size, log_to_stderr;
char	 errmsg[MAXLINE];
char	*pathname;
Client	*client = NULL;

/*
 *  服务器进程2
 */
int main(int argc, char *argv[])
{
	int		c;

	log_open("open.serv", LOG_PID, LOG_USER);

	opterr = 0;		/* don't want getopt() writing to stderr */
	while ((c = getopt(argc, argv, "d")) != EOF) {  //遍历检查函数参数,d表示守护进程
		switch (c) {
		case 'd':		/* debug */
			debug = log_to_stderr = 1;   // debug = log_to_stderr; log_to_stderr = 1;
			break;

		case '?':
			err_quit("unrecognized option: -%c", optopt);
		}
	}

	if (debug == 0)
		daemonize("opend");  //转为守护进程,父进程关闭

	loop();		/* never returns */
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值