unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

时间:2022-05-13 08:15:36

13~22章 重要

第2章 传输层: TCP/ UDP / STCP (Stream Control Transmission Protocol)

TCP 可靠,有重传机制,SYN队列号

UDP 不可靠

STCP 可靠(还包装了一些其他的)

TCP,STCP 协议中 有数据包的序列号SYN,和重传机制, 保证了数据的正确性,可靠性。

SYN就是序列号,ACK 是回应


TCP 连接的建立 和终止

2.6.1 TCP建立连接

TCP建立连接有3路分节 ( 容易被攻击,因为server要等待client 的ack包,  时间是 2*MSL  1~4min)
client SYN J
server SYN K,ACK J+1
client ACK K+1

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

2.6.2 TCP 选项

每个SYN有多个选项
- Maximum Segment Size 最大分节大小 简称MSS
每个TCP 分节中愿意接收的最大数据量(发送端用接收端的MSS作为 分节最大SIZE)
7.9节 TCP_MAXSEG 修改分节大小
- Window Scale Option
在TCP 协议首部中占16bit,所以最大是65535 Notice:
RFC1323[Jacobson,Braden,and Borman 1992]为了获取更大的吞吐量,新选项制定TCP首部中Window Scale Option扩大(左移)0~14 bit
所以最大是1GB (65535×2^14)(7.5节 修改 SO_RCVBUF 选项)
前提,必须2个OS 都支持此选项 - Timestamp Option
時间戳防止 后面发送的数据已经接受完毕, 前面发送的数据,因为路由原因导致 延迟到达(因为没有收到,所以系统会重发),出现2个SYN一样的数据包 最后2个选项 在TCP volume 1中第24章有详细说明

2.6.3 TCP connection termination ,TCP 终止连接

TCP 的终止是4个分节
client FIN M
server ACK M+1
server FIN N
client ACK N+1

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

  1. One application calls close first, and we say that this end performs the active close. This end's TCP sends a FIN segment, which means it is finished sending data.

    某个应用首先调用close,我们称该端 执行主动关闭(active close), 该端TCP发送FIN分节,表示数据发送完毕。
  1. The other end that receives the FIN performs the passive close. The received FIN is acknowledged by TCP. The receipt of the FIN is also passed to the application as an end-of-file (after any data that may have already been queued for the application to receive), since the receipt of the FIN means the application will not receive any additional data on the connection.

    接收到其他对端 发送的FIN,我们称为 执行被动关闭(passive close).这个FIN由TCP确认。
  1. Sometime later, the application that received the end-of-file willclose its socket. This causes its TCP to send a FIN.

    application接收到end-of-file之后,会close关闭套接字, 导致TCP发送FIN
  1. The TCP on the system that receives this final FIN (the end that did the active close) acknowledges the FIN.

    与SYN一样, FIN 也是占据一个字节的序列号空间,每个FIN的ACK确认号就是这个FIN序列号+1

具体看下图

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

2.6.4 TCP State Transition Diagram

基本的共有11个不同的状态,可以用netstat 显示

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

2.6.5 观察分组

下图展示了完整的TCP连接所发生的实际分组交换情况,包括连接建立/数据传送和连接终止3个阶段,

还展示了每个端点所经历的TCP状态

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

Client MSS 为536(这个值是最小重组缓冲区大小),Server MSS 为1460, 不同方向MSS可以不同


TIME_WAIT 状态

MSL maximumsegment lifetime 最长分节时间(RFC1122 建议是2min, 但是Berkeley 是30s)

所以2×MSL 是1~4min

lost duplicate

wandering duplicate

TIME_WAIT状态有两个存在的理由

1)可靠地实现TCP双全工连接的终止

2)允许老大重复分节在网络中消逝


SCTP的建立和终止

 SCTP的建立也是4个分节 ( 有效拒绝了攻击,因为SYN不必等待client 的ack包)
client init
server init ack
client cookie echo
server cookie ack SCTP的终止是3个分节
client shutdown
server shutdown ack
client shutdown complete

SCTP 状态转换图

SCTP观察分组

SCTP选项


2.9 端口

16bit 整数 0~65535

  • 0~1023(unix保留端口) 由IANA(Internet assigned numbers authority)维护, 分配和控制,清单作为RFC发布, RFC1700[1994] ,RFC3232 [2002]

  • 1024~49151 register port 已登记端口

  • 49152~65535 动态或者私用端口,也称临时端口,


2.11 缓冲区大小及限制

  • IPV4数据包的最大size是65535byte, 包括IPV4首部

  • IPV6数据包的最大size是65575byte, 包括40byte IPV6首部

    IPV6 有jumbo payload 选项,把Jumbo Payload长度字段 扩展到32位 (4GB),不过这个选项需要MTU超过65535的数据链路提供支持,

    一般来说这是为了host到host内部连接而设计的,比如HIPPI,他们通常没有内在的MTU

    (看来是本地socket 类似吧,用于IPC)

    Without special options, a payload must be less than 64KB.With a Jumbo Payload option (in a Hop-By-Hop Options extension header), the payload must be less than 4 GB.

  • MTU(Maximum transmission unit)许多网络可以由硬件 规定MTU , 一般MTU是1500 (ifconfig eth0可以看到)

  • path MTU 在两个主机之间的路径中,最小的MTU。 (简单说:由MTU最小值来决定)

  • 分片(fragmentation),如果IP数据包发送時,SIZE超过链路MTU,将会执行分片, 在所有分片数据,都到达目的地才会重组(Reassembly)

IPV4对packet分片,IPV4的路由 也会对数据进行分片(不会Reassembly),在用户主机 才 Reassembly。

IPV6对packet分片,IPV6的路由 则不会对数据进行分片(会Reassembly)。

可以参考以下链接

https://en.wikipedia.org/wiki/IP_fragmentation

https://en.wikipedia.org/wiki/IPv4#Fragmentation_and_reassembly

https://en.wikipedia.org/wiki/IPv6

https://en.wikipedia.org/wiki/IPv6_packet#Fragmentation

IPv4 and IPv6 differences

The details of the fragmentation mechanism, as well as the overall architectural approach to fragmentation, are different between IPv4, the first official version of the Internet Protocol, and IPv6, the newer version. In IPv4, routers perform fragmentation, whereas in IPv6, routers do not fragment, but drop the packets that are larger than their MTU. Though the header formats are different for IPv4 and IPv6, analogous fields are used for fragmentation, so the same algorithm can be reused for IPv4 and IPv6 fragmentation and reassembly.

In IPv4, hosts must make a best-effort attempt to reassemble fragmented IP datagrams with a total reassembled size of up to 576 bytes (the minimum MTU for IPv4). They may also attempt to reassemble fragmented IP datagrams larger than 576 bytes, but they are also permitted to silently discard such larger datagrams. Applications are recommended to refrain from sending datagrams larger than 576 bytes unless they have prior knowledge that the remote host is capable of accepting or reassembling them.[6]:12

