UNIX域的协议族是在同一台主机上的客户/服务器通信时使用的一种方法。相对其他方法(例如进程间通信的管道),它在形式上与传统套接字API的调用方法相同。UNIX域有两种类型的套接字:字节流套接字和数据报套接字。
UNIX域有如下特点:
l UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍。
l UNIX域套接字可以在同一台主机上各进程之间传递描述符。
l UNIX域套接字与传统套接字的区别是用路径名来表示协议族的描述。
1、 UNIX域函数的地址结构
UNIX域的地址结构在文件<linux/un.h>中定义,结构的原型如下:
#define UNIX_PATH_MAX 108
Struct sockaddr_un{
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX];
};
UNIX域地址结构成员变量sun_family的值是AF_UNIX或者AF_LOCAL。
Sun_path是一个路径名,此路径名的属性为0777,可以进行读写等操作。
2、 套接字函数
UNIX域的套接字函数和以太网套接字(AF_INET)的函数相同,但是当用于UNIX域套接字时,套接字函数有一些差别和限制:
q 使用函数bind()进行套接字和地址的绑定的时候,地址结构中的路径名和路径所表示的文件的默认访问权限为0777,即用户、用户所属的组和其他组的用户都能读、写和执行。
q 结构sum_path中的路径名必须是一个绝对路径,不能是相对路径。
q 函数connect()使用的路径名必须是一个绑定在某个已打开的UNIX域套接字上的路径名,而且套接字的类型也必须一致。下列情况将出错:
n 该路径名存在但不是一个套接字;
n 路径名存在且是一个套接口,但没有与该路径名相关联的打开的描述字;
n 路径名存在且是一个打开的套接字,但类型不符。
q 用函数connect()连接UNIX域套接字时的权限检查和用函数open()以只写方式访问路径名完全相同。
q UNIX域字节流套接字的connect()函数发现监听套接字的队列已满,会立刻返回一个ECONNERFUSED错误。这和TCP有所有不同:如果监听套接字的队列已满,它将忽略到来的SYN,TCP连接的发起方会接着发送几次SYN重试。
q UNIX域数据报套接字和UDP套接字类似:它们都提供一个保留记录边界的不可靠的数据服务。
q 与UDP套接字不同的是,在未绑定的UNIX域套接字上发送数据报不会给它捆绑一个路径名。这意哨声着,数据报发送者除非绑定一个路径名,否则接收者无法发回应应答数据报。同样,与TCP和UDP不同的是,给UNIX域数据报套接字调用connect()不会捆绑一个路径名。
3、 使用UNIX域函数进行套接字编程
使用UNIX域函数进行套接字编程与AF_INET的方式一致,不同的地方在于地址结构不同。
传统本地地址的地址名空间为文件系统路径名。一个进程也许会用任何可用的路径名来命名他的本地套接口。然则为了可用,命名套接口的进程必须可以访问路径名的所有目录组件,并且有权限来在指定的目录中创建最终的套接口对象。
传统AF_UNIX套接口名字的麻烦之一就在于总是调用文件系统对象。这不是必须的,而且也不方便。如果原始的文件系统对象并没有删除,而在bind调用时使用相同的文件名,名字赋值就会失败。Linux 2.2内核使得为本地套接口创建一个抽象名了成为可能。他的方法就是使得路径名的第一个字节为一个空字节。在路径名中空字节之后的字节才会成为抽象名字的一部分。
格式化抽象本地地址的方式需要将路径名的第一个字符设置为空字符,即“/0”。格式化抽象本地地址的方式举例如下:
strcpy(addr_UNIX.sun_path,”/demo/path”);
addr_UNIX.sun_path[0] = ‘/0’;
4、 传递文件描述符
在进程之间经常遇到需要在各进程之间传递文件描述符的情况,例如有一种设备它在加电期间只以打开一次,如果关闭后再次打开就会发生错误。这时就需要有一个调度程序,它调度多个相同设备,当有客户端需要此类型的设备时会向它发送一个请求,服务器会把某个设备的描述会给客户端。但是,由于不同进程之间的文件描述符所表示的对象是不同的,这需要一种特殊的机制来实现上述的要求。
Linux系统中提供了一种特殊的方法,可以从一个进程中将一个已经打开的文件描述符传递给其他的任何进程。其基本过程如下:
(1) 创建一个字节流或者数据报的UNIX域套接字。
q 如果目标是fock()一个子进程,让子进程打开描述符并将它返回给父进程,那么父进程可以用socketpair()创建一个流管道,用它来传递描述字。
q 如果进程之间没有亲缘关系,那么服务器必须创建一个UNIX域字节流套接字,绑定一个路径名,让客户连接到这个套接字然后客户端可以向服务器发送一个请求以打开某个描述字,服务器将描述符通过UNIX域套接字传回。
(2) 进程可以用任何返回描述符的UNIX函数打开一个描述符:例如open()、pipe()、mkfifo()、socket()或者accept()。可以在进程间传递任何类型的描述符。
(3) 发送进程建立一个msghdr结构,其中包含要传递的描述符。发送进程调用snedmsg()通过第一步得到的UNIX域套接字发出套接字。这时这个描述符是在飞行中的。即使在发送进程调用sendmsg()之后,但在接受进程调用recvmsg()之前将描述符关闭,它仍会为接收进程保持打开状态。描述符的发送导师致它的访问统计数加1。
(4) 接收进程调用recvmsg()在UNIX域套接字上接收套接字。通常接收进程收到的描述符的编号和发送进程中的描述符的编号不同,但这没有问题。传递描述符不是传递描述符的编号,而是在接收进程中建立一个新的描述符,指向内核的文件给中与发送进程发送的描述符相同的项。
5、 socketpair()函数
建立一对匿名的已经连接的套接字,其特性由协议族d、类型type、协议protocol决定,建立的两个套接字描述符会放在sv[0]和sv[1]中。
Socketpair()建立的通道与pipe()建立的通道相区别的是,前者创建的通道是双向的,即每一端都可以进行读写。
6、 传递文件描述符的事例代码