Unix 域套接字概述

阅读更多
        Unix 域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用的 API 就是在不同主机上执行客户/服务器通信所用的套接字 API,可视为进程间通信(IPC)方法之一(POSIX 也把 Unix 域协议称为“本地 IPC”)。
        Unix 域提供了两类套接字:字节流套接字(类似 TCP)和数据报套接字(类似 UDP)。尽管也提供原始套接字,不过不曾见过使用。使用 Unix 域套接字有以下 3 个理由。
        (1)Unix 域套接字往往比位于同一个主机的 TCP 套接字快一倍。X Window System 就发挥了这个优势。当一个 X11 客户启动并打开到 X11 服务器的连接时,该客户会检查 DISPLAY 环境变量的值,其中指定了服务器的主机名、窗口和屏幕。如果服务器与客户处于同一个主机,客户就打开一个到服务器的 Unix 域字节流连接,否则打开一个 TCP 连接。
        (2)Unix 域套接字可用于同一个主机上的不同进程之间传递描述符。
        (3)Unix 域套接字较新的实现把客户的凭证(用户 ID 和组 ID)提供给服务器,从而能够提供额外的安全检查措施。
        Unix 域中用于标识客户和服务器的协议地址是普通文件系统中的路径名,不过这些路径名不是普通的 Unix 文件:除非把它们关联到 Unix 域套接字,否则无法读写这些文件。
        Unix 域套接字地址结构如下。
#include 

struct sockaddr_un{
    sa_family_t  sun_family;      // AF_LOCAL
    char         sun_path[104];   // null-terminated pathname
};

        其中要注意的是,存放在 sun_path 数组中的路径名必须以空字符结尾。此外,因历史原因,虽然这里使用 104 来指明了 sun_path 的大小,但 POSIX 规范并没有明确规定 sun_path 的长度,因此应用程序应该在运行时使用 sizeof 运算符来计算 sockaddr_un 结构的大小。实现提供的 SUN_LEN 宏以一个指向 sockaddr_un 结构的指针为参数,并返回该结构的长度,其中包括路径名中的非空字节数。未指定地址通过以空字符串作为路径名指示,即 sun_path[0] 值为 0 的地址结构,它等价于 IPv4 的 INADDR_ANY 常值以及 IPv6 的 IN6ADDR_ANY_INIT 常值。
        下面这个示例创建一个 Unix 域套接字,往其上 bind 一个路径名,再调用 getsockname 输出这个绑定的路径名。
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct sockaddr	SA;

int main(int argc, char **argv){
	if(argc != 2){
		printf("Usage: unixBind \n");
		exit(2);
	}
	unlink(argv[1]);		// OK if this fails

	struct sockaddr_un	addr1, addr2;
	bzero(&addr1, sizeof(addr1));
	addr1.sun_family = AF_LOCAL;
	strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1);
	int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
	bind(sockfd, (SA *)&addr1, SUN_LEN(&addr1));

	socklen_t len = sizeof(addr2);
	getsockname(sockfd, (SA *)&addr2, &len);
	printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
	
	exit(0);
}

        程序运行结果如下:
$ umask
0022
$ ./unixBind /tmp/moose
bound name = /tmp/moose, returned len = 13
$ 
$ ./unixBind /tmp/moose                         # 再运行一次
bound name = /tmp/moose, returned len = 13
$ 
$ ls -l /tmp/moose
srwxr-xr-x. 1 lei root 0 3月  12 00:37 /tmp/moose
$ 
$ ls -lF /tmp/moose
srwxr-xr-x. 1 lei root 0 3月  12 00:37 /tmp/moose=
$ 

        创建的 Unix 域套接字的文件类型显示为“s”(有些系统把 Unix 域套接字视为 FIFO,从而显示为 p)。
        为了方便,Unix 域套接字提供了 socketpair 函数,它可以创建两个随后连接起来的套接字。
#include 
int socketpair(int family, int type, int protocol, int sockfds[2]);
                                    /* 返回值:若成功则为 0,否则为 -1 */

        这里,family 参数必须为 AF_LOCAL,protocol 参数必须为 0。type 参数可以是 SOCK_STREAM,也可以是 SOCK_DGRAM。sockfds 数组里就是保存的新创建的两个套接字描述符。
        这样创建的两个套接字不曾命名,也就是说其中没有设计隐式的 bind 调用。指定 type 参数为 SOCK_STREAM 得到的结果称为流管道,它与调用 pipe 创建的普通 Unix 管道类似,差别在于流管道是全双工的,即两个描述符都是既可读又可写的,而 POSIX 并不要求 pipe 返回两个全双工的描述符。
        虽然 Unix 域套接字用的也是套接字 API 函数,不过其中也还是存在如下一些差异和限制。
        (1)由 bind 创建的路径名默认访问权限应为 0777,并按照当前的 umask 值进行修正。
        (2)与 Unix 域套接字关联的路径名应该是一个绝对路径名,因为使用相当路径的话,对它的解析依赖于调用者的当前工作目录。
        (3)在 connect 调用中指定的路径名必须是一个当前已经绑定在某个打开的 Unix 域套接字上的路径名,而且它们的套接字类型(字节流或数据报)也必须一致(即 Unix 域字节流套接字不能连接到与 Unix 域数据报套接字关联的路径名,反之亦然)。
        (4)调用 connect 连接一个 Unix 域套接字涉及的权限测试等同于调用 open 以只写方式访问相应的路径名。
        (5)Unix 域字节流套接字类似于 TCP 套接字:它们都为进程提供一个无记录边界的字节流接口。
        (6)如果对于某个 Unix 域字节流套接字的 connect 调用发现该监听套接字的队列已满,调用就立即返回一个 ECONNREFUSED 错误。这一点不同于 TCP:如果 TCP 监听套接字的队列已满,TCP 监听端就忽略新到达的 SYN,而 TCP 连接发起端将数次发送 SYN 进行重试。
        (7)Unix 域数据报套接字类似于 UDP 套接字:它们都提供一个保留记录边界的不可靠的数据报服务。
        (8)在一个未绑定的 Unix 域套接字上发送数据报不会自动给这个套接字捆绑一个路径名,这一点不同于 UDP 套接字:在一个未绑定的 UDP 套接字上发送 UDP 数据报导致给这个套接字捆绑一个临时端口。这意味着除非数据报发送端已经捆绑一个路径名到它的套接字,否则数据报接收端无法发回应答数据报。类似地,对于某个 Unix 域数据报套接字的 connect 调用不会给本套接字捆绑一个路径名,这一点不同于 TCP 和 UDP。

你可能感兴趣的:(网络编程,套接字,UNIX域套接字)