In IPv6, hosts must make a best-effort attempt to reassemble fragmented datagrams with a total reassembled size of up to 1500 bytes, larger than IPv6's minimum MTU of 1280 bytes.[7] Fragmented datagrams with a total reassembled size larger than 1500 bytes may optionally be silently discarded. Applications relying upon IPv6 fragmentation to overcome a path MTU limitation must explicitly fragment the datagram at the point of origin; however, they should not attempt to send fragmented datagrams with a total size larger than 1500 bytes unless they know in advance that the remote host is capable of reassembly.

  • IPv4首部的 不分片位 (don't fragment)即DF位,若被设置,则不管是发送数据到主机,还是转发的路由,都不会分片。

当IPV4路由接收到一个超过其 输出链路的MTU大小的数据包,且设置了DF位,将产生一个ICMPv4【"destination unreachable, fragmention needed but DF bit set" 目的地不可达,需要分片,但是DF位已设置】的error信息

既然IPv6在路由上不分片,每个IPv6数据报于是隐含一个DF位。

当IPv6路由器接收到超过输出链路MTU大小的IPv6 数据报时,将产生一个ICMPv6“packet too big ”的错误信息

路径MTU在互联网中是有问题的,现在很多防火墙会丢弃ICMP消息,导致TCP永远收不到 要求降低数据分片值的信息。
但是IETF已经在考虑定义 路径MTU发现方法,不依赖ICMP的出错信息
  • IPv4和IPv6都定义了最小 重组缓冲区大小(minimmum reassembly buffer size)

手上没有英文版本, 但是中文是写错了(写成1500 bytes了)。

IPV4 最大MTU是1500 bytes,最小MTU是 576 bytes

IPV6 最大MTU是1500 bytes,最小MTU是 1280 bytes

https://tools.ietf.org/html/rfc2460

IPv6 requires that every link in the internet have an MTU of 1280 octets or greater.

  • MSS maximum segment size ,最大分节大小,用于向对端TCP通告对端 在每个分节中能发送的最大TCP数据量。

MSS的目的是告诉对端其 重组缓冲区大小的实际值,从而避免分片。

MSS经常设置成【MTU-IP-TCP首部 的固定长度】

在以太网中

使用IPv4的MSS值为1460bytes, MTU [1500 byte] - IPv4 [20 byte] - TCP 首部[20 byte] = 1500 -20 -20 = 1460

使用IPv6的MSS是1440bytes, MTU [1500 byte] - IPv6 [40 byte] - TCP 首部[20 byte] = 1500 -40 -20 = 1440

没有 jumbo payload 的IPv6 数据包中的最大TCP数据量为 65535 -20 = 65515(只需要减去TCP首部20字节)

65535被表示“无限”的一个特殊值(只有用到jumbo payload 选项才使用)

如果使用【jumbo payload 选项】并且接收的对端MSS为65535,那么发送数据包的大小限制就是接口MTU,如果值太大(也就是说 所在路径中某个MTU较小),那么MTU发现功能将确定这个较小值(通过error信息)

  • SCTP基于对端 所有地址中 发现最小路径MTU,保持一个分片点, 这个最小MTU大小用于把较大的用户分割成较小的能够以单个IP数据报发送的数据,SCTP_MAXSEG选项,可以修改该值。(使用户可以使用设置一个更小的 MTU 分片值)
分片fragmentation与分段(分节)segment  的区别??

分片(fragmentation),如果IP数据包发送時,SIZE超过链路MTU,将会执行分片, 在所有分片数据,都到达目的地才会重组(Reassembly).

MSS maximum segment size ,最大分节大小,用于向对端TCP通告对端 在每个分节中能发送的最大TCP数据量。
MSS的目的是告诉对端其 重组缓冲区大小的实际值,从而避免分片。

2.11.1 TCP 输出

每个TCP套接字有一个发送缓冲区,可以使用SO_SNDBUF 来更改缓冲区大小(详见7.5节)

接收是SO_RCVBUF

还有SO_REUSEADDR、SO_REUSEPORT 这2个是复用addres和port,否则用同一个address 和port 建立多个TCP链接,会出现错误。


第3章

3.8 sock_ntop 和相关函数

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);//convert IPv4 and IPv6 addresses from binary to text form

第一个参数是 协议族, 第二个参数需要一个 套接字地址结构 的地址

//IPV4

struct sockaddr_in addr;

inet_ntop(AF_INET, &addr.sin_addr, str, sizeof(str));

//IPv6

struct sockaddr_in6 addr6;

inet_ntop(AF_INET6, &addr6.sin6_addr, str, sizeof(str));

所以就规定了一定要那种 协议, 可以封装下,同时支持IPv4和IPv6

比如

char* scok_ntop( const struct sockaddr *sa, socklen_t salen)
{
char poststr[8];
static char str[128];//unix domain is largest switch(sa->sa_famliy)
{
case AF_INET:
{ struct sockaddr_in * sin = (struct sockaddr_in*)sa;
if(inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) return (NULL);
if(ntohs(sin->sin_port) != 0)
{
snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port) );
strcat(str,portstr);
}
return (str);
}
case AF_INET6:
{
//........
}
}
}

static 静态成员导致该函数 不可重入,非线程安全,

网络函数中有很多都是如此,比如gethostbyname(), gethostbyaddr(),getservbyname(),getservbyaddr(), inet_ntoa()

getaddrinfo() gethostbyaddr() 可重入的前提是 调用的函数 都是可重入的.

而inet_ptoa,inet_ntop 是可重入的

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》


第4章 基本套接字编程

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

4.2 socket函数:

#include <sys/socket.h>
int socket(int family, int type, int protocol);//return: 若成功返回非负值(描述符), 若出错返回-1

socket函数 的family常用值
family      说明
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议(见15章 又称本地socket)
AF_ROUTE 路由套接字(见18章)
AF_KEY 密钥套接字(见19章)

socket函数 的type常用值
type                 说明
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字

socket函数 的 AF_INET 或 AF_INET6的 protocol常用值
protocol                说明
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

AF_XXX对比PF_XXX

AF_前缀 表示地址族

PF_前缀 表示协议族

历史上有这样的想法:单个协议可以支持多个地址族,用PF_值来实现创建套接字,AF_用于套接字地址结构。

但是支持 多个地址族的协议族,从未实现过!

所以一办场合用AF_就足够了。


4.3 connect 函数

TCP client 用connect函数来建立遇TCP服务器的连接

#include <sys/socket.h>

int connect( int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);//return: 若成功返回0, 失败返回-1

client 在调用connnect函数之前 不必非得调用bind函数

4.4 bind 函数

bind函数把一个本地协议地址 赋予一个套接字。

对于网际网协议,协议地址是32为的IPv4地址 或者128位的IPv6地址 与 16位端口号

inclue <sys/socket.h>
int bind(int sockfd, const struct scokaddr * myaddr, socklen_t addrlen);//renturn: 成功返回0,错误返回-1
  • 服务器在启动时 绑定端口。

    如果一个TCP client或者server未曾调用bind 绑定一个端口,当我们调用connect 或listen时,内核就要为相应的套接字选择一个临时端口。

让内核来选择临时端口对于TCP client端是正常的,除非应用需要一个预留端口;

但是在server端一般都是通过固定端口去 和server端通信。(所以server一般要固定IP地址 和 端口)

进程在接受连接(TCP)或者接收数据报(UDP)之前必须调用bind(),因为客户端进程需要同已知的地址建立连接 或 发送数据。

  • 进程可以把一个特定的IP地址 绑定到他的socket上,不过这个IP地址必须属于其 所在主机的网络接口之一。

对于TCP client端,这就为了在 该socket上发送的IP数据报 指派了源IP地址。

对于TCP server端,这就限定该socket 只接收那些目的地 为这个IP地址的客户端连接。

TCP客户端通常不把IP地址绑定到他的socket上。 当连接socket时,内核将根据所用 外出网络接口 来选择源IP地址,而所用的外出接口 取决于到达服务器的路径。

比如一个client 有2个网口, 是联通+电信,(如果 切换网络到铁通、 某一个(电信)带宽机房故障等等因素) 都可以发送数据到server, 但是

如果bind 之后那就是固定某个网 发送数据到server

bind可以指定IP地址或端口,可以2个都指定,也可以2个都不指定。

下图给出了, bind 函数 指定要绑定的IP地址和端口号,产生的结果

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

如果指定端口号为0,那么内核就在bind被调用时选择一个临时端口。

如果指定IP地址为通配地址,那么内核将等到socket已连接(TCP)或已在socket上发出数据报(UDP)时才选择一个本地IP地址。

IPv4:通配地址由常值INADDR_ANY来指定,其值默认为0。它告知内核去选择IP地址,

struct sockaddr_in servaddr;

servaddr.sin_addr.s_addr = htol(INADDR_ANY);//wildcard

IPv6: IPV6的IP地址为128位, 就无法用INADDR_ANY 32位来赋值,

struct sockaddr_in6 serv;

serv.sin6_addr = in6addr_any;//wildcard

系统预先分配in6addr_any变量并将其初始化 为常值IN6ADDR_ANY_INIT。 头文件<netinet/in.h> 中声明extern in6addr_any。

ANADDR_ANY 值为0,所以无论 主机字节序(小端) 还是 网络字节序 (大端),都是一样的。

但是 头文件<netinet/in.h> 中定义的所有ANADDR_XX 常值都是按照主机字节序定义的,所以最好使用 htonl()

如果让内核来选择临时端口,那么需要用getsockname 来获取协议地址。

bind 函数经常出现EADDRINUSE 错误,"address alrady in use",解决方法是 7.5 SO_REUSEADDR和SO_REUSEPROT 这两个socket选项。

4.5 listen 函数

listen函数仅由TCP服务器端调用。它做两件事。

1)当socket函数创建一个套接字时,它被假设为一个主动套接字,

也就是说,他是将调用connect发起连接的客户端套接字。listen 函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。

下图2-4 TCP状态转换, 调用listen导致套接字 从CLOSED状态 转换到 LISTEN状态。

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

listen 通常在调用socket()和bind() 之后,在调用accpet()之前.

2)第二个参数规定了内核为该套接字 排队的最大连接 个数。

include <sys/socket.h>
int listen(int sockfd, int backlog);//return:成功返回0,错误返回-1.
为了理解第二个参数backlog,需要了解内核为任何一个给定的监听套接字 维护两个队列:
1)未完成连接队列(incomplete connection queue)
每个这样的SYN分节对应其中一项:已由某客户端发出并到达 服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。(图2-4)

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

2)已完成连接队列(completed connection queue)
每个已完成TCP三路握手过程的客户对应其中一项。 这些套接字处于ESTABLISHED状态(图2-4)

unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

在第三个分节,客户对服务器SYN的ACK (超时时间 Berkeley实现为75s)

关于以上 两个队列的处理,有几点需要考虑:

  • listen函数的backlog参数曾被规定为这个两个队列的总和的最大值。

  • 源自berkeley的实现给backlog增加了一个模糊的隐私(fudge factor):把它乘以1.5得到未处理队列最大长度(TCPv1 page257, TCPv2 page462)

    举例 backlog 指定为5,实际上允许最多有5*1.5= 8 个消息队列.( 当然不同系统 还有不同的算法)(图4-10)

  • 不要把backlog 设为0

  • 在三路握手正常完成的前提下,(也就是说没有丢失分节,从而没有重传),未完成连接队列中的任何一项在其中的存留时间为RTT,而RTT取决于特定的客户与服务器。

    (TCPv3 14.4节 指出,对于一个web服务器,许多客户与单个服务器之间的平均值RTT为187ms,当然随意可以指定这个值)

  • 历史上backlog值为5(BSD4.2 支持的最大值),但是现在每秒处理几百万连接的服务器来说,太少了,TCPv3 page187~192,需要修改这个值。

  • backlog值为5,不够,那么进程应该指定多大的值? 修改后如何编译更新? 允许通过环境变量 LISTENQ 修改backlog值。

  • backlog 值应该设的比你想象中更大的原因:随着客户SYN分节的到达,未完成连接队列中的数量可能还会增长,它们还在 等着进行三路握手的完成。

  • 当一个客户SYN到达时,若【未完成队列】是满的,TCP就忽略该分节(TCPv2 page930~931),也就是不发送RST。

    这么做的原因是: 这种情况是暂时的,客户端TCP将重发SYN,期望不久就能进入【未完成队列】, 但若是服务器响应一个RST,客户的connect()调用就会立即返回一个错误,强制应用进程处理这种case。

    另外,客户端无法区别,相应SYN的RST,是“该端口没有服务在监听”,还是“该端口有服务监听,不过队列满了”。

    所以正确的做法是:让TCP的正常重传机制来处理。

  • 在三路握手完成后,但在服务器调用accpet之前到达的数据应由服务器TCP排队,最大数据量为相应已连接套接字的接收缓冲区大小。

SYN flooding 攻击

高速率给 受害主机 发送SYN程序,用以填装一个或者多个TCP端口的未完成连接,

此程序的每个SYN源IP地址都设置成随机数,IP欺骗(IP spoofing),

导致服务器SYN/ACK 发往各种地方,同时防止受攻击服务器 获取hacker的真实IP。这样通过以伪造的SYN装填未完成队列,使合法SYN排不上队,导致针对合法客户的服务被拒绝(denial of service).

解决方案有2种:=>[Borman 1997c]

4.7 accpet函数

  accpet函数由TCP服务器调用,用于从 已完成连接队列 队列头部 返回下一个已完成连接(图4-7)

如果已完成连接队列为空,那么进程将被投入睡眠(假定套接字为 默认阻塞方式)

include <sys/socket.h>
int accpet (int sockfd, struct sckaddr *cliaddr, socklen_t *addrlen);//return:若成功返回非负的描述符,错误则返回-1 如果accpet成功,那么其返回值是由内核自动生成的一个 全新的描述符,代表与所返回客户的TCP连接, Notice: 需要注意2个参数,区分这2个参数很重要。
>> sockfd: 为监听套接字描述符(listening socket descriptor),由socket()函数创建,随后用作bind()和listen()
>> 返回值: 为已连接套接字描述符(connected socket descriptor)

此函数最多返回3个值:

  1. 函数结束的 返回值,
  2. 客户端的地址(cliaddr指针的内容)
  3. 客户端的地址size (cliaddr指针的内容的大小=> addrlen)

图1-9 展示了这些指针

已连接套接字每次都在循环中关闭,但监听套接字在服务器的整个 有效期都保持开放。

若不想了解客户端的地址,第二,第三个参数可以为空指针。 accpet(sockfd,NULL,NULL);

include "unp.h"
include <time.h>
int main(int argc, char ** argv)
{
int listenfd,connfd;
socklen_t len;
struct sockaddr_in servaddr,cliaddr;
char buf[MAXLINE];
time_t ticks; listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htonl(13);//自己定义的daytime server Bind(listenfd, LISTENQ);
for(;;){
len = sizeof(cliaddr);
connfd = Accept(listefd, (SA*)&cliaddr, &len);
//printf();
ticks = time(NULL);
Write(connfd, sizeof(buf), "%.24\r\n",ctime(&ticks));
Close(connfd);
}
}

