Unix/Linux编程:Unix domain socket
发布时间:2022-12-21 11:05:10 所属栏目:Unix 来源:
导读: UNIX domain socket用于同一主机系统上的相互通信
UNIX domain socket 地址:struct sockaddr_un
在Unix domain中,socket地址以路径名来表示,domain特定的socket地址结构的定义如下所示:
UNIX domain socket 地址:struct sockaddr_un
在Unix domain中,socket地址以路径名来表示,domain特定的socket地址结构的定义如下所示:
UNIX domain socket用于同一主机系统上的相互通信 UNIX domain socket 地址:struct sockaddr_un 在Unix domain中,socket地址以路径名来表示,domain特定的socket地址结构的定义如下所示: struct sockaddr_un{ sa_family_t sun_family; // always AF_UNIX char sun_path[128]; }; sockaddr_un 结构中字段的 sun_前缀与 Sun Microsystems 没有任何关系,它是根据 socket unix 而来的。 为将一个Unix domain socket绑定到一个地址上,需要初始化一个sockaddr_un结构,然后将指定这个结构的一个指针作为addr参数bind()并将addrlen指定为这个结构的大小。如下所示: const char *SV_SOCK_PATH = "/tmp/mysock"; int sfd; struct sockaddr_un addr; // 绑定一个 UNIX domain socket sfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sfd == -1){ perror("socket"); exit(EXIT_FAILURE); } memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1); if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) { printf("bind"); exit(EXIT_FAILURE); } 当用来绑定 UNIX domain socket 时,bind()会在文件系统中创建一个条目(因此作为socket路径名的一部分目录需要可访问和可读写)。文件的所有权将根据常规的文件创建规则来确定。这个文件会被标记为一个socket。当在这个路径名上stat()时,它会在stat结构的st_mode字段中的文件类型部分返回值S_IFSOCK。当使用ls –l列出时,UNIX domain socket 在第一列将会显示类型 s,而 ls –F 则会在 socket 路径名后面附加上一个等号(=)。 有关绑定一个 UNIX domain socket 方面还需要注意以下几点 UNIX domain 中的流 socket 下面是一个简单的 UNIX domain 流 socket 服务器 (这个服务器是一个简单的迭代式服务器——服务器在处理下一个客户端之前一次只处理一个客户端) // us_xfr.h #include #define BACKLOG 5 #define SV_SOCK_PATH "/tmp/us_xfr" #define BUF_SIZE 100 int main(int argc, char *argv[]) { struct sockaddr_un addr; int sfd, cfd; ssize_t numRead; char buf[BUF_SIZE]; // 创建一个 socket。 sfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sfd == -1){ perror("socket"); exit(EXIT_FAILURE); } if (strlen(SV_SOCK_PATH) > sizeof(addr.sun_path) - 1){ printf("Server socket path too long: %s", SV_SOCK_PATH); exit(EXIT_FAILURE); } // 删除所有与路径名一致的既有文件,这样才能将 socket 绑定到这个路径名上 if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT){ printf("remove-%s", SV_SOCK_PATH); exit(EXIT_FAILURE); } //为服务器 socket 构建一个地址结构 memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1); //将 socket 绑定到该地址上 if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) { printf("bind"); exit(EXIT_FAILURE); } // 将这个 socket 标记为监听 socket。 if (listen(sfd, BACKLOG) == -1){ printf("listen"); exit(EXIT_FAILURE); } // 执行一个无限循环来处理进入的客户端请求。每次循环迭代执行下列任务。 for (;;) { /* Handle client connections iteratively */ /* Accept a connection. The connection is returned on a new socket, 'cfd'; the listening socket ('sfd') remains open and can be used to accept further connections. */ // 接受一个连接,为该连接获取一个新 socket cfd。 cfd = accept(sfd, NULL, NULL); if (cfd == -1) { printf("accept"); exit(EXIT_FAILURE); } /* Transfer data from connected socket to stdout until EOF */ //从已连接的 socket 中读取所有数据并将这些数据写入到标准输出中。 while ((numRead = read(cfd, buf, BUF_SIZE)) > 0) if (write(STDOUT_FILENO, buf, numRead) != numRead) { printf("partial/failed write"); exit(EXIT_FAILURE); } if (numRead == -1) { printf("read"); exit(EXIT_FAILURE); } // 关闭已连接的 socket cfd if (close(cfd) == -1) { printf("close"); exit(EXIT_FAILURE); } } } 下面是一个简单的 UNIX domain 流 socket 客户端 int main(int argc, char *argv[]) { struct sockaddr_un addr; int sfd; ssize_t numRead; char buf[BUF_SIZE]; // 创建一个 socket。 sfd = socket(AF_UNIX, SOCK_STREAM, 0); /* Create client socket */ if (sfd == -1){ perror("socket"); exit(EXIT_FAILURE); } /* Construct server address, and make the connection */ // 为服务器 socket 构建一个地址结构并连接到位于该地址处的 socket。 memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1); if (connect(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) { perror("connect"); exit(EXIT_FAILURE); } /* Copy stdin to socket */ //执行一个循环将其标准输入复制到 socket 连接上。当遇到标准输入中的文件结尾时客 // 户端就终止,其结果是客户端 socket 将会被关闭并且服务器在从连接的另一端的 // socket 中读取数据时会看到文件结束。 while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) if (write(sfd, buf, numRead) != numRead) { printf("partial/failed write"); exit(EXIT_FAILURE); } if (numRead == -1) { printf("read"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); /* Closes our socket; server sees EOF */ } 使用效果:首先在后台运行服务器。 然后创建一个客户端用作输入的测试文件并运行客户端。 此刻子进程已经结束了。现在终止服务器并检查服务器的输出是否与客户端的输入匹配。 diff 命令没有产生任何输出unix进程通信,表示输入和输出文件是一致的。 注意在服务器终止之后,socket 路径名会继续存在。这就是为何服务器在调用 bind()之前使用 remove()删除 socket 路径名的所有既有实例。 UNIX domain 中的数据报 socket 一般来说,使用数据报socket的通信是不可靠的,但这个论断适用于通过网络传输的数据报。对于Unix domin socket来讲,数据报的传输是在内核中发送的,并且也是可靠的,所有的消息都会按照顺序被传递而且也不会发生重复情况 下面是一个简单的 UNIX domain 数据报服务器 程序 // ud_ucase_sv #include #include #include #define BUF_SIZE 10 /* Maximum size of messages exchanged between client and server */ #define SV_SOCK_PATH "/tmp/ud_ucase" int main(int argc, char *argv[]) { struct sockaddr_un svaddr, claddr; int sfd, j; ssize_t numBytes; socklen_t len; char buf[BUF_SIZE]; // 先创建一个 socket sfd = socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */ if (sfd == -1){ perror("socket"); exit(EXIT_FAILURE); } if (strlen(SV_SOCK_PATH) > sizeof(svaddr.sun_path) - 1){ printf("Server socket path too long: %s", SV_SOCK_PATH); exit(EXIT_FAILURE); } //服务器先删除了与该地址匹配的路径名,以防出现这个路径名已经存在的情况 if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT){ printf("remove-%s", SV_SOCK_PATH); exit(EXIT_FAILURE); } // 将其绑定到一个众所周知的地址上 memset(&svaddr, 0, sizeof(struct sockaddr_un)); svaddr.sun_family = AF_UNIX; strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1); if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1) { printf("bind"); exit(EXIT_FAILURE); } /* Receive messages, convert to uppercase, and return to client */ for (;;) { len = sizeof(struct sockaddr_un); numBytes = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len); if (numBytes == -1) { printf("recvfrom"); exit(EXIT_FAILURE); } printf("Server received %ld bytes from %s\n", (long) numBytes, claddr.sun_path); /*FIXME: above: should use %zd here, and remove (long) cast */ for (j = 0; j < numBytes; j++) buf[j] = toupper((unsigned char) buf[j]); if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) != numBytes) { printf("sendto"); exit(EXIT_FAILURE); } } } 下面是一个简单的 UNIX domain 数据报客户端 // ud_ucase_cl #include #include #include #define BUF_SIZE 10 /* Maximum size of messages exchanged between client and server */ #define SV_SOCK_PATH "/tmp/ud_ucase" int main(int argc, char *argv[]) { struct sockaddr_un svaddr, claddr; int sfd, j; size_t msgLen; ssize_t numBytes; char resp[BUF_SIZE]; if (argc < 2 || strcmp(argv[1], "--help") == 0){ printf("%s msg...\n", argv[0]); exit(1); } /* Create client socket; bind to unique pathname (based on PID) */ sfd = socket(AF_UNIX, SOCK_DGRAM, 0); if (sfd == -1) { perror("socket"); exit(EXIT_FAILURE); } memset(&claddr, 0, sizeof(struct sockaddr_un)); claddr.sun_family = AF_UNIX; snprintf(claddr.sun_path, sizeof(claddr.sun_path), "/tmp/ud_ucase_cl.%ld", (long) getpid()); if (bind(sfd, (struct sockaddr *) &claddr, sizeof(struct sockaddr_un)) == -1) { perror("bind"); exit(EXIT_FAILURE); } /* Construct address of server */ memset(&svaddr, 0, sizeof(struct sockaddr_un)); svaddr.sun_family = AF_UNIX; strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1); /* Send messages to server; echo responses on stdout */ for (j = 1; j < argc; j++) { msgLen = strlen(argv[j]); /* May be longer than BUF_SIZE */ if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) != msgLen) { perror("sendto"); exit(EXIT_FAILURE); } numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL); /* Or equivalently: numBytes = recv(sfd, resp, BUF_SIZE, 0); or: numBytes = read(sfd, resp, BUF_SIZE); */ if (numBytes == -1) { perror("recvfrom"); exit(EXIT_FAILURE); } printf("Response %d: %.*s\n", j, (int) numBytes, resp); } remove(claddr.sun_path); /* Remove client socket pathname */ exit(EXIT_SUCCESS); } 下面演示了如何使用服务器和客户端程序。 从客户端程序的第二个调用(有意在 recvfrom()调用中指定了一个比消息更小的 length 值),可以看出消息会被静默地截断 UNIX domain socket 权限 socket文件的所有权和权限绝对了哪些进程能够与这个socket进行通信: 此外,需要在存放 socket 路径名的所有目录上都拥有执行(搜索)权限。 在默认情况下,创建 socket(通过 bind())时会给所有者(用户)、组以及 other 用户赋予所有的权限。要改变这种行为可以在调用 bind()之前先调用 umask()来禁用不希望赋予的权限。 创建互联 socket 对:socketpair() 有时候让单个进程创建一对socket并将它们连接起来是比较有用的。这可以通过使用两个socket()调用和一个bind()调用以及对listen()、connect()、accept()(用于流 socket)的调用或对 connect()(用于数据报 socket)的调用来完成。socketpair()系统调用则为这个操作提供了一个快捷方式。具体请参见socketpair() Linux抽象socket名空间 所谓的抽象路径名空间是Linux特有的一项特性,它允许将一个Unix domain socket绑定到一个名字上但不会在文件系统中创建该名字。这种做法具备几点优势: 要创建一个抽象绑定就需要将sun_path字段的第一个字节指定为NULL字节(\0)。这样就能够将抽象socket名字与传统的Unix domain socket路径名区分开来,因为传统的名字是由一个或者多个非空字节以及终止NULL字节构成的字符串。sun_path字段的余下字节为socket定义了抽象名字。在解释这个名字时需要用到全部字节,而不是将其看成是一个以 null结尾的字符串。 下面演示了如何创建一个抽象 socket 绑定 // us_abstract_bind #include #include int main(int argc, char *argv[]) { int sockfd; struct sockaddr_un addr; char *str; memset(&addr, 0, sizeof(struct sockaddr_un)); /* Clear address structure */ addr.sun_family = AF_UNIX; /* UNIX domain address */ /* addr.sun_path[0] has already been set to 0 by memset() */ str = "xyz"; /* Abstract name is "\0xyz" */ strncpy(&addr.sun_path[1], str, strlen(str)); // In early printings of the book, the above two lines were instead: // // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2); // /* Abstract name is "xyz" followed by null bytes */ sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket"); exit(EXIT_FAILURE); } if (bind(sockfd, (struct sockaddr *) &addr, sizeof(sa_family_t) + strlen(str) + 1) == -1) { perror("bind"); exit(EXIT_FAILURE); } // In early printings of the book, the final part of the bind() call // above was instead: // sizeof(struct sockaddr_un)) == -1) sleep(60); exit(EXIT_SUCCESS); } 使用一个初始null字节来区分抽象socket名和传统的socket名会带来不同寻常的结果。假设变量name正好指向了一个长度为零的字符串并将一个 UNIX domain socket绑定到一个照下列方式初始化 sun_path 的名字上。 strncpy(&addr.sun_path, str, strlen(addr.sun_path) - 1); 在 Linux 上,就会在无意中创建了一个抽象 socket 绑定。但这种代码可能并不是期望中的代码(即一个 bug)。在其他 UNIX 实现中,后续的 bind()调用会失败 总结 Unix domain socket允许位于同一主机上的应用程序之间进行通信。Unix domain支持流和数据报socket Unix domain socket是通过文件系统中的一个路径来标识的。文件权限可以用来控制对Unix domain socket的访问 socketpair()系统调用创建一对相互连接的Unix domain socket。这样就无需调用多个系统调用来创建、绑定以及连接socket。一个socket对的使用方式通常与管道类似:一个进程创建socket对,然后创建一个其引用socket对的描述符的紫禁城,然后这两个进程就能够通过这个socket对进行通信了。 Linux 特有的抽象 socket 名空间允许将一个 UNIX domain socket 绑定到一个不存在于文件系统中的名字上。 (编辑:开发网_新乡站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
推荐文章
站长推荐