概述

UNIX域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用API与在不同主机上执行客户/服务器通信所用的API(套接口API)相同。UNIX域协议可视为进程间通信(IPC)方法之一。

UNIX域提供两类套接口:字节流套接口(类似TCP)和数据报套接口(类似UDP)。

使用UNIX域套接口的理由有3个:

在源自Berkeley的实现中,UNIX域套接口往往比通信两端位于同一主机的TCP套接口快出一倍。

UNIX域套接口可用于在同一个主机上的不同进程间传递描述字。

UNIX域套接口较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施。

UNIX域中用于标识客户和服务器的协议地址是普通文件系统中的路径名。这些路径名不是普通的UNIX文件:除非把它们和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数组中的路径名必须以空格字符结尾。

实现提供的SUN_LEN宏以一个指向sockaddr_un结构的指针为参数并返回该结构的长度,其中包括路径名中非空字节数。

未指定地址(通配地址),通过以空字符串作为路径名指示,也就是一个sun_path[0]值为0的地址结构。这是UNIX域中与IPv4的INADDR_ANY常值以及IPv6的IN6ADDR_ANY_INIT常值等价的一个地址。

POSIX把UNIX域协议重新命名为“本地IPC”,以消除它对于UNIX操作系统的依赖。历史性的AF_UNIX常值变为AF_LOCAL。尽管POSIX努力使它独立于操作系统,它的套接口地址结构仍然保留_un后缀。

实例:UNIX域套接口的bind调用

创建一个UNIX域套接口,往其上bind一个路径名,再调用getsockname输出这个绑定的路径名。

#include <sys/un.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int
main(int argc, char **argv)
{
int sockfd;
socklen_t len;
struct sockaddr_un addr1, addr2; if(argc != 2)
{
printf("usage: unixbind <pathname> ");
exit(0);
}
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
unlink(argv[1]); /* 如果文件系统中已存在该路径名,bind将会失败。为此我们先调用unlink删除这个路径名,以防止它已经存在。 */
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); exit(0);
}

运行结果如下:

socketpair函数

socketpair函数创建两个随后连接起来的套接口。本函数仅适用于UNIX域套接口。

#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);
返回:0——成功,-1——出错

family参数必须为AF_LOCAL;

protocol参数必须为0;

type参数可以是SOCK_STREAM,也可以是SOCK_DGRAM。

新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回。

本函数类似于UNIX的pipe函数:返回两个彼此连接的描述字。事实上,源自berkeley的实现通过执行与socketpair一样的内部操作给出pipe接口。

这样创建的两个套接口不曾命名;也就是说其中没有涉及隐式的bind调用。它与调用pipe创建的普通UNIX管道类似,差别在于流管道(socketpair创建的)是全双工的,即两个描述字都是既可读又可写。

POSIX不要求全双工管道。

套接口函数

当用于UNIX域套接口时,套接口函数中存在一些差异和限制:

由bind创建的路径名缺省访问权限应为0777(属主用户、组用户和其他用户都可读、可写并可执行),并按照当前umask值进行修正。

与UNIX域套接口关联的路径名应该是一个绝对路径名,而不是一个相对路径名。

在connect调用中指定的路径名必须是一个当前捆绑在某个打开的UNIX域套接口上的路径名,而且它们的套接口类型(字节流或数据报)也必须一致。

调用connect连接一个UNIX域套接口涉及的权限测试等同于调用open以只读方式访问相应的路径名。

UNIX域字节流套接口类似于TCP套接口:它们都为进程提供一个无记录边界的字节流接口。

如果对于某个UNIX域字节流套接口的connect调用发现这个监听套接口的队列已满,调用就立即返回一个ECONNREFUSED错误。这一点不同于TCP:如果TCP监听套接口的队列已满,TCP监听端就忽略新到达的SYN,而TCP连接发起端将数次发送SYN进行重试。

UNIX域数据报套接口类似UDP套接口:它们都提供一个保留记录边界的不可靠的数据报服务。

在一个未绑定的UNIX域套接口上发送数据报不会自动给这个套接口捆绑一个路径名,这一点不同于UDP套接口:在一个未绑定的UDP套接口上发送UDP数据报导致给这个套接口捆绑一个临时端口。这一点意味着除非数据报发送端已经捆绑一个路径名到它的套接口,否则数据报接收端无法发回应答数据报。类似地,对于某个UNIX域数据报套接口的connect调用不会给本套接口绑定一个路径名,这一点不同于TCP和UDP。

UNIX域字节流客户/服务器程序

/* unixstrserv01.c */

#include <sys/un.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#define UNIXSTR_PATH "/tmp/unix.str" int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
void sig_chld(int); daemonize("unixstrserver");
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0); unlink(UNIXSTR_PATH);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXSTR_PATH); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 5);
signal(SIGCHLD, sig_chld); for(;;)
{
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
{
if(errno == EINTR)
continue; /* back to for() */
else
{
perror("accept");
exit(1);
}
} if((childpid = fork()) == 0)
{
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
} void
sig_chld(int signo)
{
pid_t pid;
int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("child %d terminated\n", pid);
} return;
}
/* unixstrcli01.c */

#include <sys/un.h>
#include <strings.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h> #define UNIXSTR_PATH "/tmp/unix.str" int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr; sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXSTR_PATH); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
exit(0);
}

其他相关使用到的函数参见:http://www.cnblogs.com/nufangrensheng/p/3587962.html 以及http://www.cnblogs.com/nufangrensheng/p/3544104.html

UNIX域数据报客户/服务器程序

/* unixdgserv01.c */

#include <sys/un.h>
#include <sys/socket.h> #define UNIXDG_PATH "/tmp/unix.dg" int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr, cliaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); unlink(UNIXDG_PATH);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXDG_PATH); bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); dg_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
}
/* unixdgcli01.c */

#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h> #define UNIXDG_PATH "/tmp/unix.dg"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un cliaddr, servaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sun_family = AF_LOCAL;
strcpy(cliaddr.sun_path, tmpnam(NULL)); bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXDG_PATH); dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); exit(0);
}

使用到的相关函数可参考:http://www.cnblogs.com/nufangrensheng/p/3592158.html

注意与UDP客户不同的是,当使用UNIX域数据报协议时,我们必须显式bind一个路径名到我们的套接口,这样服务器才会有回送应答的路径名

描述字传递

当考虑从一个进程到另一个进程传递打开的描述字时,我们通常会想到:

(1)fork调用返回后,子进程共享父进程的所有打开的描述字。

(2)exec调用执行之后,所有描述字通常保持打开状态不变。

在(1)中,进程先打开一个描述字,再调用fork,然后父进程关闭这个描述字,子进程则处理这个描述字。这样一个打开的描述字就从父进程传递到子进程。然而我们也可能想让子进程打开一个描述字并把它传递给父进程。

当前的UNIX系系统提供了用于从一个进程到任一其他进程传递任一打开的描述字的方法。也就是说,这两个进程之间无需存在亲缘关系。这种技术要求首先在这两个进程之间创建一个UNIX域套接口,然后使用sendmsg跨这个UNIX域套接口发送一个特殊消息。这个消息由内核处理,从而把打开的描述字从发送进程传递到接收进程。使用UNIX域套接口的描述字传递方法是最便于移植的编程技术。

在两个进程之间传递描述字涉及的步骤如下:

(1)创建一个字节流或数据报的UNIX域套接口。

如果目标是fork一个子进程,让子进程打开待传递的描述字,再把它传递回父进程,那么父进程可以预先调用socketpair创建一个可用于在父子进程之间交换描述字的流管道。

如果进程之间没有亲缘关系,那么服务器进程必须创建一个UNIX域字节流套接口,bind一个路径到该套接口,以允许客户进程connect到该套接口。客户然后可以向服务器发送一个打开某个描述字的请求,服务器再把该描述字通过UNIX域套接口传递回客户。客户和服务器之间也可以使用UNIX域数据报套接口,不过这么做缺乏优势,而且数据报存在被丢弃的可能性。

(2)发送进程通过调用返回描述字的任一UNIX函数打开一个描述字,这些函数的例子有:open、pipe、mkfifo、socket和accept。可以在进程之间传递的描述字不限类型,这就是我们称这种技术为“描述字传递”而不是“文件描述字传递”的原因。

(3)发送进程创建一个msghdr结构(http://www.cnblogs.com/nufangrensheng/p/3567376.html),其中含有待传递的描述字。POSIX规定描述字作为辅助数据(msghdr结构的msg_control成员)发送。发送进程调用sendmsg跨来自步骤(1)的UNIX域套接口发送该描述字。至此我们说这个描述字“在飞行中(in flight)”。即使发送进程在调用sendmsg之后但在接收进程调用recvmsg之前就关闭了该描述字,对于接收进程它仍然保持打开状态。发送一个描述字导致该描述字的引用计数加1.

(4)接收进程调用recvmsg在来自步骤(1)的UNIX域套接口上接收这个描述字。这个描述字在接收进程中的描述字号不同于它在发送进程中的描述子号是正常的。传递一个描述字并不是传递一个描述字号,而是涉及在接收进程中创建一个新的描述字,而这个描述字指引的内核中文件表项和发送进程中飞行前的那个描述字指引的相同。

客户和服务器之间必须存在某种应用协议,以便描述字的接收进程预先知道何时期待接收。另外,在期待接收描述字的recvmsg调用中应该避免使用MSG_PEEK标志,否则后果不可预料。

UNIX网络编程读书笔记:UNIX域协议的更多相关文章

  1. UNIX网络编程--读书笔记

    会集中这段时间写UNIX网络编程这本书的读书笔记,准备读三本,这一系类的文章会不断更新,一直会持续一个月多,每篇的前半部分是书中讲述的内容,每篇文章的后半部分是自己的心得体会,文章中的红色内容是很重要 ...

  2. UNIX网络编程读书笔记:简介

    认知套接口编程接口 理解原始套接口(raw socket)的概念   值得注意的是,客户和服务器是典型的用户进程,而TCP和IP协议则通常是系统内核协议栈的一部分. 上图中在TCP和UDP之间留有间隙 ...

  3. Unix 网络编程 读书笔记1

    第一章: C/C++语言提供两种不同的编程模式:IPL32和PL64.► IPL32 ● 表示integer/pointer/long三种数据类型是32位(4个字节),在这种模式下,提供32位的地址空 ...

  4. UNIX网络编程读书笔记:原始套接口

    概述 应用程序可以绕过传输层而直接使用IPv4和IPv6,这称为原始套接口(raw socket).http://www.cnblogs.com/nufangrensheng/p/3583435.ht ...

  5. UNIX网络编程读书笔记:套接口选项

    概述 有很多方法来获取和设置影响套接口的选项: getsockopt和setsockopt函数 fcntl函数 ioctl函数 getsockopt和setsockopt函数 这两个函数仅用于套接口. ...

  6. UNIX网络编程读书笔记:基本TCP套接口编程

    编写一个完整的TCP客户和服务器程序所需要的基本套接口函数: 1.socket函数(客户端.服务器端都必须调用) 参数family指明协议族(family),该参数也往往被称为协议域(domain). ...

  7. UNIX网络编程读书笔记:I/O模型(阻塞、非阻塞、I/O复用、信号驱动、异步)

    I/O模型 UNIX下可用的5种I/O模型: (1)阻塞I/O (2)非阻塞I/O (3)I/O复用(select和poll) (4)信号驱动I/O(SIGIO) (5)异步I/O 对于一个套接口上的 ...

  8. Unix 网络编程 读书笔记2

    第三章 套接字编程简介 每一个 Socket 都用一个半相关描述:{协议,本地地址,本地端口}一个完整的 Socket 则用一个相关描述{协议,本地地址,本地端口,远程地址,远程端口}每一个 Sock ...

  9. UNIX网络编程读书笔记:端口号、套接口对和套接口

    端口号 端口号(port number):16位整数,用来区分不同的进程. 服务器使用的端口号:TCP和UDP定义了一组众所周知的端口(well-known port),用于标识众所周知的服务. 客户 ...

随机推荐

  1. CSS 笔记——列表表格

    6. 列表表格 -> 列表 (1)list-style 基本语法 list-style : list-style-image || list-style-position || list-sty ...

  2. luogu P4115 Qtree4

    题目链接 luogu P4115 Qtree4 题解 动态点分治,和上一题一样.同样三个堆.就是带权,用边权替换深度就好 为什么要单独写这个题解呢,因为我卡常卡了一天....据说树剖比rmq快? 在第 ...

  3. 51nod 1412 AVL树的种类

    非常简单的一道题,一眼题 枚举左儿子大小,再枚举深度即可 复杂度$O(n^2 log n)$ #include <cstdio> #include <cstring> #inc ...

  4. [BZOJ4651][NOI2016]网格(Tarjan)

    下面直接给出结论,相关证明见官方题解. 1.若跳蚤数不超过1或仅有两只跳蚤且相邻,则答案为-1. 2.若跳蚤形成的连通块个数大于1,则答案为0. 3.若跳蚤之间建图存在割点,则答案为1. 4.否则为2 ...

  5. [BZOJ 4071] 巴邻旁之桥

    Link: BZOJ 4071传送门 Solution: 首先算出能提前算的贡献 $K=1$:肯定选中间的点,小学数学 $K=2$:对于每对$(x,y)$一定选离$(x+y)/2$近的桥 也就是说将$ ...

  6. lightoj 1306 - Solutions to an Equation 扩展的欧几里得

    思路:看题就知道用扩展的欧几里得算法做!!! 首先我们可以求出ax+by=gcd(a,b)=g的一个组解(x0,y0).而要使ax+by=c有解,必须有c%g==0. 继而可以得到ax+by=c的一个 ...

  7. Python turtle绘图实例分析

    画一个红色的五角星 from turtle import * color('red','red') begin_fill() for i in range(5): fd(200) rt(144) en ...

  8. Miller-Rabin算法 codevs 1702 素数判定 2

    转载自:http://www.dxmtb.com/blog/miller-rabbin/ 普通的素数测试我们有O(√ n)的试除算法.事实上,我们有O(slog³n)的算法. 定理一:假如p是质数,且 ...

  9. 某DP题目2

    题意: 有一个栈,有n个数1~n按顺序插进栈中,但弹出顺序不定.另有m个限制,表示为a b,即数a必须在数b弹出之前弹出.问有多少种弹出的方案数.n <= 300,m <= 90000 分 ...

  10. HDU 3974 Assign the task 并查集/图论/线段树

    Assign the task Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?p ...