Loading

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

Unix 域协议

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

系列文章导航:《Unix 网络编程》笔记

介绍

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

Unix 域协议

  • 并不是一个实际的协议组,而是在单个主机上执行 C/S 通信的一种方式

  • 所用的 API 就是之前学习的套接字 API

  • 使用普通文件系统中的路径名标识协议地址

    这些路径名不是普通的 Unix 文件,除非把它们和 Unix 域套接字关联起来,否则无法读写这些文件

  • Unix 域协议可以被视为 IPC 方法之一

两类套接字:

  • 字节流套接字(类似 TCP)
  • 数据报套接字(类似 UDP)

为什么要使用:

  1. :比位于同一主机的 TCP 快出一倍多
  2. 可以用在同一主机不同进程间传递描述符
  3. 把客户的凭证提供给服务器,从而可以提供额外的安全检查措施

Unix 域套接字结构

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://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

  1. 可见路径的长度是有限制的,不同系统的长度可能不一致,可能在 92 ~ 108 之间
  2. 应该以 \0 结尾
  3. 如果没有指定地址,则只保存一个 \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 函数

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://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 管道类似,差别在于流管道是全双工的,即两个描述符都是既可读又可写。

套接字函数的差别

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

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

  1. 由 bind 创建的路径名默认访问权限应该是 0777,并按照当前 umask 值进行修正

  2. 与 Unix 域套接字关联的路径名应该是一个绝对路径名,而不是一个相对路径名

  3. 在 connect 调用中指定的路径名必须是一个当前绑定在某个打开的 Unix 域套接字上的路径名,而且它们的套接字类型也必须一致

    如果该路径不是 Unix 域套接字,或没有与之关联的打开的描述符,或类型不符,就会报错

  4. 调用 connect 连接一个 Unix 域套接字涉及的权限等同于调用 open 以只写方式访问相应的路径名

  5. Unix 域字节流套接字类似 TCP 套接字:它们都提供无记录边界的字节流接口

  6. 如果对于某个 Unix 域字节流套接字的 connect 调用发现这个监听套接字的队列已满,调用就立即返回一个 E.CONN.REFUSED 错误,这一点不同于 TCP:服务端忽略,客户端重传

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

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

字节流程序

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://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);
}

数据报程序

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://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

描述符传递

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

之前的方法

之前我们可以让父进程把描述符传递给子进程:

  • fork 调用返回之后,子进程共享父进程所有打开的描述符
  • exec 调用执行之后,所有描述符通常保持打开状态不变

本章的方法

使用 Unix 域套接字:

  • 在两个进程之间创建一个 Unix 域套接字,然后使用 sendmsg 跨这个套接字发送一个特殊消息
  • 这个消息由内核来专门处理,会把打开的描述符从发送进程传递到接受进程

具体步骤如下:

  1. 创建一个 Unix 域套接字

    • 如果是子进程向父进程传递,父进程可以预先调用 socketpair 创建一个可用于在父子进程间交换描述符的流管道
    • 如果没有亲缘关系,则父进程必须创建一个 Unix 域套接字,bind 一个路径名到该套接字以允许客户进程 connect 到该套接字;然后客户可以向服务器发送一个打开某个描述符的请求,服务器再把该描述符通过 Unix 域套接字传递回客户
  2. 发送进程打开一个描述符

    可以用 Unix 函数如:open、pipe、mkfifo、socket、accept 等创建一个描述符(而不只是文件描述符)

  3. 发送进程创建一个 msghdr 结构,将待传递的描述符作为辅助数据(msg_control)发送

    发送描述符会使描述符的引用计数增加1(回忆之前的 fork),因此,即使发送进程在调用 sendmsg 之后但在接受进程调用 recvmsg 之前关闭了该描述符,对于接受进程而言它仍然保持打开状态。我们说这个描述符“在飞行中”(in flight)。

  4. 接受进程调用 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);
}

接收发送者的凭证

★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文信息本文信息防爬虫替换信息
作者网站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
联系方式me@tencent.mlme@tencent.ml
原文标题《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园《Unix 网络编程》15:Unix 域协议 - 樵仙 - 博客园
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://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) ...
posted @ 2022-06-08 09:52  樵仙  阅读(644)  评论(0编辑  收藏  举报