CSDN 上的 APUE 读书笔记之第十六章 -- 网络 IPC:套接字

20 篇文章 0 订阅

第十六章 网络 IPC:套接字


1、概述

套接字的原始版本是 BSD 套接字,它是通信端点的抽象。可用于同一机器上的进程间通信,典型应用为 Unix 域套接字;也可用于通信网络上任何体系结构的计算机之间的通信,典型应用为互联网套接字及在此基础上的 TCP/IP 协议栈实现。

1983 年,4.2 BSD 发布了基于套接字技术的第一个 TCP/IP 协议栈 API 实现,它成为此后其它系统TCP/IP 实现的基础。POSIX 的 socket(7)标准是在 4.4 BSD 的基础上制定,微软则于 1990 年代初期在成功移植 BSD 套接字的基础上开发了 winsock,此外使用 TCP/IP 技术进行通信的各种嵌入式系统也有诸多基于 Socket API 的移植版本。

套接字是在文件 I/O 机制的基础上实现的,包括匿名和有名两种文件形式。典型的有名套接字是/dev/log,它使用的是Unix 域套接字,守护进程syslogd(8)使用它和使用系统日志服务的客户进程通信。下面的内容除非特别注明,否则“套接字”特指匿名套接字。

用于分析 TCP/IP 协议的经典 Unix 工具包括 netcat(1)和 tcpdump(1)。前者被称为网络瑞士军刀,可以建立任意基于 TCP/IP 的网络连接并进行输入输出;后者可以把所在网络上的数据流转储到当前的标准输出,这些输出可通过管道线连接到一些文本过滤器之类的程序进行分析。

本章只讲述套接字的建立、设置、数据收发等基本接口。关于套接字机制与 TCP/IP 实现细节可参考:

  • TCP/IP Illustrated Volume 2: The Implementation中文译名《TCP/IP 详解 卷 2:实现》。

基于 4.4 BSD-Lite 的套接字机制讲述 TCP/IP 实现;

  • The Design and Implementation of 4.4BSD中文译名《4.4 BSD 设计与实现》。

讲述包括 Sockets 机制在内的 4.4 BSD 设计原理与实现细节; 

  • Understanding Linux Network Internals中文译名《深入理解 Linux 网络技术内幕》。

包括 Linux 环境的网络实现细节及解决方案。暂无简体中文版。


关于 TCP/IP 协议及应用可参考:

  •  TCP/IP Illustrated Volume 1: The Protocols

中文译名为《TCP/IP 详解 卷 1:协议》。讲述 TCPIP 协议族的体系结构及细节; 

  • UNIX Network Programming中文译名为《UNIX 网络编程》。

其中第二版分为两卷,第一卷 The Sockets Networking API(中文译名:《套接口 API》)讲述了 Sockets 编程的细节;


  • Internetworking With TCP/IP Vol Ⅲ:Client-Server Programming And Applications

中文译名为《用 TCP/IP 进行网际互联 第三卷:客户-服务器编程与应用》。讲述 C/S程序设计的典型模型与应用;

  •   RFC  
RFC 是互联网技术的文献资料集,这些文件通过编号排定,也有译为中文的 RFC 文档;

2、套接字的创建和关闭

套接字是 Unix 基本文件类型的一种,可以使用文件 I/O 的大部分函数,fchdir(2)只适用于目录文件,而以下是否可以与实现有关,通常不允许使用:fchmod(2), ftruncate(2), lseek(2), mmap(2);(我在linux 下,用 root 身份对有名套接字文件/dev/log 执行 chmod(1)是成功的,但 cp(1)操作失败);
socket(2)函数用于创建并打开一个套接字:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain 为通信域选项,即地址族(Address Family)。
主要的地址族包括:  
  • AF_UNIX(AF_LOCAL) Unix 域  
  • AF_INET ipv4 因特网域 
  • AF_INET6 ipv6 因特网域
type 为套接字类型选项,主要包括  
  • SOCK_STREAM 面向流的套接字,默认为 TCP 协议;  
  • SOCK_DGRAM 面向数据报的套接字,默认为 UDP 协议;  
  • SOCK_RAW 访问 IP 层的数据报接口,用户自行构造协议,此选项需要 root 权限;
