《Unix 网络编程》15:Unix 域协议
Unix 域协议

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

介绍

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

Unix 域协议:
- 并不是一个实际的协议组,而是在单个主机上执行 C/S 通信的一种方式 
- 所用的 API 就是之前学习的套接字 API 
- 使用普通文件系统中的路径名标识协议地址 - 这些路径名不是普通的 Unix 文件,除非把它们和 Unix 域套接字关联起来,否则无法读写这些文件 
- Unix 域协议可以被视为 IPC 方法之一 
两类套接字:
- 字节流套接字(类似 TCP)
- 数据报套接字(类似 UDP)
为什么要使用:
- 快:比位于同一主机的 TCP 快出一倍多
- 可以用在同一主机不同进程间传递描述符
- 把客户的凭证提供给服务器,从而可以提供额外的安全检查措施
Unix 域套接字结构

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

结构说明
#include <sys/un.h>
struct sockaddr_un {
  sa_family_t sun_familly; // AF_LOCAL
  char sun_path[104];
}
sun_path:
- 可见路径的长度是有限制的,不同系统的长度可能不一致,可能在 92 ~ 108 之间
- 应该以 \0结尾
- 如果没有指定地址,则只保存一个 \0即可,等价于 IPv4 的 INADDR_ANY 以及 IPv6 的 IN6ADDR_ANY_INIT
sun_family:
POSIX 为了推广 Unix 域,而不仅仅是在 Unix 操作系统上使用,把它重命名为 本地 IPC ,并把 sun_family 由 AF_UNIX 变为 AF_LOCAL ,但是我们仍然使用 Unix 域这个称呼;
另外,尽管 POSIX 努力使它独立于操作系统,它的套接字地址结构仍然保留 _un 后缀
案例:bind 调用
int main(int argc, char** argv) {
    int sockfd;
    socklen_t len;
    struct sockaddr_un addr1, addr2;
    if (argc != 2)
        err_quit("usage: unixbind <pathname>");
    sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
    // 为了防止已经存在,我们先删除这个路径名
    // 如果不存在,会返回一个我们将要忽略的错误
    unlink(argv[1]); 
    bzero(&addr1, sizeof(addr1));
    addr1.sun_family = AF_LOCAL;
    // 复制命令行参数,如果过长则会截断以免路径名存不下
    // 由于 size - 1,所以保证了以 0 结尾
    strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1);
    // 绑定
    // 用 SUN_LEN 计算了 addr1 的长度
    Bind(sockfd, (SA*)&addr1, SUN_LEN(&addr1));
    len = sizeof(addr2);
    Getsockname(sockfd, (SA*)&addr2, &len);
    printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
    exit(0);
}
测试:
[root@centos-5610 unixdomain]# ./unixbind /tmp/abc123
bound name = /tmp/abc123, returned len = 14
# 可以多次运行
<div class=anti_spider><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文信息<th>本文信息<th>防爬虫替换信息<tbody><tr><td><strong>作者网站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>联系方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文标题</strong><td>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园<td><code>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持</li><li>原文会不断地<strong>更新和完善</strong>,<strong>排版和样式会更加适合阅读</strong>,并且<strong>有相关配图</strong></li><li>如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div></div></div>
[root@centos-5610 unixdomain]# ./unixbind /tmp/abc123
bound name = /tmp/abc123, returned len = 14
# 用 ll 观察一下是否存在该目录,可以看到类型为 s
<div class=anti_spider><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文信息<th>本文信息<th>防爬虫替换信息<tbody><tr><td><strong>作者网站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>联系方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文标题</strong><td>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园<td><code>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持</li><li>原文会不断地<strong>更新和完善</strong>,<strong>排版和样式会更加适合阅读</strong>,并且<strong>有相关配图</strong></li><li>如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div></div></div>
[root@centos-5610 unixdomain]# ll /tmp
total 0
srwxr-xr-x. 1 root root  0 Jun  7 02:35 abc123
# 如果名称过长,会发生截断
<div class=anti_spider><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文信息<th>本文信息<th>防爬虫替换信息<tbody><tr><td><strong>作者网站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>联系方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文标题</strong><td>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园<td><code>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持</li><li>原文会不断地<strong>更新和完善</strong>,<strong>排版和样式会更加适合阅读</strong>,并且<strong>有相关配图</strong></li><li>如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div></div></div>
# 这里最后的长度为 5 + 102 + 1(\0) = 108,很正确
<div class=anti_spider><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文信息<th>本文信息<th>防爬虫替换信息<tbody><tr><td><strong>作者网站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>联系方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文标题</strong><td>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园<td><code>《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持</li><li>原文会不断地<strong>更新和完善</strong>,<strong>排版和样式会更加适合阅读</strong>,并且<strong>有相关配图</strong></li><li>如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://image.lymtics.top/common/logo/default.png loading=lazy></div></div></div>
[root@centos-5610 unixdomain]# ./unixbind /tmp/1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000aaaaaaaaaa
bound name = /tmp/1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000aa, returned len = 110
问题:
上述代码有一个问题,当发生截断时,再次调用会报错
bind error: Address already in usev原因是作者的代码是先 unlink 再截断的,所以说 unlink 的是那个长的,而不是截断后的
关于 umask 的补充说明:
作者在执行上述代码前先调用了 umask,可以参考:umask 是什么,下面是一个摘要:
用户创建的文件和目录,都有默认的权限,比如,文件的默认权限为0666,文件夹的默认权限为0777,因为:
- 创建文件一般是用来读写,所以默认情况下所有用户都具有读写权限,但是没有可执行权限,所以文件创建的默认权限为0666
- 而文件夹的 x 权限表示的是打开权限,所以这个权限必须要有,所以文件夹的默认权限为0777
但是系统为了保护用户创建文件和文件夹的权限,此时系统会有一个默认的用户掩码
umask,大多数的Linux系统的默认掩码为022用户掩码的作用是用户在创建文件时从文件的默认权限中去除掩码中的权限。所以文件创建之后的权限实际为:
默认权限 - umask,所以在用户不修改umask的情况下,创建文件的权限为:0666-0022=0644。创建文件夹的权限为:0777-0022=0755
socketpair 函数

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

