56 Socket

Socket 基础

Socket 分为 UNIX Domain Socket 和 Internet Domain Socket,两者都有数据报和字节流两种工作模式。前者(UNIX Domain Socket)是同一台主机不同进程之间通信的方式,是可靠的通信(包括数据报通信方式)。

SUS 规定 socket 至少可选以下的 domain,具体实现可以提供更多选择:(AF 表示地址族,PF 表示协议族,早期设计人员认为一个协议族可以支持多个地址族,但实际上协议族和地址族基本上是一一对应的关系,而且所有 PF_ 开头的常量都被定义成对应的 AF_ 开头的常量。)

SUS 规定 socket 至少可选流(SOCK_STREAM)和数据报(SOCK_DGRAM),具体实现可以提供更多选择,比如 SOCK_RAW 表示直接使用 IP 而不是传输层协议,但这可能需要特权。在流类型的 socket 中,连接的另外一方被称为 peer socket(对等 socket)。

流 socket

系统调用的过程

  1. 使用 socket() 系统调用创建一个 socket。
  2. (服务器)使用 bind() 系统调用把 socket 绑定到一个周知的地址上。Internet domain 上的 socket 可以不用 bind() 直接调用 listen()
  3. (服务器)使用 listen() 系统调用允许一个流 socket 来接收其他 socket 的接入连接。
  4. (服务器)使用 accept() 系统调用等待对等程序的连接,连接成功时返回一个指向新 socket 的文件描述符。接下来可以通过这个文件描述符指定的 socket 和对等 socket 交换信息。
  5. (客户端)使用 connect() 系统调用发送连接请求。

创建 socket

SYNOPSIS
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

TLPI 这本书中不需要用到特殊的 protocol,总是把 protocol 填 0。类型字段还能或上 SOCK_NONBLOCK 或者 SOCK_CLOEXEC,相当于一步完成了对 socket 文件描述符的 fcntl()

刚创建出来的 socket 都是主动 socket,一个主动 socket 在调用 listen() 之后会变成被动 socket。

把 socket 绑定到地址

略。

让 socket 监听请求

略。

连接 socket

使用 connect() 可以和一个被动 socket 连接。如果连接失败,可移植的处理方法是关闭当前的 socket,创建新的 socket,然后用新的 socket 重试。

关闭 socket

close() 关闭即可。TLPI 后面会讲到 shutdown() 系统调用,控制更加精细。

数据报 socket

先用 socket 创建一个 socket(这个时候还没有完成绑定,只要进程退出资源就会被正确清理),然后 bind 到一个地址上。如果只是需要给服务器发送信息、不需要服务器回应,客户端可以不用 bind,但如果要实现双向传输,客户端也需要通过 bind 获得一个自己的地址。

信息的传输使用专门的系统调用 recvfrom()sendto()数据报 socket 也能使用 connect() 来连接,这样就能直接通过 read()write() 系统调用来传输数据,但是这个 socket 今后将只能读取对等 socket() 的数据报。而且这种连接是不对称的,只在调用了 connect() 的 socket 文件描述符上生效,而对等 socket 想要得到同样的效果也需要再调用一次 connect()

发送时数据报的大小是有上限的。另一方面,接收时如果数据报缓冲区的大小不足够大,则会发生静默截断。