
APUE 第十六章 socket套接字
第十六章 网络IPC:套接字 socket
套接字用来进行不同计算机上的进程相互通信
16.2 套接字描述符
应用程序使用套接字描述符访问套接字。在UNIX系统中,套接字描述符被看作一种文件描述符。
为了创建一个套接字,调用 socket
函数
1 |
|
domain
参数指定地址族 address family ,不同地址族的地址格式不同,表示地址族的常数以 AF_
开头

type
参数指定套接字的类型,进一步确定通信特征

protocol
通常是 0 ,表示为给定的域和套接字选择默认协议。
AF_INET
通信域,SOCK_STREAM
的默认协议是 传输控制协议 (Transmission Control Protocol,TCP)
AF_INET
通信域,SOCK_DGRAM
的默认协议是 用户数据报协议 (User Datagram Protocol,UDP)
下图是一些协议

socket
函数返回套接字描述符。本质上它是一个文件描述符,所以很多以文件描述符为参数的函数同样可以使用套接字描述符,下图定义了一些函数以套接字描述符为参数调用时的行为

套接字通信是双向的,用 shutdown
函数来禁止一个套接字的 I/O
1 |
|
how
可以是 SHUT_RD
、SHUT_WR
和 SHUT_RDWR
,分别关闭 读、写 和 读写
16.3 寻址 addressing
如何准确确定目标通信地址 ? 计算机网络地址 + 端口号
16.3.1 字节序
字节序是一个处理器架构特性,用于指示大数据类型(如整型)内部的字节如何排序

MSB : Most Significant Byte 最高有效字节
LSB: Least Significant Byte 最低有效字节
由上图可见,大端(big-endian) 字节由MSB向 LSB排列,小端则相反
TCP/IP 使用大端字节序。
所以应用程序有时需要在处理器字节序和网络字节序之间实施转换。
对于TCP/IP应用程序,有4个函数能完成此转换操作
1 |
|
16.3.2 地址格式
不同的通信域有不同的地址格式,为了让不同格式的地址能够传入套接字,地址会被强制转换成 一个通用的结构 sockaddr
1 | struct sockaddr { |
在 IPv4 因特网域(AF_INET
)中,套接字地址用结构 sockaddr_in
表示
1 | struct in_addr { |
1 | struct sockaddr_in { |
在 IPv6 因特网域(AF_INET6
)中,套接字地址用结构 sockaddr_in6
表示
1 | struct in6_addr { |
1 | struct sockaddr_in6 { |
有时为了打印出人类易于观察的地址,需要在二进制地址格式和点分十进制字符之间进行转换,以下两个函数负责这个操作
1 |
|
inet_ntop
将二进制地址转换成文本字符串格式,size
指定缓冲区 str
的大小。INET_ADDRSTRLEN
指定了足够容纳 IPv4 地址的大小,INET6_ADDRSTRLEN
指定了足够容纳 IPv6 地址的大小
inet_pton
将文本字符串格式转换为二进制地址
domain
参数 可选 AF_INET
和 AF_INET6
16.3.3 地址查询 address lookup
gethostent
函数获得给定计算机系统的主机信息
1 |
|
hostent
结构如下
1 | struct hostent { |
下面这些函数获得网络名字和网络编号
1 |
|
netent
结构
1 | struct netent { |
协议名 和 协议编号 之间的映射
1 |
|
protoent
结构体
1 | struct protoent { |
服务和端口号之间的映射
1 |
|
servent
结构
1 | struct servent { |
getaddrinfo
函数允许将一个主机名和一个服务名映射到一个地址
1 |
|
addrinfo
结构
1 | struct addrinfo { |
可以提供一个 可选的 hint
来选择符合特定条件的地址。hint
相当于一个过滤器,可以过滤 ai_family
、ai_flags
、ai_protocol
和 ai_socktype
字段,但是其它的字段必须设置为 0 。
ai_flags
可选值如下

如果 getaddrinfo
失败,要调用 gai_strerror
将返回的错误码转换成错误消息
1 |
|
getnameinfo
函数将一个地址转换成一个主机名和一个服务名
1 |
|
flags
参数指定了控制翻译的方式

16.3.4 将套接字与地址关联
bind
函数关联套接字和地址
1 |
|
getsockname
函数发现绑定到套接字上的地址
1 |
|
调用前,alenp
设置为一个指向整数的指针,该整数指定缓冲区 addr
的长度
如果 socket
已经和对方连接,可以使用 getpeername
函数找到对方的地址
1 |
|
16.4 建立连接
connect
负责建立连接
1 |
|
由于一些原因,连接可能会失败,应用程序必须能够处理 connect
返回的错误
服务器 调用 listen
函数表明可以接受连接请求
1 |
|
一旦调用了 listen
,服务器就能接收连接请求。使用 accept
函数获得连接请求并建立连接
1 |
|
此函数返回值是一个连接到 调用 connect
的客户端的套接字描述符。
如果不关心客户端标识,后两个参数可以设置为 NULL
;否则应该为 addr
分配足够大的缓冲区,并用 len
指出此缓冲区的长度
如果没有连接请求 ,accept
会阻塞到请求到;除非 sockfd
设置为 非阻塞的 ,这种情况下如果没有连接请求,accept
会返回 -1
并将 errno
设置为 EAGAIN
16.5 数据传输
用于通过套接字发送数据的函数
1 |
|
除了第四个参数,这个函数和 write
函数很类似,flags
常见选项如下

1 |
|
sendto
与 send
的不同之处在于 sendto
可以在无连接的套接字上指定一个目标地址
1 |
|
msghdr
结构
1 | struct msghdr { |
通过 sendmsg
和 msghdr
结构可以在发送数据时指定多重缓冲区(与 writev
函数类似)
接下来是接受数据的函数
1 |
|
flags
选项

recvfrom
函数可以在接收数据的时候顺便保存发送端的信息
1 |
|
recvmsg
可以用 msghdr
结构指定多重缓冲区以存放接收到的数据
1 |
|
16.6 套接字选项
1 |
|
option
可选项:

1 |
|
16.7 带外数据 Out-of-Band Data
与普通数据相比,带外数据允许更高优先级的数据传输。
TCP 支持带外数据,UDP不支持。TCP 将带外数据称为 紧急数据(urgent data)
课后习题
1.写一个程序判断所使用系统的字节序
树莓派是小端