socketpair 函数创建两个随后连接起来的套接字。本函数仅适用于 Unix 域套接字。
#include <sys/socket.h>
int socketpair(int family,		// AF_LOCAL
               int type,			// SOCK_STREAM 或 SOCK_DGRAM
               int protocal,	// 0
               int sockfd[2]	// 新创建的套接字描述符作为 sockfd[0] 和 sockfd[1] 返回
              );
这样创建的两个套接字不曾命名,也就是说其中没有设计隐式的 bind 调用
指定 type 为 SOCK_STREAM 得到的结果称为流管道,它与调用 pipe 创建的普通 Unix 管道类似,差别在于流管道是全双工的,即两个描述符都是既可读又可写。
套接字函数的差别

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

当用于 Unix 域套接字时,套接字函数中存在一些差异和限制:
- 由 bind 创建的路径名默认访问权限应该是 0777,并按照当前 umask 值进行修正 
- 与 Unix 域套接字关联的路径名应该是一个绝对路径名,而不是一个相对路径名 
- 在 connect 调用中指定的路径名必须是一个当前绑定在某个打开的 Unix 域套接字上的路径名,而且它们的套接字类型也必须一致 - 如果该路径不是 Unix 域套接字,或没有与之关联的打开的描述符,或类型不符,就会报错 
- 调用 connect 连接一个 Unix 域套接字涉及的权限等同于调用 open 以只写方式访问相应的路径名 
- Unix 域字节流套接字类似 TCP 套接字:它们都提供无记录边界的字节流接口 
- 如果对于某个 Unix 域字节流套接字的 connect 调用发现这个监听套接字的队列已满,调用就立即返回一个 E.CONN.REFUSED 错误,这一点不同于 TCP:服务端忽略,客户端重传 
- Unix 域数据报套接字类似 UDP 套接字:它们都提供一个保留记录边界的不可靠的数据报服务 
- 在一个未绑定的 Unix 域套接字上发送数据报不会自动给这个套接字捆绑一个路径名,这一点和 UDP 不同;这意味着除非数据发送端已经绑定一个路径名到它的套接字,否则数据报接收端无法发回应答数据报;类似地,对于某个 Unix 域数据报套接字的 connect 调用不会给本套接字捆绑一个路径名,这一点不同于 TCP 和 UDP 
字节流程序

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

