笔记:UNIX网络编程卷1(第3版)

一. TCP/UDP基础知识

二. I/O复用

  • select
  • epoll

1.1 TCP/UDP对比

|items|TCP|UDP| |:----:|:----:|:----:| |连接和建立|三次握手|无需连接操作,直接发送| |数据发送|进程缓冲区复制到发送缓冲区,如果发送缓冲区容纳不下阻则塞直到发送缓冲区为空继续复制不存在进程缓冲区,所以write返回成功只能说明发送缓冲区可用,并不能说明数据已发送到对端,发送缓冲区内容保留直到收到正确回复|进程缓冲区直接复制到内核缓冲区,过大返回错误,否则直接发送,并丢弃已发送数据| |流程|bind/listen/accept/connect|sendto/recvfrom| |包结构|地址/序号/拥塞控制/校验/偏移等+数据|信息/校验和/长度+数据|

1.2 TIME_WAIT状态

![](https://img2018.cnblogs.com/blog/1214153/202002/1214153-20200212014019494-1786701069.png) + TIME_WAIT状态存在的理由: + 1)可靠地实现TCP全双工连接的终止 保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
  • 2)允许老的报文在网络中消逝
    防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

1.3 缓冲区大小及限制

+ 两个主机之间路径的最小MTU称之为路径MTU,1500的MTU在当今以太网中比较常见;两个主机之间相反方向上的MTU可以不同,因为以太网中路由选择往往是不对称的. + 当一个IP数据报文从某一个接口发出时,如果她得大小超过链路MTU,IPv4和IPv6将执行分片,这些分片在到达对端之前不会重组. + 如有必要主机和路由器都会对IPv4数据报进行分片,IPv6主机会执行分片,路由器不会;如果IPv4首部的DF位被置位,即表示对当前的数据报不进行分片出来,那么发送的主机以及转发的路由器都不能对其分片,如果数据报大小超过路由外出链路的MTU,则会产生一个"目标不可达,需要分片但DF位已设置"的错误.因为IPv6的数据报经过路由器不会分片(其隐含DF位),数据长度超了时会返回错误"分组过大". + v4和v6协议都定义了最小重组缓冲区,且要求必须实现,v4 576字节,v6 1500字节;因此UDP按照小于等于576字节发送数据是安全的. + TCP有一项参数MSS(最大分节大小),用于通知对端所能发送的最大数据长度.IPv4 MSS一般设置为1460,即MTU-(TCP+IP)首部固定长度,用来避免分片;IPv6首部固定长度为40,所以IPv6的MSS一般设置为1500-(20+40) = 1440.

1.4 socket相关结构及细节

+ IPv4 套接字地址定义头文件为中,结构名为sockaddr_in; ```c struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { unit_8 sin_len; sa_familay_t sin_family; in_port_t sin_port; struct in_addr sin_addr; char sin_zero[0]; //保留 }; ``` IPv4地址有两中访问方法,svr.sin_addr访问时表示按照in_addr结构引用其中的32位地址;而用svr.sin_addr.s_addr方式访问时,值为无符号32位整形. 使用前习惯上使用bzero或memset等函数将结构整体置0,再重新赋值地址端口等字段. 套接字地址结构仅用在主机上,其结构并不在主机间传送. + 通用套接字地址结构sockaddr ```c sockaddr { unit8_t sa_len; sa_familay_t sa_familay; char sa_data[14]; }; ``` 用于传递给内核时强转ipv4/ipv6/域套接字地址,因为内核对地址结构各字段的使用和应用层不同. bind/connect函数参数为通用套接字结构,所以调用的时候需要借其强转. + 字节序处理,主机字节序的数值在用于网络参数前需要统一转换为网络字节序 ```c htons() // host 16bit value to net htonl() // host 32bit value to net ntohs() // host to net 16 ntohl() // host to net 32 ``` + 网络地址符号与数字之间转换 ```c //c 字符串转换为32位二进制地址 不常用 int inet_aton(const char*, struct in_addr*) // 常用 点分10进制地址转换为32bit二进制网络字节序地址,不可转换全255,接口缺陷 in_addr_t inet_addr(const char*) // 转换为点分十进制地址 char* inet_ntoa(in_addr_t) ```

1.5 socket各流程相关接口需注意地方

+ int socket(int family, int type, int protocol)
family type protocol
TCP AF_INET/AF_INET6 SOCK_STREAM 0
UDP AF_INET/AF_INET6 SOCK_DGRAM 1
域套接字 AF_LOCAL - -
路由套接字 AF_ROUTE - -
秘钥套接字 AF_KEY - -

-: 待补充

  • int bind(int fd, sockaddr*, socklen_t)
    绑定指定地址结构到指定的套接字上,服务端一般是需要调用此接口的,客户端可以省略这个接口,如果不指或指定一部分值,其余参数将有系统自行确定.

  • int connect(int fd, sockaddr*, socklen_t)
    客户端调用此接口将触发三次握手,如果发出的SYN没有收到响应,则会再尝试几次,如一直失败,返回超时;如果收到RST则表明当前连接的地址或端口不存在;如果收到回复,继续三次握手,成功后链接状态变为ESTABLISHED.

  • int listen(int fd, int backnum)
    调用此接口,服务端套接字进入监听状态.
    操作系统会为每个套接字维护两个监听队列,即等待连接队列和连接成功队列,backnum为两者之和;等待队列中的连接会转换为连接成功状态,前者出队,后者入队.
    当今互联网程序的backnum需要设置为一个较大的值.一般而言操作系统设置的值为backnum*1.5.

  • int accept(int fd, sockaddr* client, socklen_t*)
    服务端每调用当前函数一次,从已连接队列中取出一个连接,client为对端地址结构,因为地址结构和结构长度都是从内核态拿到的,所以都需要以指针形式传入.
    IPV4的len是固定的,类似于域套接字长度是不确定的,所以传入的len可能会被内核修改掉.

  • close(int fd)
    关闭套接字,向对端发送FIN;在并发服务(以fork创建子进程为例)中可能会对fd执行两次close,但并不会发送两次FIN,因为fd作用效果相当于文件句柄,每次引用都会导致其引用计数加一,所以每调用一次close实际作用效果是减小其引用计数,直到计数为0才会发送FIN.

  • read/write接口
    read/write函数正常返回并不代表数据发送成功,所以必须要检查其返回值再做进一步处理;比如循环对返回值和待发送数据总长度作对比,直到已发送数据数量等于总长度才能说明所有数据已发送;另外函数的返回不能代表数据已经发送到对端,只能说明已发送.

1.6 基于fork的并发服务常见问题梳理

+ 信号处理 + 僵尸子进程回收 + 连接状态问题
原文地址:https://www.cnblogs.com/zhangyi-studio/p/12297573.html