当前位置:嗨网首页>书籍在线阅读

02-概述

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

56.1 概述

在一个典型的客户端/服务器场景中,应用程序使用socket进行通信的方式如下。

  • 各个应用程序创建一个socket。socket是一个允许通信的“设备”,两个应用程序都需要用到它。
  • 服务器将自己的socket绑定到一个众所周知的地址(名称)上使得客户端能够定位到它的位置。

使用socket()系统调用能够创建一个socket,它返回一个用来在后续系统调用中引用该socket的文件描述符。

1438.png 在后续章节中将会对socket domain和类型进行介绍。在本书介绍的所有应用程序中,protocol参数总是被指定为0。

通信domain

socket存在于一个通信domain中,它确定:

  • 识别出一个socket的方法(即socket“地址”的格式);
  • 通信范围(即是在位于同一主机上的应用程序之间还是在位于使用一个网络连接起来的不同主机上的应用程序之间)。

现代操作系统至少支持下列domain。

  • UNIX (AF_UNIX) domain允许在同一主机上的应用程序之间进行通信。(POSIX.1g使用名称AF_LOCAL作为AF_UNIX的同义词,但SUSv3并没有使用这个名称。)
  • IPv4 (AF_INET) domain允许在使用因特网协议第4版(IPv4)网络连接起来的主机上的应用程序之间进行通信。
  • IPv6 (AF_INET6) domain允许在使用因特网协议第6版(IPv6)网络连接起来的主机上的应用程序之间进行通信。尽管IPv6被设计成了IPv4接任者,但目前后一种协议仍然是使用最广的协议。

表56-1对这些socket domain的特点进行了总结。

在一些代码中读者可能会看到名称诸如PF_UNIX而不是AF_UNIX的常量。在这种上下文中,AF表示“地址族(address family)”,PF表示“协议族(protocol family)”。在一开始的时候,设计人员相信单个协议族可以支持多个地址族。但在实践中,没有哪一个协议族能够支持多个已经被定义的地址族,并且所有既有实现都将PF_常量定义成对应的AF_常量的同义词。(SUSv3规定了AF_常量,但没有规定PF_常量。)在本书中会一直使用AF_常量。更多有关这些常量的历史信息可以在[Stevens et al., 2004]的4.2节中找到。

表56-1:socket domain

| Domain | 执行的通信 | 应用程序间的通信 | 地址格式 | 地址结构 | | :----- | :----- | :----- | :----- | :----- | :----- | :----- | | AF_UNIX | 内核中 | 同一主机 | 路径名 | sockaddr_un | | AF_INET | 通过IPv4 | 通过IPv4网络连接起来的主机 | 32位IPv4地址+16位端口号 | sockaddr_in | | AF_INET6 | 通过IPv6 | 通过IPv6网络连接起来的主机 | 128位IPv6地址+16位端口号 | sockaddr_in6 |

socket类型

每个socket实现都至少提供了两种socket:流和数据报。这两种socket类型在UNIX和Internet domain中都得到了支持。表56-2对这两种socket类型的属性进行了总结。

表56-2:socket类型及其属性

| 属性 | socket类型 | | :----- | :----- | :----- | :----- | | 流 | 数据报 | | 可靠地递送? | 是 | 否 | | 消息边界保留? | 否 | 是 | | 面向连接? | 是 | 否 |

流socket(SOCK_STREAM)提供了一个可靠的双向的字节流通信信道。在这段描述中的术语的含义如下。

  • 可靠的:表示可以保证发送者传输的数据会完整无缺地到达接收应用程序(假设网络链接和接收者都不会崩溃)或收到一个传输失败的通知。
  • 双向的:表示数据可以在两个socket之间的任意方向上传输。
  • 字节流:表示与管道一样不存在消息边界的概念(参见44.1节)。

一个流socket类似于使用一对允许在两个应用程序之间进行双向通信的管道,它们之间的差别在于(Internet domain)socket允许在网络上进行通信。

流socket的正常工作需要一对相互连接的socket,因此流socket通常被称为面向连接的。术语“对等socket”是指连接另一端的socket,“对等地址”表示该socket的地址,“对等应用程序”表示利用这个对等socket的应用程序。有些时候,术语“远程”(或外部)是作为对等的同义词使用。类似地,有些时候术语“本地”被用来指连接的这一端上的应用程序、socket或地址。一个流socket只能与一个对等socket进行连接。

数据报socket(SOCK_DGRAM)允许数据以被称为数据报的消息的形式进行交换。在数据报socket中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或者根本就无法到达。

数据报socket是更一般的无连接socket概念的一个示例。与流socket不同,一个数据报socket在使用时无需与另一个socket连接。(在56.6.2节中将会看到数据报socket可以与另一个socket连接,但其语义与连接的流socket是不同的。)

在Internet domain中,数据报socket使用了用户数据报协议(UDP),而流socket则(通常)使用了传输控制协议(TCP)。一般来讲,在称呼这两种socket时不会使用术语“Internet domain数据报socket”和“Internet domain流socket”,而是分别使用术语“UDP socket”和“TCP socket”。

socket系统调用

关键的socket系统调用包括以下几种。

  • socket()系统调用创建一个新socket。
  • bind()系统调用将一个socket绑定到一个地址上。通常,服务器需要使用这个调用来将其socket绑定到一个众所周知的地址上使得客户端能够定位到该socket上。
  • listen()系统调用允许一个流socket接受来自其他socket的接入连接。
  • accept()系统调用在一个监听流socket上接受来自一个对等应用程序的连接,并可选地返回对等socket的地址。
  • connect()系统调用建立与另一个socket之间的连接。

在大多数Linux架构上(除了Alpha和IA-64),所有这些socket系统调用实际上被实现成了通过单个系统调用socketcall()进行多路复用的库函数。(这是Linux socket实现的最初的开发工作,作为一个单独的项目的产物。)但在本书中将所有这些函数都称为系统调用,因为它们在最初的BSD实现以及其他很多同时代的UNIX实现上是被实现成系统调用的。

socket I/O可以使用传统的read()和write()系统调用或使用一组socket特有的系统调用(如send()、recv()、sendto()以及recvfrom())来完成。在默认情况下,这些系统调用在I/O操作无法被立即完成时会阻塞。通过使用fcntl() F_SETFL操作(5.3节)来启用O_NONBLOCK打开文件状态标记可以执行非阻塞I/O。

在Linux上可以通过调用ioctl(fd, FIONREAD, &cnt)来获取文件描述符fd引用的流socket中可用的未读字节数。对于数据报socket来讲,这个操作会返回下一个未读数据报中的字节数(如果下一个数据报的长度为零的话就返回零)或在没有未决数据报的情况下返回0。这种特性没有在SUSv3中予以规定。