服务端
服务器程序就像整合了我们之前的 TCP 客户端和刚刚说的 Unix 域套接字操作:
int main(int argc, char** argv) {
    int listenfd, connfd;
    pid_t childpid;
    socklen_t clilen;
  	// sockaddr_un 而不是 sockaddr,下同
    struct sockaddr_un cliaddr, servaddr;
    void sig_chld(int);
    // AF_LOCAL 指明创建 Unix 域套接字
    listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
    // UNIXSTR_PATH:预先定义好的路径常量
    unlink(UNIXSTR_PATH);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);
    Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
    Listen(listenfd, LISTENQ);
    Signal(SIGCHLD, sig_chld);
    for (;;) {
        clilen = sizeof(cliaddr);
        if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue; /* back to for() */
            else
                err_sys("accept error");
        }
        if ((childpid = Fork()) == 0) { /* child process */
            Close(listenfd);            /* close listening socket */
            str_echo(connfd);           /* process request */
            exit(0);
        }
        Close(connfd); /* parent closes connected socket */
    }
}
客户端
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, (SA*)&servaddr, sizeof(servaddr));
    str_cli(stdin, sockfd); /* do it all */
    exit(0);
}
数据报程序

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

服务端
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, (SA*)&servaddr, sizeof(servaddr));
    dg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr));
}
客户端
int main(int argc, char** argv) {
    int sockfd;
    struct sockaddr_un cliaddr, servaddr;
    sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);
    bzero(&cliaddr, sizeof(cliaddr)); /* bind an address for us */
    cliaddr.sun_family = AF_LOCAL;
    strcpy(cliaddr.sun_path, tmpnam(NULL));
  	// 要手动 Bind !! UDP 则不用
    Bind(sockfd, (SA*)&cliaddr, sizeof(cliaddr));
    bzero(&servaddr, sizeof(servaddr)); /* fill in server's address */
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);
  	// 实际的发送在这个函数里
    dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));
    exit(0);
}
如果没有 Bind,那么服务器在 dg_echo 函数中的 recvfrom 调用将返回一个空路径名,这个空路径名将导致服务器在调用 sendto 时发生错误:
[root@centos-5610 unixdomain]# ./unixdgserv01
sendto error: Transport endpoint is not connected
描述符传递

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

之前的方法
之前我们可以让父进程把描述符传递给子进程:
- fork 调用返回之后,子进程共享父进程所有打开的描述符
- exec 调用执行之后,所有描述符通常保持打开状态不变
本章的方法
使用 Unix 域套接字:
- 在两个进程之间创建一个 Unix 域套接字,然后使用 sendmsg 跨这个套接字发送一个特殊消息
- 这个消息由内核来专门处理,会把打开的描述符从发送进程传递到接受进程
具体步骤如下:
- 创建一个 Unix 域套接字 - 如果是子进程向父进程传递,父进程可以预先调用 socketpair 创建一个可用于在父子进程间交换描述符的流管道
- 如果没有亲缘关系,则父进程必须创建一个 Unix 域套接字,bind 一个路径名到该套接字以允许客户进程 connect 到该套接字;然后客户可以向服务器发送一个打开某个描述符的请求,服务器再把该描述符通过 Unix 域套接字传递回客户
 
