网络编程学习——Unix域协议
网络编程学习——Unix域协议
thanatos_y 发表于2年前
网络编程学习——Unix域协议
  • 发表于 2年前
  • 阅读 22
  • 收藏 0
  • 点赞 1
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

摘要: 通过《UNIX网络编程卷1:套接字联网API》学习网络编程

1 概述

  Unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方式,所用API就是在不同主机上执行客户/服务器通信所用的API(套接字API)。

  Unix域提供两类套接字:字节流套接字(类似于TCP)和数据报套接字(类似UDP)尽管也提供原始套接字,不过它的语义不曾见于任何文档,作者们也未见过任何使用它的程序,POSIX也没有它的定义。

  使用Unix域套接字有以下3个理由。

  1. 在BSD的实现中,Unix域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍。X Window System发挥了Unix域套接字的这个优势。当一个X11客户启动并打开到X11服务器的连接时,该客户检查DISPLAY环境变量的值,其中指定服务器的主机名、窗口和屏幕。如果服务器与客户出于同一主机,客户就打开一个到服务器的Unix域字节流连接,否则打开一个到服务器的TCP连接。

  2. Unix域套接字可用于在同一个主机上的不同进程之间传递描述符。

  3. Unix域套接字较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施。

  Unix域中用于标识客户和服务器的协议地址是普通文件系统中的路径名。这些路径名不是普通的Unix文件:除非把它们和Unix域套接字关联起来, 否则无法读写这些文件。

2 Unix域套接字地址结构

  Unix域套接字地址结构如下。

#include <sys/un.h>
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket.  */
struct sockaddr_un
  {
    __SOCKADDR_COMMON (sun_);
    char sun_path[108];        /* Path name.  */
  };

  存放在sun_path数组中的路径名必须以空字符结尾。

例子:Unix域套接字的bind调用

  下面程序创建一个Unix域套接字,往其上bind一个路径名,再调用getsockname输出这个绑定的路径名。

3 socketpair函数

  socketpair函数创建两个随后连接起来的套接字。不函数仅适用于Unix域套接字。

#include <sys/socket.h>
/* Create two new sockets, of type TYPE in domain DOMAIN and using
   protocol PROTOCOL, which are connected to each other, and put file
   descriptors for them in FDS[0] and FDS[1].  If PROTOCOL is zero,
   one will be chosen automatically.  Returns 0 on success, -1 for errors.  */
extern int socketpair (int __domain, int __type, int __protocol,
               int __fds[2]) __THROW;

  __domain参数必须为AF_LOACL,__protocol参数必须为0。__type参数既可以是SOCK_STREAM,也可以是SOCK_DGRAM。新建的两个套接字描述符作为__fds[0]和__fds[1]返回。

  这样创建两个套接字不曾命名,也就是说其中没有涉及隐式的bind调用。指定type参数为SOCK_STREAM调用socketpair得到的结果称为流管道(pipe)。它与调用pipe创建的普通Unix管道类似,差别在于流管道是全双工的,即两个描述符都是既可读又可写的。

4 套接字函数

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

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

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

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

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

  6. 如果对于某个Unix域字节流套接字的connect调用发现这个监听套接字的队列已满,调用就立即返回一个ECONNREFSUED错误。

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

  8. 在一个未绑定的Unix域套接字上发送数据报不会自动给这个套接字捆绑一个路径名,这一点不同于UDP套接字:在一个未绑定的UDP套接字上发送UDP数据报导致给这个套接字捆绑一个临时端口。

5 描述符传递

  当考虑从一个进程到另一个进程传递打开的描述符时,我们通常会想到:

  • fork调用返回之后,子进程共享父进程的所有打开的描述符;

  • exec调用执行之后,所有描述符通常保持打开状态不变。

  当前的Unix系统提供了用于从一个进程向任一其他进程传递一打开的描述符的方法。也就是说,这两个进程之间无需存在亲缘关系,譬如父子进程关系。这种技术要求首先在这两个进程之间创建一个Unix域套接字,然后使用sendmsg跨这个套接字发送一个特殊消息。这个消息由内核来专门处理,会把打开的描述符从发送进程传递到接收进程。

  在两个进程之间传递描述符涉及的步骤如下。

  1. 创建一个字节流的或数据报的Unix域套接字。

  2. 发送进程通过调用返回描述符的任一Unix函数打开一个描述符,这些函数的例子有open、pipe、mkfifo、socket和accpet。

  3. 发送进程创建一个msghdr结构,其中含有待传递的描述符。

  4. 接收进程调用recvmsg在来自步骤1的Unix域套接字上接收这个描述符。

 

描述符传递的例子

  图1-1展示了上述步骤(1):通过调用socketpair创建一个流管道后的mycat进程。我们以后以[0]和[1]标示socketpair返回的两个描述符。

图1-1 使用socketpair创建流管道后的mycat进程

  mycat进程接着调用fork,进程再调用exec执行openfile程序。父进程关闭[1]描述符,子进程关闭[0]描述符。图1-2展示如此处理后的结果。

图1-2 启动执行openfile程序后的mycat进程

  父进程必须给openfile传递三条信息:(1)待打开文件的路径名,(2)打开方式(只读、读写或只写),(3)流管道本进程端。

  下面是mycat程序,把一个文件复制到标准输出。

  如果把my_open调用换成open调用,这个简单的程序就只是把一个文件复制到标准输出。

  read_fd函数接收数据和一个描述符。

  下面是openfile程序。它取得三个必须传入的命令行参数,并调用通常的open函数。

  最后一个函数是write_fd函数。

 

6 接收发送者的凭证

  通过Unix域套接字作为辅助数据传递的另一种数据是用户凭证(user credential)。

#include <bits/socket.h>
/* User visible structure for SCM_CREDENTIALS message */
struct ucred
{
  pid_t pid;            /* PID of sending process.  */
  uid_t uid;            /* UID of sending process.  */
  gid_t gid;            /* GID of sending process.  */
};

例子

  该函数由子进程在父进程接受了一个新的客户连接并调用fork之后调用。

 

 

 

 

 

 

 

 

 

 

共有 人打赏支持
粉丝 7
博文 90
码字总数 309348
×
thanatos_y
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: