【读过的书,留下的迹】Unix网络编程

前言

因这本书知道Richard Stevens这位神级大牛,感慨一个人如何能写了这么多本被奉为圣经级别的IT图书,我们又有什么理由不去读这些书呢?

第3章:套接字编程简介

(1)套接字地址结构



(2)字节排序

  • 主机字节序有可能是小端字节序,也有可能是大端字节序
  • 网际协议统一采用大端字节序

字节序转换函数

// h for host, n for network, s for short, l for long
unit16_t htons(unit16_t host16bitvalue);
unit32_t htonl(unit32_t host32bitvalue);
unit16_t ntohs(unit16_t net16bitvalue);
unit32_t ntohl(unit32_t net32bitvalue);

(3)地址转换函数

p代表presentation,n代表numeric



第4章:基本TCP套接字编程

(1)TCP套接字各函数调用



(2)fork和exec

  • for有两个用法
    • 一个进程创建一个自身的副本
    • 一个进程执行另一个程序,先fork,再调用exec把自身替换成新的程序

每个文件或套接字都有一个引用计数器,父进程调用close只是导致相应描述符的引用计数器减1,想要立即关闭,应该调用shutdown

第5章:TCP客户/服务器程序示例

(1)正常终止

  • 客户端终止,发送FIN给服务器,服务器响应ACK(四次挥手前两次)
  • 服务器子进程关闭,子进程触发四次挥手后两次
  • 服务器子进程发送SIGCHLD信号给父进程,如果父进程未处理,子进程进入僵死状态

(2)SIGCHLD信号处理

  • 当子进程终止提交SIGCHLD信号时,若父进程未处理,子进程僵死(zombie),以便父进程以后获取子进程的相关信息
  • 父进程可调用wait或者waitpid处理SIGCHLD信号
  • wait是阻塞的,而unix信号是不排队的,这将导致部分信号丢失
  • waitpid则是非阻塞的,能较好处理所有信号

(3)accept返回前连接终止



* POSIX指定返回的errno必须是ECONNECONNABORTED

(4)服务器进程终止(服务器能发出FIN)

  • 服务端发起四次挥手的前两次,服务端到客户端方向连接关闭
  • 当客户端再向服务端请求时,服务端响应一个RST(复位)
  • 在本章例子中(未使用select和poll),客户端看不到这个RST,因为其调用writen后随机调用readline
  • 由于客户端已收到FIN,所有readline立即返回0(代表EOF),以出错信息“server terminated prematurely“退出

(5)服务器主机奔溃(服务器不能发出FIN)

  • 客户端TCP持续重传数据分节,试图从服务器接收ACK
  • 客户端的数据分节没有响应,返回ETIMEOUT
  • 或者某个路由器判断服务器不可达,返回EHOSTUNREACH或者ENETUNREACH

(6)服务器主机奔溃后重启

  • 服务器丢失了奔溃前的所有连接,所以服务器TCP对于所收到的来自客户的数据分节响应一个RST
  • 客户TCP收到RST时,正阻塞于readline调用,导致返回ECONNRESET错误

(7)服务器关机

  • init进程给所有进程发送SIGTERM信号(可被捕获)
  • 等待一段时间后(5到20s),发送SIGKILL信号(不可捕获),随后发生服务器进程终止的步骤

(8)穿越socket传输二进制

  • 把所有数值数据作为文本串来传输
  • 现实定义所支持数据类型的二进制格式(位数、大端、小段字节)

第6章:I/O复用:select和poll函数

内核一旦发现进程指定的一个或多个IO条件就绪,它就通知进程。这个能力称为IO复用

(1)IO模型

  • 阻塞式IO
  • 非阻塞式IO
  • I/O复用
  • 信号驱动IO
  • 异步IO



  • 一个IO输入通常包括两个阶段
    • 内核等待数据准备好
    • 由内核空间向进程复制数据

(2)select、poll、epoll函数

select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 允许进程指示内核等待多个事件中的任何一个发生,并只在一个或多个事件发生或经历一段指定时间后才唤醒它
  • 缺点
    • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
    • 同时每次调用select都需要在内核遍历(轮询)传递进来的所有fd,这个开销在fd很多时也很大
    • select支持的文件描述符数量太小了,默认是1024

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);
  • poll函数的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
  • 优点
    • pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式
    • pollfd并没有最大数量限制

epoll

epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生

  • 改进
    • 每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
    • epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表
    • epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目

第7章:套接字选项

主要有两个函数:getsockopt和setsockopt

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

第8章:基于UDP套接字编程

UPD编程所用的函数



一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。每一个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字缓冲区。

(1)UDP的connect函数

UDP调用connect函数与TCP调用connect函数完全不同:没有三次握手过程,内核只是检查是否存在立即可知的错误,记录对端的IP地址和端口号,然后立即返回给调用过程

已连接的UDP比未连接的UDP有以下不同

  • 不能给输出操作指定目的IP地址和端口号
  • 不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg
  • 由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接收任何异步错误

当应用进程要给同一目的地址发送多个数据报时,显式连接套接字效率更高。因为未连接UDP每次发送数据均需要连接该UDP套接字

第11章:名字与地址转换

本章涉及的函数与域名有关系,需调用DNS服务器获取

(1) 获取主机

主机数据结构

struct hostent { 
   char *h_name;     /* 地址的正式名称 */
   char **h_aliases;     /* 空字节-地址的预备名称的指针 */
   int h_addrtype;     /* 地址类型; 通常是AF_INET */ 
   int h_length;     /* 地址的比特长度 */ 
   char **h_addr_list;     /* 主机网络地址指针 */ 
}; 

/* 由域名获取 */
struct hostent *gethostbyname(const char *hostname);
/* 由二进制ip地址获取 */
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);

(2)获取服务

struct servent  {
 char *s_name;         /* Official service name.  */
 char **s_aliases;     /* Alias list.  */
 int s_port;           /* Port number.  */
 char *s_proto;        /* Protocol to use.  */
};

/* 根据给定名字查找响应服务 */
struct servent *getservbyname(const char *name, const char *proto);
/* 根据给定端口号和可选协议查找响应服务 */
struct servent *getservbyport(int port, const char *proto);

(3)getaddrinfo、getnameinfo函数

  • gethostbyname和gethostbyaddr这两个函数仅支持IPv4。getaddrinfo函数能够处理名字到地址、服务到端口两种转换,而且与协议无关。
  • getnameinfo是getaddrinfo的互补函数,它以一个套接字地址为参数,返回描述其中的主机的一个字符串和描述其中的服务的另一个字符串。

(4)可重入性

  • gethostbyname、gethostbyaddr、getservbyname、getservbyport这4个函数是不可重入的,因为它们都返回指向同一个静态结构的指针
  • getaddrinfo可重入的前提是由它调用的函数都可重入
  • getnameinfo可重入的前提是由它调用的函数都可重入

第12章:IPv4和IPv6互操作性

IPv4和IPv6客户与服务器互操作性总结



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

所有服务,如FTP、Telnet、Rlogin、TFTP等,都有一个进程与之关联,这些进程都是在系统自举阶段从/etc/rc文件中启动,而且每个进程几乎执行相同的启动任务:创建一个套接字,把本服务器的众所周知端口捆绑到该套接字,等待一个连接或者数据报,然后产生子进程。存在两个问题:

(1)所有这些守护进程含有几乎相同的启动代码,即表现在创建套接字上,也表现在演变成守护进程上
(2)每个守护进程在进程表上占据一个表项,然而它们大部分时间处于睡眠状态

可用因特网超级服务器(即inetd守护进程)解决上述问题

(1)通过由inetd处理普通守护进程的大部分启动细节以简化守护进程的编写
(2)单个进程就能为多个服务器等待外来的客户请求,以此取代每个服务一个进程的做法

第14章:高级I/O函数

(1)套接字超时

  • 调用alarm,它在指定超时期满时产生SIGALRM信号
  • 在select中阻塞等待I/O,以此代替直接阻塞在read或write调用上
  • 使用较新的SO_RCVTIMEO和SO_SNDTIMEO

前两个技术适用于任何描述符,而第三个技术仅仅适用于套接字描述符

(2)套接字和标准I/O

套接字I/O:包括read和write这两个函数及它们的变体
标准I/O:这个函数由ANSI标准规范,意在便于移植到支持ANSI的非UNIX系统上

第15章:Unix域协议

Unix域协议是在单个主机上执行客户/服务器通信的一种方法,可视为IPC方法之一

  • 什么时候适用Unix域协议
    • Unix域套接字往往比通信两端位于同一主机的TCP套接字快一倍
    • Unix域套接字可用于在同一个主机上的不同进程之间传递描述符
    • Unix域套接字较新的实现把客户的凭证提供给服务器,能够提供额外的安全检查措施

第22章:高级UDP套接字编程

UDP的优势

  • UDP支持多播和广播
  • UDP没有连接建立和拆除

UDP的劣势

  • 丢失分组重传,重复分组检测
  • 窗口式流量控制
  • 慢启动和拥塞避免

建议

  • 多播和广播必须使用UDP
  • 对于简单的请求-应答应用程序可以使用UDP
  • 对于海量数据传输不应该使用UDP

给UDP增加可靠性

  • 超时和重传:用于处理丢失的数据报
  • 序列号:供客户验证一个应答是否匹配响应的请求
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页