- 发送进程打开一个描述符 - 可以用 Unix 函数如:open、pipe、mkfifo、socket、accept 等创建一个描述符(而不只是文件描述符) 
- 发送进程创建一个 msghdr 结构,将待传递的描述符作为辅助数据(msg_control)发送 - 发送描述符会使描述符的引用计数增加1(回忆之前的 fork),因此,即使发送进程在调用 sendmsg 之后但在接受进程调用 recvmsg 之前关闭了该描述符,对于接受进程而言它仍然保持打开状态。我们说这个描述符“在飞行中”(in flight)。 
- 接受进程调用 recvmsg 接收这个描述符,并可能会分配一个新的描述符数字 
案例演示
目标:
- 有父、子两个进程
- 子进程打开一个文件,并把描述符传递给父进程
- 父进程用这个描述符输出文件的内容
图示:

代码:
mycat.c
int my_open(const char*, int);
int main(int argc, char** argv) {
    int fd, n;
    char buff[BUFFSIZE];
    if (argc != 2)
        err_quit("usage: mycat <pathname>");
    // 调用 my_open 打开文件
    // 如果改成 open,则是简单地打开一个文件
    if ((fd = my_open(argv[1], O_RDONLY)) < 0)
        err_sys("cannot open %s", argv[1]);
    while ((n = Read(fd, buff, BUFFSIZE)) > 0)
        Write(STDOUT_FILENO, buff, n);
    exit(0);
}
myopen.c
int my_open(const char* pathname, int mode) {
    int fd, sockfd[2], status;
    pid_t childpid;
    char c, argsockfd[10], argmode[10];
    // 创建一个流管道,返回两个描述符保存在 sockfd 中
    Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
    if ((childpid = Fork()) == 0) { /* child process */
        // 关闭 0 ,使用 1
        Close(sockfd[0]);
        // int snprintf(char *str, size_t size, const char *format, ...)
        // 将可变参数(...)按照 format 格式化成字符串,并将字符串复制到 str
        // size 为要写入的字符的最大数目,超过 size 会被截断。
        snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
        snprintf(argmode, sizeof(argmode), "%d", mode);
        // exec 的一种变型,之前提到过
        execl("./openfile", "openfile", argsockfd, pathname, argmode,
              (char*)NULL);
        err_sys("execl error");
    }
    // 如下为父进程的操作
    // 关闭 1,流下 0
    Close(sockfd[1]); /* close the end we don't use */
    // 等待子进程终止 waitpid 的知识前面提到过
    Waitpid(childpid, &status, 0);
    if (WIFEXITED(status) == 0)
        err_quit("child did not terminate");
    // 用 W.EXIT.STATUS 把终止状态转换成退出状态
    if ((status = WEXITSTATUS(status)) == 0)
        // 我们自己写的函数,他将通过流管道接收描述符
        // 除了描述符外,我们还读取了一个字节的数据,但是不对其进行任何处理
        Read_fd(sockfd[0], &c, 1, &fd);
    else {
        errno = status; /* set errno value from child's status */
        fd = -1;
    }
    Close(sockfd[0]);
    return (fd);
read_fd.c
ssize_t read_fd(int fd, void* ptr, size_t nbytes, int* recvfd) {
    struct msghdr msg;
    struct iovec iov[1];
    ssize_t n;
// 本函数必须处理两个版本的 recvmsg:使用 msg_control 或 msg_accrights
// 前者会定义常量 HAVE_MSGHDR_MSG_CONTROL
#ifdef HAVE_MSGHDR_MSG_CONTROL
    // msg_control 缓冲区必须为 cmsghdr 结构适当地对齐,所以声明这个联合
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr* cmptr;
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
#else
    int newfd;
    msg.msg_accrights = (caddr_t)&newfd;
    msg.msg_accrightslen = sizeof(int);
#endif
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    // 接收数据
    if ((n = recvmsg(fd, &msg, 0)) <= 0)
        return (n);
#ifdef HAVE_MSGHDR_MSG_CONTROL
    // 对辅助数据进行格式验证
    if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
        cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_RIGHTS)
            err_quit("control type != SCM_RIGHTS");
        // 通过验证,取出数据
        *recvfd = *((int*)CMSG_DATA(cmptr));
    } else
        *recvfd = -1; /* descriptor was not passed */
