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.写一个程序判断所使用系统的字节序
树莓派是小端