protocol 为协议选项,为 0 时使用 type 的默认值。可以通过 getprotoent(3)转换协议名为协议值。
以下函数用于设置和获取指定套接字的选项
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t len);
int setsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict len);
level 表明 option 应用在哪个协议上,例如: 
  •  SOL_SOCKET 套接字通用选项;  
  • IPPROTO_TCP TCP 协议选项;  
  • IPPROTO_IP IP 协议选项;
option 则为 level 对应的可选项,这些选项可能是一个 on/off 开关,也可能是一个数值;
如果option 是一个开关,val 表示是否勾选此选项的功能;如果option 是数值选项,val 表示其取值;
套接字可以用 close(2)来关闭,或者用 shutdown(2)关闭套接字的一端:
#include <sys/socket.h>
int shutdown(int sockfd, int how);
how 包括 SHUT_RD、SHUT_WR、SHUT_RDWR;

3、计算机字节顺序

网络上通信的双方可能是异构主机,这意味着可能存在字节顺序的不同。
例如 Motorola 68K系列、早期的 SPARC等采用的是大端(或称高地址优先)字节顺序,即在一个机器字的存储单元上,低字节存在高地址,高字节存在低地址上;而 Interl X86等则采用小端(或称低地址优先)字节顺序,即在一个机器字的存储单元上,低字节存在低地址,高字节存在高地址;而ARM, SPARC V9, MIPS 等体系结构可以选择使用大端还是小端模式。它们之间直接通信会得到错误的数据。
以下接口函数提供了主机字节顺序和网络地址顺序的转换。用户不必关心网络字节顺序是什么,只要数据从主机发送到网络上或者从网络上接收数据时,使用这些函数进行转换,就不用担心字节顺序错误的问题:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);
uint32_t htons(uint32_t hostint16);
uint32_t ntohl(uint32_t netint32);
uint32_t ntohs(uint32_t netint16);
异种体系结构的不同字节顺序同时也带来带有位操作的程序的可移植性问题,移植时需要特别注意。

4、套接字与进程地址标识的关联
一个套接字绑定的进程,在网络上主要以该进程的主机(或 IP 地址,网络层的标识)、协议(传输层的标识)、端口(应用层的标识)等信息来标识。这些信息在ipv4 因特网域中,以结构sockaddr_in来描述,并封装到套接字的sockaddr 结构。
结构sockaddr_in 包括sin_family、sin_port、sin_addr三个成员;结构sockaddr 包括sa_family和sa_data 两个成员;
bind(2)将套接字绑定到指定的地址
#include <sys/types.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

对 addr 的成员有如下限制:
  • 必须使用本地地址;
  • 必须与 socket(2)创建时的 domain 格式匹配;
  • 只有 root 进程的端口号可以小于 1024;
  • 对于 AF_INET,如果 IP 地址为 INADDR_ANY,则 sockfd 绑定到本地系统的全部链路层接口;
以下两个函数可以通过套接字获取本地或者对端所绑定 sockfd 的地址标识:
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
查找和转换进程地址标识信息的传统API包括:
gethostbyname(3), gethostbyaddr(3), getnetbyaddr(3), getnetbyname(3), getprotobyname(3), getprotobynumber(3), getservbyname(3), getservbyport(3)。
它们返回相应的 hostent/netent/protoent/servent 结构的指针。也可以通过 gethostent(3), getnetent(3), getservent(3)直接取当前进程的相关结构的指针。它们的信息取自 hosts(5), services(5), protocols(5)等文件。
而POSIX 定义了一些新的函数代替上述函数的功能,包括
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host,const char *restrict service,const struct addrinfo *restrict hint,struct addrinfo **restrict res);
int getnameinfo(const struct sockaddr *restrict addr,socklen_t alen, char *restrict host,socklen_t hostlen, char *restrict service,socklen_t servlen, unsigned int flags);

getaddrinfo(3)通过给定的参数 host, service, hint 填充结构 res。
  • 其中  host  的字符串可以是主机名或者点分十进制 IP 地址;  
  •  service  为标准的服务名;   
  • hint  只使用 ai_flags, ai_family, ai_socktype, ai_protocol 这几个成员,其它成员必须设为 0 或者NULL;   
  • res  是指向一个 addrinfo 结构的链表首址,它的后趋结点的指针为 ai_next;
  • 释放这个链表的函数为 freeaddrinfo(3);
