套接字和网络连接

套接字用于通信,特别是在网络上。套接字起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种同样支持套接字,而且支持套接字是Open Group的单一Unix规格[Open Group 1997]所要求的。System V系统传统上使用一个不同(不兼容)的网络通信接口,但这对像Solaris这样包括对套接字支持的系统来说不值一提。Socket(2)创建一个通信端点并返回一个描述符,与open(2)对文件的操作相似。套接字的参数指定协议族和类型,例如Internet域(TCP/IPv4)、Novell的IPX或“Unix域”。服务器程序一般调用bind(2)、listen(2)和accept(2)或select(2),客户程序一般调用bind(2)(虽然可能被省略)和connect(2)。参见这些例程相应的man帮助页以了解更多信息。通过相应的man帮助页可能很难理解如何使用套接字;也许需要参考Hall的“Beej”[1999]一类的文献来学习如果共同使用这些调用。

“Unix域套接字”实际上并不代表一个网络协议;它们只能与同一台机器上的套接字相连接。(在为标准Linux内核编写本文的目前情况下)。在被作为流使用时,它们与命名管道非常相似,而优越性很明显。特别是Unix域套接字面向连接;每一个到套接字的新连接都产生一个新的通信管道,这与命名管道完全不同。正是由于这一特性,Unix域套接字经常被用来代替命名管道实现很多重要服务中的IPC。就像可以拥有非命名管道一样,可以用socketpair(2)来得到非命名Unix域套接字;与非命名管道类似,非命名Unix域套接字对于IPC也很有用。

Unix域套接字有几个有趣的安全内涵。首先,虽然Unix域套接字可以出现在文件系统中,而且可以对它们使用stat(2),却不能用open(2)打开它们(只能使用socket(2)和友好接口)。其次,Unix域套接字可以用来在进程间传递文件描述符(而不仅仅是文件的内容)。其它IPC机制都不提供的这一奇特能力被用来破解所有规范(描述符基本上可以用作受限制版本的计算机科学意义上的“能力”)。文件描述符用sendmsg(2)发送,其中msg(消息)的msg_control域指向一个控制消息头的数组(msg_controllen域必须指定数组中所包含的字节数目)。每条控制消息都是一个带有数据的cmsghdr结构,为达到此目的需要把cmsg_type设置为SCM_RIGHTS。文件描述符通过recvmsg(2)获得,然后以相似的方式传递下去。坦白地说,该特性风格有些绮靡,但值得了解。

Linux 2.2支持Unix域套接字的一个附加特性:可以获取对端的“可信任证明”(pid、uid和gid)。下面是一段代码示例:
 /* fd= file descriptor of Unix domain socket connected
    to the client you wish to identify */

 struct ucred cr;
 int cl=sizeof(cr);

 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
   printf("Peer's pid=%d, uid=%d, gid=%d\n",
           cr.pid, cr.uid, cr.gid);

标准的Unix约定是需要root权限来绑定小于1024的数字作为TCP和UDP本地端口号,而任何进程都可以绑定一个大于或等于1024的不受约束的端口号。Linux遵循了这一约定,更具体地说,要绑定小于1024的端口号,Linux要求进程具有CAP_NET_BIND_SERVICE能力;一般此能力只有euid为0的进程才拥有。想进一步了解的读者可以查看Linux下相应的源码;在Linux 2.2.12中为文件/usr/src/linux/net/ipv4/af_inet.c中的函数inet_bind()。