《Unix网络编程》卷1 高级

本文详细探讨了IPv4和IPv6的互操作性,阐述了双栈主机如何同时处理IPv4和IPv6通信,并介绍了地址测试宏、源代码可移植性。此外,还深入讲解了守护进程和超级服务器的概念,包括守护进程的启动、创建以及在系统中的角色。最后,讨论了套接字的高级函数,如超时设置、套接字选项以及I/O函数的差异,提供了更高效的数据处理方式。
摘要由CSDN通过智能技术生成

第12章 IPv4和IPv6的互操作性

12.1 概述

未来,因特网会逐渐从IPv4转换到IPv6所以应用程序能够对两种协议协同工作是很重要的,我们将讨论IPv4应用如何与IPv6进行通信

12.2 IPv4 客户与 IPv6 服务器

  • 双栈主机一个很重要的特性就是技能处理IPv4又能处理IPv6客户。这是通过使用IPv4映射的IPv6地址来实现的
  • 主机使用IPv6服务器,收到IPv4的帧时,会将源IPv4地址转换为一个映射的IPv6地址
    • IPv4 地址可以转换为一个唯一的IPv6 地址(IPv4地址补齐到IPv6地址格式, 方式是在前补::ffff)
    • 然后把数据报相应的字段给替换掉(包括IP版本标识字段)

  • 补充 IPv6 地址格式
  • IPv6中的双冒号:
    • 通过使用双冒号(::)替换一系列零来指定 IPv6 地址, 一个IP 地址中只可使用一次双冒号。
      • 例如: IPv6 地址: ff06:0:0:0:0:0:0:c3 可写作 ff06::c3
  • IPv4映射的IPv6地址使用此替代格式。
    • 此类型的地址用于将IPv4节点表示为IPv6地址。
      • 例如: IPv4地址192.1.56.10映射为IPv6地址0:0:0:0:0:ffff:192.1.56.10::ffff:192.1.56.10/96(短格式)
  • 注意IPv6地址不能直接转IPv4地址

  • 一个IPv4客户端IPv6服务端通信地步骤如下:
    • IPv6服务器启动后创建一个IPv6监听套接字
    • IPv4客户调用getsockname找到服务器地A记录。服务器既有A也有AAAA记录,因为它是双栈
    • 客户调connect,发送一个IPv4的SYN给服务器
    • 服务器收到这个SYN,把它标志为IPv4映射为IPv6,响应一个IPv4 SYN/ACK。连接建立后, 由accept返回给服务器的地址就是这个IPv4映射的IPv6地址
    • 服务器向这个客户端发送数据时,会使用客户端的IPv4地址,所以通信使用的全部都是IPv4连接
    • 如果服务器不检查这个地址是IPv6还是IPv4映射过来,它永远不会知道客户端的 IP 类型,客户端也不需要知道服务器的类型
  • 大多数双栈主机遵循以下规则:
    • IPv4 监听套接字只能接受来自IPv4 客户的外来连接
    • 如果服务器有一个绑定了IPv6 的监听套接字,该套接字没设置 IPV6_V6ONLY 套接字选项,它可以接收 IPv4 连接
    • 如果服务器有一个 IPv6 监听套接字,绑定了通配地址,该套接字设置了 IPV6_V6ONLY 套接字选项,它只能接收 IPv6 连接
      TCP IPv4 客户与 IPv6 服务器
  • IPv6 UDP服务器的情况与之类是, 差别在于数据报的地址格式有所不同.例如IPv6服务器收到来自某个IPv4客户的数据报,recvfrom返回的地址将是该客户端的IPv6地址(由于IPv4映射而来)

12.3 IPv6客户与IPv4服务器

  • 客户机运行在双栈主机上并使用IPv6套接字描述符
  • 过程:
    • 一个IPv4 服务器在只支持IPv4的主机上启动一个IPv4 监听套接字
    • IPv6 客户启动后调用getaddrinfo单纯查找IPv6的地址,因为服务器只支持IPv4,所以返回给客户端的是一个IPv4映射的IPv6地址
    • IPv6 客户在作为函数参数的 IPv6 套接字地址结构中设置这个 IPv4 映射的 IPv6 地址然后调用 connect。内核检测到这个映射地址后自动发送一个IPv4 SYN到服务器
    • 服务器响应一个IPv4 SYN/ACK,连接于是通过使用IPv4 数据报建立