#else
    /* *INDENT-OFF* */
    if (msg.msg_accrightslen == sizeof(int))
        *recvfd = newfd;
    else
        *recvfd = -1; /* descriptor was not passed */
                      /* *INDENT-ON* */
#endif
    return (n);
}
/* end read_fd */
ssize_t Read_fd(int fd, void* ptr, size_t nbytes, int* recvfd) {
    ssize_t n;
    if ((n = read_fd(fd, ptr, nbytes, recvfd)) < 0)
        err_sys("read_fd error");
    return (n);
}
openfile.c
int main(int argc, char** argv) {
    int fd;
    if (argc != 4)
        err_quit("openfile <sockfd#> <filename> <mode>");
    if ((fd = open(argv[2], atoi(argv[3]))) < 0)
        exit((errno > 0) ? errno : 255);
    if (write_fd(atoi(argv[1]), "", 1, fd) < 0)
        exit((errno > 0) ? errno : 255);
    exit(0);
}
write_fd.c
ssize_t write_fd(int fd, void* ptr, size_t nbytes, int sendfd) {
    struct msghdr msg;
    struct iovec iov[1];
#ifdef HAVE_MSGHDR_MSG_CONTROL
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr* cmptr;
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int*)CMSG_DATA(cmptr)) = sendfd;
#else
    msg.msg_accrights = (caddr_t)&sendfd;
    msg.msg_accrightslen = sizeof(int);
#endif
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    // 发送
    return (sendmsg(fd, &msg, 0));
}
/* end write_fd */
ssize_t Write_fd(int fd, void* ptr, size_t nbytes, int sendfd) {
    ssize_t n;
    if ((n = write_fd(fd, ptr, nbytes, sendfd)) < 0)
        err_sys("write_fd error");
    return (n);
}
接收发送者的凭证

| 本文信息 | 本文信息 | 防爬虫替换信息 | 
|---|---|---|
| 作者网站 | LYMTICS | https://lymtics.top | 
| 作者 | LYMTICS(樵仙) | https://lymtics.top | 
| 联系方式 | me@tencent.ml | me@tencent.ml | 
| 原文标题 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园 | 
| 原文地址 | https://www.cnblogs.com/lymtics/p/16354451.html | https://www.cnblogs.com/lymtics/p/16354451.html | 
- 如果您看到了此内容,则本文可能是恶意爬取原作者的文章,建议返回原站阅读,谢谢您的支持
- 原文会不断地更新和完善,排版和样式会更加适合阅读,并且有相关配图
- 如果爬虫破坏了上述链接,可以访问 `lymtics.top` 获取更多信息