4.7 fork() 和exec() 函数

fork()创建子进程:fork()调用一次,返回2次,
include <unistd.h>
int pid_t fork(void);//return:在自进程中为0; 在父进程中为子进程ID;若出错为-1 。 父进程先返回,返回值=》是新派生进程(称为子进程)ID号。
子进程再返回,返回值=》为0. Tips:
子进程可以getpid()得到父进程的进程ID。
父进程则无法获取各个子进程的ID,如果父进程想要跟踪所有子进程ID, 就必须在每次调用fork()的时候记录子进程ID。 exec()执行可执行文件
include <unistd.h>
int execl(const char * pathname, const char * arg0, ... /* (char*) 0 */ );
int exevc(const char *pathnamem, char *const *argv[]);
int execle(const char *pathname, const char *arg0,... /* (char*) 0, char *const envp[] */ );
int execve(const char *pathname, char * const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, .../* (char*) 0 */ );
int execvp(const char *filename, char *const argv[]);
返回值全部一样:若成功则不返回,失败返回-1; ![](http://images0.cnblogs.com/blog2015/541722/201508/272223354846619.jpg)
6个函数的区别,这个就不细说了,将来再看。

进程在调用exec()之前打开着的描述符通常跨exec 继续保持打开。

默认行为可以使用fcntl 设置FD_CLOEXEC 描述符禁止掉,inetd 服务器就是 利用这种特性。(具体在 13.5 细说)

http://linux.die.net/man/2/fcntl

FD_CLOEXEC, the close-on-exec flag. If the FD_CLOEXEC bit is 0, the file descriptor will remain open across an execve(2), otherwise it will be closed.

4.8 并发服务器

这里只是将 用fork()不断创建子进程 来应对更多的客户端。没什么好说的。

4.9 close()函数

 通常Unix close()用来关闭套接字,并终止TCP连接。

include <unistd.h>
int close(int sockfd);//returne:若成功则返回0, 失败返回-1

close 一个TCP套接字的默认行为是把该套接字 标记成已关闭,然后立即返回到调用进程。

该套接字描述符不能再由调用进程使用,

描述符引用计数

4.8节 说过,并发服务器中,父进程关闭已连接套接字只是导致相应描述符的引用计数-1。既然引用计数仍 大于0,这个close调用并不引发TCP的四分组连接终止序列。

对于父进程与子进程共享已连接的套接字的并发服务器中,这正是所期望的。

如果我们确实想在某个TCP连接上发送一个FIN,那么可以改用 shutdown()函数 6.6节, 以代替close().

如果每次accpet()之后,没有close 那将导致 1> FD资源耗尽, 2> 没有一个客户端连接 会被终止。(连接一直打开着)

4.10

getsockname() 返回某个socket关联的 本地协议地址

为何需要这个函数:

1) 在一个没有调用bind 的TCP客户端,connect 成功返回后, getsockname用于返回由内核赋予该链接的本地IP Address和 本地port

2) 在以端口号0 调用bind(告知内核去选择本地 端口号)后,getsockname用于返回由内核赋予的本地端口号

3) getsockname可用于获取某个socket 的地址族协议

4) 在一个以通配IP地址 调用bind的TCP服务器上, 与某个客户的链接一旦建立(accpet成功返回),getsockname就可以返回由内核赋予该连接的本地IPaddress.

在这样的调用中,socket描述符 必须是已连接socket 的描述符,而不是监听socket的描述符。

getpeername() 返回摸个socket关联的外地协议地址

为何需要这个函数:

1)当一个服务器是由调用过accept的某个进程 通过调用exec执行程序时,它能获取客户身份的唯一途径就是getpeername


第5章 TCP client/server 程序 例子 (没有大篇幅多说)

POSIX 信号处理

信号(signal) 就是告知某个进程发生了某个时间的通知,(也称软件中断 software interrupt) 一般异步发生

信号:

  • 由一个进程发给另一个进程
  • 由内核发给某个进程

处理SIGCHLD信号

wait(int * statloc);

waitpid(pid_t pid, int *statloc, int options);

等待子进程 全部结束后,才关闭父进程, 所以要父进程 要等 子进程 发送SIGCHLD信号,才能关闭