12.2-12.3 互操作性总结

  • IPv4客户端IPv6服务器(双栈)
    • 套接字接受数据报分析:
      • 对于IPv4套接字它只能接受IPv4数据报(IPv6地址不能映射为IPv4地址)
      • 对于IPv6套接字(带双栈)它可以接受IPv4数据报IPv6数据报(IPv4地址可以被唯一映射为IPv6地址)
    • 具体分析: 如果目的地为IPv6套接字IPv4数据报,那么内核(目的地内核)把该数据包的源IPv4地址映射为IPv6地址作为accept(TCP)recvfrom(UDP)返回的对端IP地址
      IPv4客户端与IPv6服务器
  • IPv4服务器IPv6客户端(双栈)
  • 套接字发送数据报分析:
    • IPv4数据报可以接受IPv4套接字IPv6套接字的数据发送(IPv6套接字的目的地为由IPv4地址映射的IPv6地址是内核将其转变为IPv4数据报)
    • IPv6数据报只能接受IPv6套接字的数据发送
  • IPv4套接字不能发送一个IPv6数据报,因为不可能往IPv4套接字上设置IPv6地址,毕竟IPv4套接字接受的sockaddr_in的in_addr成员只有4字节的长度
  • IPv6套接字发送数据时,内核检测到目的IP地址为由IPv4地址映射的IPv6地址,所以此地址转换为IPv4地址发送IPv4数据报
    IPv4服务器与IPv6客户端(双栈)

  • 互操作性总结
    互操作性总结

IPv6地址测试宏

  • 有一些 IPv6 应用必须知道和它通信的是 IPv6 还是 IPv4 协议,使用 <netinet/in.h> 中的函数可以进行测试
  • int IN6_IS_ADDR_V4MAPPED(const struct in6_addr *aptr)宏测试IPv6地址是否由IPv4映射而来

源代码可移植性

  • 考虑到源码的可移植性,编写代码时应尽量避免 gethostbyname, gethostbyaddr等套接字函数,使用 getaddrinfo, getnameinfo等函数,使得代码变得与协议无关。

小结

  • 双栈主机的IPv6 服务器可以和两种客户端进行通讯, 对于IPv4客户端使用IPv4数据报进行通信
  • 双栈主机的IPv6 客户端也可以和两种服务器进行通讯, 对于IPv4服务器使用IPv4数据报进行通信

第13章:守护进程和inetd超级服务器

  • T:2019/10/28 W:1 14:10:40

13.1 概述

  • 守护进程:是在后台运行且不与任何控制终端关联的进程。Unix系统通常有很多守护进程在后台运行(通常为20~50个的数量级)执行不同的管理任务.
  • 守护进程没有终端,通常是因为他们由开机时的脚本进行启动。守护进程也可能从某个终端由用户在shell提示符下键入命令行进行启动,这样的守护进程必须亲自脱离与控制终端的关联,从而避免与 作业控制终端, 会话管理终端产生信号等发生不希望的交互,也防止后台的守护进程输出到终端
  • 守护进程的启动方式:
    • 在系统阶段进行启动,许多守护进程由系统初始化脚本进行启动,脚本通常位于 /etc 等目录,这些脚本启动的守护进程开始就拥有超级用户权限(inetdWebsendmailsyslogd 等等)
    • 许多网络服务器由 inetd超级服务器(其本身由第一条启动) 进行启动。Inetd监听网络请求,每当有一个请求到达,启动相应的实际服务器(Telnet,FTP…)
    • cron守护进程(其本身由第一条启动) 按规则定期执行一些程序,这些程序同样作为守护继承运行(单词cron,计时程序)
    • at 命令用于指定将来某个时刻的程序执行,时间到达时,通常使用corn来进行执行
    • 守护进程还可以从用户的终端在前台或者后台进行启动。这么做往往是测试守护进程或者重启关闭的守护进程。
  • 因为守护进程没有终端,所以他们的消息使用 syslog 进行处理,即使用 syslog 函数,将消息发送给 syslogd 进程

13.2 syslodg守护进程

  • syslogd守护进程通常由系统初始化脚本进行启动,并在系统工作时间一直运行,启动步骤如下:
    • 读取配置文件,在 /etc/syslog.conf 配置文件指定守护进程收取的各种日志消息应如何处理。可能添加到一个文件中,或被写到用户的登录窗口,或被转发给另一个主机上的 syslogd 进程
    • 创建 Unix 域数据报套接字,给它捆绑路径名 /var/run/log
    • 创建 UDP 套接字,捆绑 514 端口,接收别的主机发送过来的日志
    • 打开路径名 /dev/klog来自内核的任何出错消息从这个设备输入
    • syslog 使用 select 来监听上面 2,3,4 步的描述符来接受日志,并按照配置文件进行处理。如果守护进程读取 SIGHUP 信号,就重新读取配置文件
  • 最新的系统不建议开启 514 端口,会遭到攻击