介绍
凭证传递仍然是一个尚未普及且无统一规范的特性,然而它是对 Unix 域协议的一个尽管简单却也重要的补充。
当客户和服务器进行通信时,服务器通常需要以一定的手段获悉客户的身份,以便验证客户是否有权限请求相应的服务。
// FreeBSD
#include <sys/socket.h>
struct cmsgcred {
  pid_t	cmcred_pid;		// PID
  uid_t	cmcred_uid;		// real UID
  uid_t	cmcred_euid;	// effective UID
  gid_t	cmcred_gid;		// read GID of sending process
  short	cmcred_ngroups; // 组 数量
  gid_t	cmcred_groups[CMGROUP_MAX]; // 组
}
凭证信息总是可以通过 Unix 域套接字在两个进程之间传递,然而发送进程在发送它们时往往需要做特殊的封装处理,接收程序接收它们时也往往需要特殊的接受处理。
例如,在 FreeBSD 系统中,接收进程只需在调用 recvmsg 同时提供一个足以存放凭证的辅助数据空间即可,而发送进程调用 sendmsg 时必须作为辅助数据包含一个 cmsgcred 结构才会随数据传递凭证。
需要注意的是,cmsgcred 结构虽然是用户提供的,但是其内容却是内核填写的,发送进程无法伪造,总而保证了通过 Unix 域套接字传递凭证来验证用户身份的可靠性。
案例
目的:
- 改写之前那个 Unix 域套接字回射程序
- 让客户端在用 sendmsg 发送消息时额外携带 一个空的 cmsgcred 结构
- 修改 strecho.c,使其在应答前先用read_cred函数获取用户的凭证信息
read_cred 代码如下:
readcred.c
#define CONTROL_LEN (sizeof(struct cmsghdr) + sizeof(struct cmsgcred))
ssize_t read_cred(int fd,
                  void* ptr,
                  size_t nbytes,
                  struct cmsgcred* cmsgcredptr  // 凭证
) {
    struct msghdr msg;
    struct iovec iov[1];
    char control[CONTROL_LEN];
    int n;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);
    msg.msg_flags = 0;
    if ((n = recvmsg(fd, &msg, 0)) < 0)
        return (n);
    cmsgcredptr->cmcred_ngroups = 0; /* indicates no credentials returned */
    // 如果有凭证返回
    if (cmsgcredptr && msg.msg_controllen > 0) {
        struct cmsghdr* cmptr = (struct cmsghdr*)control;
        // 对其长度、等级、类型进行验证
        if (cmptr->cmsg_len < CONTROL_LEN)
            err_quit("control length = %d", cmptr->cmsg_len);
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_CREDS)
            err_quit("control type != SCM_CREDS");
        // 通过验证,则复制到 cmsgcred 结构中
        memcpy(cmsgcredptr, CMSG_DATA(cmptr), sizeof(struct cmsgcred));
    }
    return (n);
}
strecho.c 改写如下:
ssize_t read_cred(int, void*, size_t, struct cmsgcred*);
void str_echo(int sockfd) {
    ssize_t n;
    int i;
    char buf[MAXLINE];
    struct cmsgcred cred;
again:
    while ((n = read_cred(sockfd, buf, MAXLINE, &cred)) > 0) {
        if (cred.cmcred_ngroups == 0) {
            printf("(no credentials returned)\n");
        } else {
            printf("PID of sender = %d\n", cred.cmcred_pid);
            printf("real user ID = %d\n", cred.cmcred_uid);
            printf("real group ID = %d\n", cred.cmcred_gid);
            printf("effective user ID = %d\n", cred.cmcred_euid);
            printf("%d groups:", cred.cmcred_ngroups - 1);
            for (i = 1; i < cred.cmcred_ngroups; i++)
                printf(" %d", cred.cmcred_groups[i]);
            printf("\n");
        }
        Writen(sockfd, buf, n);
    }
    if (n < 0 && errno == EINTR)
        goto again;
    else if (n < 0)
        err_sys("str_echo: read error");
}
可以用
id如下命令查看个人的当前凭证:[root@centos-5610 unixdomain]# id
uid=0(root) gid=0(root) groups=0(root) ...
《Unix 网络编程》15:Unix 域协议的更多相关文章
- UNIX网络编程——通过UNIX域套接字传递描述符和 sendmsg/recvmsg 函数
		在前面我们介绍了UNIX域套接字编程,更重要的一点是UNIX域套接字可以在同一台主机上各进程之间传递文件描述符. 下面先来看两个函数: #include <sys/types.h> #in ... 
- UNIX网络编程——TCP 滑动窗口协议
		什么是滑动窗口协议? 一图胜千言,看下面的图.简单解释下,发送和接受方都会维护一个数据帧的序列,这个序列被称作窗口.发送方的窗口大小由接受方确定,目的在于控制发送速度,以免接受方的缓存不够大, ... 
- Unix网络编程--卷一:套接字联网API
		UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 0.准备环境 1.简介 2.传输层:TCP.UD ... 
- 《网络编程》Unix 域套接字
		概述 Unix 域套接字是一种client和server在单主机上的 IPC 方法.Unix 域套接字不运行协议处理,不须要加入或删除网络报头,无需验证和,不产生顺序号,无需发送确认报文,比因特网域套 ... 