getnameinfo(3)通过给定的套接字地址标识 addr 以 flags 标志设置 host 或 service 的值;
这两个函数的返回值可以通过gai_strerror(3)转换为字符串:
#include <netdb.h>
const char *gai_strerror(int error);

5、基于套接字的数据传输

面向流的协议如TCP(7),需要建立连接才能进行数据传输。在C/S模型中,建立连接的请求通常为客户机向服务器提出。请求建立连接的函数为
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
该函数请求套接字sockfd连接到地址标识addr 端对应的套接字。
而服务器则只需要监听端口,在请求到来的时候决定是否接受这个连接即可。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
listen 将套接字设置为监听模式,backlog 指定了等待队列的最大长度,超过此长度的连接请求将直接被拒绝;
accept 将使进程阻塞,直到 sockfd 的监听队列非空,此时取出最早一个连接请求,并将此请求的相关地址标识存到 addr 指向的缓冲区,创建一个新的套接字描述符(与监听的 sockfd 有相同的 sockaddr标识)与此请求建立连接,并返回这个套接字描述符。
connect 和 accept 都是低速系统调用,即它们在资源不可用下将永远阻塞直到被一个信号打断;根据第十四章,可以用 ioctl(2)或者 fcntl(2)设置对应的套接字描述符为非阻塞模式,或者使用有多路转接与等待超时功能的select(2)和poll(2),描述符可读表示有连接请求在等待处理,描述符可写表示连接建立成功。
对于数据发送,可以直接用 write(2),或者使用下面 3个专门针对 sockets 机制设计的函数:
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

send(2)除了带一个标志参数外,其它用法同 write(2)。flags 有 4 个选项,包括

  •  MSG_DONTROUTE 该套接字仅在本地使用 
  •  MSG_DONTWAIT 使套接字非阻塞  
  • MSG_EOR 表明发送记录结束  
  • MSG_OOB 表明发送带外数据(若协议支持)
sendto(2)可以指定一个目标地址,常用于 UDP(7)等无连接的协议;
sendmsg(2)类似 wrtiev(2),可以设定多个缓冲区;
对于数据接收,可以直接用 read(2),或者使用下面 3个专门针对 sockets 机制设计的函数:

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

recv(2)可用的标志包括  
  • MSG_OOB 表明接收带外数据(若协议支持)  
  • MSG_PEEK 只查看数据而不取出队;  
  • MSG_TRUNC 要求返回报文的实际长度而不是实际收到的长度;  
  • MSG_WAITALL 等待到数据都已可用(对于可靠连接的 SOCK_STREAM 而言)
recvfrom(2)则可以拿到数据发送者的地址标识存到指定缓冲区;recvmsg(2)类似 readv(2)。

6、书中实例的程序模型

下面为书中给出的几个客户机/服务器模型的程序结构,橙色表示会引起进程阻塞的低速系统调用。








注意里面的函数print_uptime 调用了alarm(2)。如果其它地方先使用了alarm(2)的话,原来的定时可能会被覆盖并带来 bug。我给它打了个补丁(APUE2源程序的 sockets/ruptime-dg.c)



7、带外数据及异步 IO

带外数据(也称紧急数据)为传输队列上优先被传输的数据。TCP(7)支持带外数据,UDP(7)不支持。
调用 send(2)的标志参数 flags 为 MSG_OOB 时即为发送带外数据。recv(2)的 flags为 MSG_OOB时优先接收带外数据。在整个TCP 队列中,只允许出现一个字节的带外数据。如果send(2)设置了MSG_OOB不止一个字节时,最后一个字节为带外数据。对接收方来说,数据队列中只有最新的带外数据有效。
收到带外数据时产生信号SIGURG。可以通过fcntl(2)设置接收指定套接字产生的SIGURG 信号的接收者。进程就可以通过在SIGURG 的信号捕捉函数中处理 sockfd 的I/O 来处理这个带外数据:
fcntl(sockfd, F_SETOWN, pid);
还可以使用fcntl(2)实现基于套接字的异步 I/O:
fcntl(sockfd, F_SETFL, O_ASYNC);
在 sockfd 的 I/O 可用时,进程将收到 SIGIO 信号。这样,进程就可以在 SIGIO 的信号捕捉函数中处理 sockfd 的 I/O。
关于实时扩展的异步 I/O 机制已经在 第 14 章说明,另可参考 aio.h(7)。





1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、下载 4使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.m或d论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值