UNIX域套接字用于在同一台计算机上运行的进程之间的通信。这点比用因特网域套接字效率更高。因为UNIX域套接字仅仅复制数据,并不执行协议处理,不要添加或删除网络报头,无需计算校验和, 不要产生顺序号,无需发送确认报文。 UNIX域套接字提供流和数据包两种接口。UNIX域数据报服务是可靠的,既不会丢失报文,也不会传递出错。UNIX域套接字就像是套接字和管道的混合。可以使用它们面向网络的域套接字接口或使用socketpair函数创建一对无命名的、相互连接的UNIX域套接字。
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
返回值: 若成功,返回0; 若出错,返回-1
一对相互连接的UNIX域套接字可以起到全双工通道的作用:两端对读写都开放。
消息队列是XSI IPC三种方式之一(其他两个:信号量、共享存储)。每个内核中的IPC结构都用一个非负整数的标识符加以引用。例如, 要向一个消息队列发送消息或者从一个消息队列取消息,只需要知道其队列标识符。与文件描述符不同,IPC标识符不是小的整数。 当一个IPC结构被创建时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大正值,然后又回转到0。
标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上汇聚,提供了一个外部命名方案。 每个IPC对象都与一个键相关联,将这个键作为该对象的外部名。当创建一个新IPC结构时,都应该指定一个键,这个键的数据类型是基本系统数据类型key_t.
但是,消息队列不能关联到文件描述符,这就限制了它不能和poll或select函数一起使用。然而,套接字是和文件描述符相关联的,消息到达时,可以用套接字来通知。对每一个消息队列使用一个线程。每个线程都会在msgrcv调用中阻塞。当消息到达时,线程会把它写入一个UNIX域套接字的一端。当poll指示套接字可以读数据时,应用程序会使用这个套接字的另一端接收消息。
// 17-3.c
//使用UNIX域套接字轮询 XSI消息队列
#include "apue.h"
#include "myerror.h"
#include <pthread.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/poll.h>
#define NQ 3 /* number of queues */
#define MAXMSZ 512 /* maximum message size */
#define KEY 0x123 /* key for first message queue */
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");
printf("\n");
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);
}
在main函数中创建了一些消息队列和UNIX域套接字,并为每个消息队列开启了一个线程。然后它在一个无线循环中用poll来论询选择一个套接字端点。当某个套接字可读时,程序从套接字中读取数据并把消息打印到标准输出上。
//17-4.c
//给XSI消息队列发送消息
//程序需要两个参数: 消息队列关联的键值以及一个包含消息主体的字符串。
#include "apue.h"
#include "myerror.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);
}
测试结果: