参考:《UNIX 网络编程 · 卷1 : 套接字联网API》
概述
Unix域套接字不是一个协议族,是单个主机上进程间通信的一种方法。它提供了两类套接字:字节流套接字和数据报套接字,类似于TCP和UDP套接字。
虽然socket套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据,它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。
Unix域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。
Unix域中用于标识客户端和服务器协议地址是普通文件系统的路径名。但这些路径名不是普通的Uinx文件,除非把他们和Unix套接字关联起来,否则无法读写这些文件。
Unix 域套接字地址结构
在头文件<sys/un.h>
中定义的Unix域套接字地址结构如下:
struct sockaddr_un
{
sa_family_t sun_family; //AF_LOCAL
char sun_path[104]; //null-terminated pathname
}
sun_path中的路径名必须以\0
结尾。
如下简单的示例:
int main(int argc, char **argv)
{
int sockfd;
socklen_t len;
struct sockaddr_un addr1, addr2;
if (argc != 2)
{
printf("argc not is 2.");
return 0;
}
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
unlink(argv[1]);
bzero(&addr1, sizeof(addr1));
addr1.sun_family = AF_LOCAL;
strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1);
bind(sockfd, (struct sockaddr *)&addr1, SUN_LEN(&addr1));
len = sizeof(addr2);
getsockname(sockfd, (struct sockaddr *)&addr2, &len);
printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
return 0;
}
调用bind捆绑到套接字上的路径就是命令行参数,如果已存在bind就会失败,所以先使用unlink删除这个路径名。
运行结果:
crazyang@zt-2013412:~/UNP$ ./sockaddr /tmp/moose
bound name = /tmp/moose, returned len = 13
crazyang@zt-2013412:~/UNP$ ls -l /tmp/moose
srwxrwxrwx 1 crazyang crazyang 0 Jan 1 22:09 /tmp/moose #显示为s的套接字
socketpair 函数
为了创建一对非命名的、相互连接的UNIX域套接字,用户可以使用它们面向网络的域套接字接口,也可使用socketpair函数。
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
//返回值:若成功则返回0,出错则返回-1
参数:
family:必须为AF_INET。
protocol:必须为0。
type:既可以是SOCK_STREAM,也可以是SOCK_DGRAM。
sockfd:新建的两个套接字描述符作为sockfd[0]和sockfd[1]返回。
本函数类似于Unix的pipe函数,返回两个彼此连接的描述符。当type为SOCK_STREAM时调用socketpair得到的结果成为流管道。与调用pipe创建的普通Unix管道类似,但是流管道是全双工的,即两个描述符都是可读可写的。
基本用法:
-
这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读。
-
如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功。
-
读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。
示例程序:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdlib.h>
const char* str = "SOCKET PAIR TEST.";
int main(int argc, char* argv[]){
char buf[128] = {0};
int socket_pair[2];
pid_t pid;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1 ) {
printf("Error, socketpair create failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
}
pid = fork();
if(pid < 0) {
printf("Error, fork failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
} else if(pid > 0) {
//关闭另外一个套接字
close(socket_pair[1]);
int size = write(socket_pair[0], str, strlen(str));
printf("Write success, pid: %d\n", getpid());
} else if(pid == 0) {
//关闭另外一个套接字
close(socket_pair[0]);
read(socket_pair[1], buf, sizeof(buf));
printf("Read result: %s, pid: %d\n",buf, getpid());
}
for(; ;) {
sleep(1);
}
return EXIT_SUCCESS;
}
Unix 域套接字的差异
- bind创建的路径名默认访问权限为0777。
- 与Unix套接字关联的应该是一个绝对路径名而不是一个相对路径名。
- connect调用中指定的路径名必须是一个当前绑定在某个打开的Unix域套接字上的路径名,而且它们的套接字类型必须一致。出错条件包括:1.该路径名已存在却不是一个套接字。2.该路径名已存在且是一个套接字但没有与之关联的打开的描述符。3.路径名类型不符合。
- 调用connect连接一个Unix域套接字涉及的权限测试等同于调用open以只写的方式访问相应的路径名。
- 如果某个域字节流套接字的connect调用发现这个监听套接字队列已满,调用就立即返回一个ECONNERFUSED错误。不同于TCP发起端会数次发送SYN进行重试。
- Unix套接字类似于TCP套接字,他们都为进程提供一个无记录边界的字节流接口。
- Unix域数据报套接字类似于UDP套接字,它们都提供一个保留记录边界的不可靠的数据报服务。
- 在一个未绑定的Unix域套接字上发送数据报不会自动给这个套接字捆绑一个路径名,这一点不同于UDP套接字。对于某个Unix域数据报套接字的connect调用不会给本套接字捆绑一个路径名。