- UNIX网络编程——ICMP报文分析:端口不可达
		ICMP的一个规则是,ICMP差错报文必须包括生成该差错报文的数据报IP首部(包含任何选项),还必须至少包括跟在该IP首部后面的前8个字节(包含源端口和目的端口).在我们的例子中,跟在IP首部后面的前 ... 
- 【unix网络编程第三版】阅读笔记(三):基本套接字编程
		unp第三章主要介绍了基本套接字编程函数.主要有:socket(),bind(),connect(),accept(),listen()等. 本博文也直接进入正题,对这几个函数进行剖析和讲解. 1. ... 
- 《Unix 网络编程》11:名字和地址转换
		名字和地址转换 系列文章导航:<Unix 网络编程>笔记 域名系统 简介 域名系统主要用于主机名字和 IP 地址之间的映射.主机名可以是: 简单名字,如:centos01 全限定域名(FQ ... 
- UNIX网络编程——getsockname和getpeername函数
		UNIX网络编程--getsockname和getpeername函数 来源:网络转载 http://www.educity.cn/linux/1241293.html 这两个函数或者 ... 
- Unix网络编程--卷二:进程间通信
		Unix网络编程--卷二:进程间通信 本书是一部Unix网络编程的经典之作!进程间通信(IPC)几乎是所有Unix程序性能的关键,理解IPC也是理解如何开发不同主机网络应用程序的必要条件.本书从对Po ... 
随机推荐
- EL表达式详解(常用表达式以及取值)
			EL表达式 学习总结 一. El表达式概念 二. El中的表达式 1. 算术表达式 2. 比较表达式 3. 逻辑表达式 4. 三元表达式 5. 判空表达式 三.EL 从四个作用域中取值 1. 概念 2 ... 
- EMS邮箱数据库全局监控设置
			案例任务:监控TestDB01邮箱数据库的所有邮件,监控邮箱为用户"王淑江"的邮箱. 1.EMS全局监控设置 使用PowerShell完成操作:"王淑江"监控T ... 
- spring-xml实现aop-通知的种类
			如果本代码有疑问,请访问spring-aop快速入门或者spring-aop动态代理技术(底层分析) 1.导入aop的相关坐标 <dependency> <groupId>or ... 
- Java 在Word指定段落/文本位置插入分页符
			在Word插入分页符可以在指定段落后插入,也可以在特定文本位置处插入.本文,将以Java代码来操作以上两种文档分页需求.下面是详细方法及步骤. [程序环境] 在程序中导入jar,如下两种方法: 方法1 ... 
- uniapp-uni.setNavigationBarColor 动态修改顶部背景颜色
			uni.setNavigationBarColor({ frontColor: '#ffffff', backgroundColor: "#3583ff" }) 
- Codeforces Round #306 (Div. 2), problem: (B) Preparing Olympiad【dfs或01枚举】
			题意: 给出n个数字,要求在这n个数中选出至少两个数字,使得它们的和在l,r之间,并且最大的与最小的差值要不小于x.n<=15 Problem - 550B - Codeforces 二进制 利 ... 
- synchronized有几种用法?
			在 Java 语言中,保证线程安全性的主要手段是加锁,而 Java 中的锁主要有两种:synchronized 和 Lock,我们今天重点来看一下 synchronized 的几种用法. 用法简介 使 ... 
- JMeter如何设置中文
			打开Option => Choose Language => Chinese 
- XCTF练习题---MISC---快乐游戏题
			XCTF练习题---MISC---快乐游戏题 flag:UNCTF{c783910550de39816d1de0f103b0ae32} 解题步骤: 1.观察题目,下载附件 2.还真是一个游戏,赢了就得 ... 
- [AcWing 797] 差分
			点击查看代码 #include<iostream> using namespace std; const int N = 1e5 + 10; int a[N], b[N]; void in ... 
