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
系统调用的过程
- 使用
socket()
系统调用创建一个 socket。 - (服务器)使用
bind()
系统调用把 socket 绑定到一个周知的地址上。Internet domain 上的 socket 可以不用bind()
直接调用listen()
。 - (服务器)使用
listen()
系统调用允许一个流 socket 来接收其他 socket 的接入连接。 - (服务器)使用
accept()
系统调用等待对等程序的连接,连接成功时返回一个指向新 socket 的文件描述符。接下来可以通过这个文件描述符指定的 socket 和对等 socket 交换信息。 - (客户端)使用
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()
。
发送时数据报的大小是有上限的。另一方面,接收时如果数据报缓冲区的大小不足够大,则会发生静默截断。