13.3 syslog

  • 守护进程没有终端,所以不能把消息 fprintfstderr 上。从守护进程中登记消息的常用技巧是调用 syslog 函数
  • logger 命令在 shell 脚本中以向 syslogd 发送消息
#include <syslog.h>
void syslog(int priority, const char * message, ...);
  • 参数解析:
    • priority:级别(level)和设施(facility)两者的组合体
    • message:类似 printf 格式串,增加了 %m 规范代表当前 errno
  • syslog 被应用进程首次调用时,它创建一个 Unix 域数据报套接字,然后调用 connect 连接到由 syslogd 守护进程创建的 Unix 域数据报套接字的众所周知的路径名。这个套接字一直打开,直到进程终止关闭. 可以在syslog使用前调用openlog,在不需要发送日志时,调用closelog(注意openlog并不会立即创建套截止,除非指定NDELAY选项)
    • void openlog(const char *ident, int options, int facility);
    • void closelog(void);
    • 参数
      • ident是一个由syslog冠名的每个日志消息之前的字符串,通常是程序的名字
      • options有多和常值的逻辑构成
optiions 说明
LOG_CONS 若无法发送到syslogd守护进程则登记到控制台
LOG_NDELAY 不延迟打开,立即创建套截止
LOG_PERROR 即发送到syslogd守护进程,又发送到标准错误输出
LOG_PID 随每个日志消息登记进程ID
  • 日志消息的level
level 说明
LOG_EMERG 0 系统不可用(最高优先级)
LOG_ALERT 1 必须立即采取行动
LOG_CRIT 2 临界条件
LOG_ERR 3 出错条件
LOG_WARNING 4 警告条件
LOG_NOTICE 5 正常然而重要的条件(默认值)
LOG_INFO 6 通告消息
LOG_DEBUG 7 调试级消息(最低优先级)
  • 标识消息发送进程类型的facility
facility 说明
LOG_AUTH 安全/授权消息
LOG_AUTHPRIV 安全/授权消息(私用)
LOG_CRON cron守护进程
LOG_DAEMON 西东守护进程
LOG_FTP FTP守护进程
LOG_KERN 内核消息
LOG_LOCAL0 本地消息
LOG_LOCAL1 本地消息
LOG_LOCAL2 本地消息
LOG_LOCAL3 本地消息
LOG_LOCAL4 本地消息
LOG_LOCAL5 本地消息
LOG_LOCAL6 本地消息
LOG_LOCAL7 本地消息
LOG_LPR 行式打印机系统
LOG_MAIL 邮件系统
LOG_NEWS 网络新闻系统
LOG_SYSLOG 由syslogd内部产生的消息
LOG_USER 任意的用户级消息(默认)
LOG_UUCP UUCP系统
  • 例如,当rename函数调用失败时,守护进程执行以下调用:
    • syslog(LOG_INFO|LOG_LOCAL2, "RENAME(%s,%s): %m", file1, file2);

13.4 daemon_init

  • 编写一个守护进程的创建函数,有些系统(如Linux)提供 daemon 函数用来创建守护进程,和本程序类似
  • 守护进程在没有终端的环境下运行,不会接收 SIGHUP 信号。许多守护进程把这个信号可以当作系统发送的通知,表示配置文件发送了变化,应重新读取配置文件,类似的还有 SIGINT SINGWINCH信号
#include	"unp.h"
#include	<syslog.h>
#define	MAXFD	64
extern int	daemon_proc;	/* defined in error.c */
int daemon_init(const char *pname, int facility){
   
	int		i;
	pid_t	pid;
    // 调用 fork 创建子进程,然后直接终止父进程,留下子进程继续执行。
    // 如果是在 shell 中执行的程序,父进程终止,shell 会认为程序已经结束了,子进程就可以在后台执行了
    // 子进程继承父进程的进程组 ID,但它有自己的进程 ID,这就保证了子进程不是一个进程组的头进程,这是接下来调用 setsid 的必要条件
	if ( (pid = Fork()) < 0)
		return (-1);
	else if (pid)
		_exit(0);			/* parent terminates */

	/* child 1 continues... */
    // setsid 用来创建一个新的会话。当前进程变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端
	if (setsid() < 0)			/* become session leader */
		return (-1);
    // 忽略 SIGHUP 信号,并再次调用 fork。该函数返回时,同样只使用子进
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值