diff --git a/README.md b/README.md index e5d502f..5cd275b 100644 --- a/README.md +++ b/README.md @@ -674,11 +674,11 @@ IP 是 Internet Protocol(网络协议)的简写,是为收发网络数据 IPv4 标准的 4 字节 IP 地址分为网络地址和主机(指计算机)地址,且分为 A、B、C、D、E 等类型。 -![](https://i.loli.net/2019/01/13/5c3ab0eb17bbe.png) +![](images/5c3ab0eb17bbe.png) 数据传输过程: -![](https://i.loli.net/2019/01/13/5c3ab19174fa4.png) +![](images/5c3ab19174fa4.png) 某主机向 203.211.172.103 和 203.211.217.202 传递数据,其中 203.211.172 和 203.211.217 为该网络的网络地址,所以「向相应网络传输数据」实际上是向构成网络的路由器或者交换机传输数据,然后由路由器或者交换机根据数据中的主机地址向目标主机传递数据。 @@ -826,12 +826,12 @@ CPU 保存数据的方式有两种,这意味着 CPU 解析数据的方式也 - 大端序(Big Endian):高位字节存放到低位地址 - 小端序(Little Endian):低位字节存放到低位地址 -![big.png](https://i.loli.net/2019/01/13/5c3ac9c1b2550.png) -![small.png](https://i.loli.net/2019/01/13/5c3ac9c1c3348.png) +![big.png](images/5c3ac9c1b2550.png) +![small.png](images/5c3ac9c1c3348.png) 两台字节序不同的计算机在数据传递的过程中可能出现的问题: -![zijiexu.png](https://i.loli.net/2019/01/13/5c3aca956c8e9.png) +![zijiexu.png](images/5c3aca956c8e9.png) 因为这种原因,所以在通过网络传输数据时必须约定统一的方式,这种约定被称为网络字节序(Network Byte Order),非常简单,统一为大端序。即,先把数据数组转化成大端序格式再进行网络传输。 @@ -1166,7 +1166,7 @@ TCP 是 Transmission Control Protocol (传输控制协议)的简写,意为 #### 4.1.1 TCP/IP 协议栈 -![](https://i.loli.net/2019/01/14/5c3c21889db06.png) +![](images/5c3c21889db06.png) TCP/IP 协议栈共分为 4 层,可以理解为数据收发分成了 4 个层次化过程,通过层次化的方式来解决问题 @@ -1196,7 +1196,7 @@ IP 层只关注一个数据包(数据传输基本单位)的传输过程。 这就是 TCP 的作用。如果交换数据的过程中可以确认对方已经收到数据,并重传丢失的数据,那么即便IP层不保证数据传输,这类通信也是可靠的。 -![](https://i.loli.net/2019/01/14/5c3c268b40be6.png) +![](images/5c3c268b40be6.png) #### 4.1.5 应用层 @@ -1206,7 +1206,7 @@ IP 层只关注一个数据包(数据传输基本单位)的传输过程。 #### 4.2.1 TCP 服务端的默认函数的调用程序 -![](https://i.loli.net/2019/01/14/5c3c2782a7810.png) +![](images/5c3c2782a7810.png) 调用 socket 函数创建套接字,声明并初始化地址信息的结构体变量,调用 bind 函数向套接字分配地址。 @@ -1254,7 +1254,7 @@ accept 函数受理连接请求队列中待处理的客户端连接请求。函 #### 4.2.5 TCP 客户端的默认函数调用顺序 -![](https://i.loli.net/2019/01/14/5c3c31d77e86c.png) +![](images/5c3c31d77e86c.png) 与服务端相比,区别就在于「请求连接」,它是创建客户端套接字后向服务端发起的连接请求。服务端调用 listen 函数后创建连接请求等待队列,之后客户端即可请求连接。 @@ -1294,7 +1294,7 @@ addrlen: 第二个结构体参数 servaddr 变量的字节长度 关系图如下所示: -![](https://i.loli.net/2019/01/14/5c3c35a773b8c.png) +![](images/5c3c35a773b8c.png) - 客户端只能等到服务端调用 listen 函数后才能调用 connect 函数 - 服务器端可能会在客户端调用 connect 之前调用 accept 函数,这时服务器端进入阻塞(blocking)状态,直到客户端调用 connect 函数后接收到连接请求。 @@ -1307,7 +1307,7 @@ addrlen: 第二个结构体参数 servaddr 变量的字节长度 在 Hello World 的例子中,等待队列的作用没有太大意义。如果想继续处理好后面的客户端请求应该怎样扩展代码?最简单的方式就是插入循环反复调用 accept 函数,如图: -![](https://i.loli.net/2019/01/15/5c3d3c8a283ad.png) +![](images/5c3d3c8a283ad.png) 可以看出,调用 accept 函数后,紧接着调用 I/O 相关的 read write 函数,然后调用 close 函数。这并非针对服务器套接字,而是针对 accept 函数调用时创建的套接字。 @@ -1345,10 +1345,10 @@ gcc echo_server.c -o eserver 在一个服务端开启后,用另一个终端窗口开启客户端,然后程序会让你输入字符串,然后客户端输入什么字符串,客户端就会返回什么字符串,按 q 退出。这时服务端的运行并没有结束,服务端一共要处理 5 个客户端的连接,所以另外开多个终端窗口同时开启客户端,服务器按照顺序进行处理。 server: -![server.png](https://i.loli.net/2019/01/15/5c3d523d0a675.png) +![server.png](images/5c3d523d0a675.png) client: -![client.png](https://i.loli.net/2019/01/15/5c3d523d336e7.png) +![client.png](images/5c3d523d336e7.png) #### 4.3.3 回声客户端存在的问题 @@ -1490,7 +1490,7 @@ gcc My_op_server.c -o myserver 结果: -![](https://i.loli.net/2019/01/15/5c3d966b81c03.png) +![](images/5c3d966b81c03.png) 其实主要是对程序的一点点小改动,只需要在客户端固定好发送的格式,服务端按照固定格式解析,然后返回结果即可。 @@ -1517,7 +1517,7 @@ gcc op_server.c -o opserver 结果: -![](https://i.loli.net/2019/01/16/5c3ea297c7649.png) +![](images/5c3ea297c7649.png) ### 5.2 TCP 原理 @@ -1527,7 +1527,7 @@ TCP 套接字的数据收发无边界。服务器即使调用 1 次 write 函数 实际上,write 函数调用后并非立即传输数据,read 函数调用后也并非马上接收数据。如图所示,write 函数调用瞬间,数据将移至输出缓冲;read 函数调用瞬间,从输入缓冲读取数据。 -![](https://i.loli.net/2019/01/16/5c3ea41cd93c6.png) +![](images/5c3ea41cd93c6.png) I/O 缓冲特性可以整理如下: @@ -1567,7 +1567,7 @@ TCP 套接字从创建到消失所经过的过程分为如下三步: TCP 在实际通信中也会经过三次对话过程,因此,该过程又被称为 **Three-way handshaking(三次握手)**。接下来给出连接过程中实际交换的信息方式: -![](https://i.loli.net/2019/01/16/5c3ecdec9fc04.png) +![](images/5c3ecdec9fc04.png) 套接字是全双工方式工作的。也就是说,它可以双向传递数据。因此,收发数据前要做一些准备。首先请求连接的主机 A 要给主机 B 传递以下信息: @@ -1599,7 +1599,7 @@ TCP 在实际通信中也会经过三次对话过程,因此,该过程又被 通过第一步三次握手过程完成了数据交换准备,下面就开始正式收发数据,其默认方式如图所示: -![](https://i.loli.net/2019/01/16/5c3ed1a97ce2b.png) +![](images/5c3ed1a97ce2b.png) 图上给出了主机 A 分成 2 个数据包向主机 B 传输 200 字节的过程。首先,主机 A 通过 1 个数据包发送 100 个字节的数据,数据包的 SEQ 为 1200。主机 B 为了确认这一点,向主机 A 发送 ACK 1301 消息。 @@ -1609,7 +1609,7 @@ TCP 在实际通信中也会经过三次对话过程,因此,该过程又被 与三次握手协议相同,最后 + 1 是为了告知对方下次要传递的 SEQ 号。下面分析传输过程中数据包丢失的情况: -![](https://i.loli.net/2019/01/16/5c3ed371187a6.png) +![](images/5c3ed371187a6.png) 上图表示了通过 SEQ 1301 数据包向主机 B 传递 100 字节数据。但中间发生了错误,主机 B 未收到,经过一段时间后,主机 A 仍然未收到对于 SEQ 1301 的 ACK 的确认,因此试着重传该数据包。为了完成该数据包的重传,TCP 套接字启动计时器以等待 ACK 应答。若相应计时器发生超时(Time-out!)则重传。 @@ -1624,7 +1624,7 @@ TCP 套接字的结束过程也非常优雅。如果对方还有数据需要传 先由套接字 A 向套接字 B 传递断开连接的信息,套接字 B 发出确认收到的消息,然后向套接字 A 传递可以断开连接的消息,套接字 A 同样发出确认消息。 -![](https://i.loli.net/2019/01/16/5c3ed7503c18c.png) +![](images/5c3ed7503c18c.png) 图中数据包内的 FIN 表示断开连接。也就是说,双方各发送 1 次 FIN 消息后断开连接。此过程经历 4 个阶段,因此又称四次握手(Four-way handshaking)。SEQ 和 ACK 的含义与之前讲解的内容一致,省略。图中,主机 A 传递了两次 ACK 5001,也许这里会有困惑。其实,第二次 FIN 数据包中的 ACK 5001 只是因为接收了 ACK 消息后未接收到的数据重传的。 @@ -1673,7 +1673,7 @@ TCP 与 UDP 的区别很大一部分来源于流控制。也就是说 TCP 的生 如图所示: -![](https://i.loli.net/2019/01/17/5c3fd29c70bf2.png) +![](images/5c3fd29c70bf2.png) 从图中可以看出,IP 的作用就是让离开主机 B 的 UDP 数据包准确传递到主机 A 。但是把 UDP 数据包最终交给主机 A 的某一 UDP 套接字的过程是由 UDP 完成的。UDP 的最重要的作用就是根据端口号将传到主机的数据包交付给最终的 UDP 套接字。 @@ -1696,7 +1696,7 @@ UDP 中的服务端和客户端不像 TCP 那样在连接状态下交换数据 TCP 中,套接字之间应该是一对一的关系。若要向 10 个客户端提供服务,除了守门的服务器套接字之外,还需要 10 个服务器套接字。但在 UDP 中,不管是服务器端还是客户端都只需要 1 个套接字。只需要一个 UDP 套接字就可以向任意主机传输数据,如图所示: -![](https://i.loli.net/2019/01/17/5c3fd703f3c40.png) +![](images/5c3fd703f3c40.png) 图中展示了 1 个 UDP 套接字与 2 个不同主机交换数据的过程。也就是说,只需 1 个 UDP 套接字就能和多台主机进行通信。 @@ -1758,7 +1758,7 @@ gcc uecho_server.c -o userver 结果: -![](https://i.loli.net/2019/01/17/5c3feb85baa83.png) +![](images/5c3feb85baa83.png) TCP 客户端套接字在调用 connect 函数时自动分配IP地址和端口号,既然如此,UDP 客户端何时分配IP地址和端口号? @@ -1792,7 +1792,7 @@ gcc bound_host2.c -o host2 运行结果: -![](https://i.loli.net/2019/01/17/5c3ff966a8d34.png) +![](images/5c3ff966a8d34.png) host1 是服务端,host2 是客户端,host2 一次性把数据发给服务端后,结束程序。但是因为服务端每隔五秒才接收一次,所以服务端每隔五秒接收一次消息。 @@ -1886,7 +1886,7 @@ TCP 的断开连接过程比建立连接更重要,因为连接过程中一般 Linux 的 close 函数和 Windows 的 closesocket 函数意味着完全断开连接。完全断开不仅指无法传输数据,而且也不能接收数据。因此在某些情况下,通信一方单方面的断开套接字连接,显得不太优雅。如图所示: -![](https://i.loli.net/2019/01/18/5c412a8baa2d8.png) +![](images/5c412a8baa2d8.png) 图中描述的是 2 台主机正在进行双向通信,主机 A 发送完最后的数据后,调用 close 函数断开了最后的连接,之后主机 A 无法再接受主机 B 传输的数据。实际上,是完全无法调用与接受数据相关的函数。最终,由主机 B 传输的、主机 A 必须要接受的数据也销毁了。 @@ -1898,7 +1898,7 @@ Linux 的 close 函数和 Windows 的 closesocket 函数意味着完全断开连 此处的流可以比作水流。水朝着一个方向流动,同样,在套接字的流中,数据也只能向一个方向流动。因此,为了进行双向通信,需要如图所示的两个流: -![](https://i.loli.net/2019/01/18/5c412c3ba25dd.png) +![](images/5c412c3ba25dd.png) 一旦两台主机之间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。当然,其中一个主机的输入流与另一个主机的输出流相连,而输出流则与另一个主机的输入流相连。另外,本章讨论的「优雅的断开连接方式」只断开其中 1 个流,而非同时断开两个流。Linux 的 close 函数和 Windows 的 closesocket 函数将同时断开这两个流,因此与「优雅」二字还有一段距离。 @@ -1944,7 +1944,7 @@ howto: 传递断开方式信息 上述文件传输服务器端和客户端的数据流可以整理如图: -![](https://i.loli.net/2019/01/18/5c41326280ab5.png) +![](images/5c41326280ab5.png) 下面的代码为编程简便,省略了大量错误处理代码。 @@ -1962,7 +1962,7 @@ gcc file_server.c -o fserver 结果: -![](https://i.loli.net/2019/01/18/5c4140bc8db2f.png) +![](images/5c4140bc8db2f.png) 客户端接受完成后,服务器会接收到来自客户端的感谢信息。 @@ -2025,7 +2025,7 @@ DNS 是对IP地址和域名进行相互转换的系统,其核心是 DNS 服务 相当于一个字典,可以查询出某一个域名对应的IP地址 -![](https://i.loli.net/2019/01/18/5c41854859ae3.png) +![](images/5c41854859ae3.png) 如图所示,显示了 DNS 服务器的查询路径。 @@ -2070,7 +2070,7 @@ struct hostent 调用 gethostbyname 函数后,返回的结构体变量如图所示: -![](https://i.loli.net/2019/01/18/5c41898ae45e8.png) +![](images/5c41898ae45e8.png) 下面的代码通过一个例子来演示 gethostbyname 的应用,并说明 hostent 结构体变量特性。 @@ -2085,7 +2085,7 @@ gcc gethostbyname.c -o hostname 结果: -![](https://i.loli.net/2019/01/18/5c418faf20495.png) +![](images/5c418faf20495.png) 如图所示,显示出了对百度的域名解析 @@ -2109,7 +2109,7 @@ inet_ntoa(*(struct in_addr *)host->h_addr_list[i]) 若只看 hostent 的定义,结构体成员 h_addr_list 指向字符串指针数组(由多个字符串地址构成的数组)。但是字符串指针数组保存的元素实际指向的是 in_addr 结构体变量中地址值而非字符串,也就是说`(struct in_addr *)host->h_addr_list[i]`其实是一个指针,然后用`*`符号取具体的值。如图所示: -![](https://i.loli.net/2019/01/18/5c419658a73b8.png) +![](images/5c419658a73b8.png) #### 8.2.3 利用IP地址获取域名 @@ -2139,7 +2139,7 @@ gcc gethostbyaddr.c -o hostaddr 结果: -![](https://i.loli.net/2019/01/18/5c41a019085d4.png) +![](images/5c41a019085d4.png) 从图上可以看出,`8.8.8.8`这个IP地址是谷歌的。 @@ -2162,7 +2162,7 @@ gcc gethostbyaddr.c -o hostaddr 2. **阅读如下对话,并说明东秀的方案是否可行。(因为对话的字太多,用图代替)** - ![](https://i.loli.net/2019/01/18/5c41a22f35390.png) + ![](images/5c41a22f35390.png) 答:东秀的方案是可行的。DNS 服务器采用分布式层次结构,具有冗余性和容错性。当一台 DNS 服务器故障时,可以自动切换到其他可用的 DNS 服务器进行查询,不会导致整个域名解析系统瘫痪。此外,DNS 解析结果通常会在本地缓存一段时间,即使 DNS 服务器暂时不可用,已缓存的解析记录仍然可以正常使用。 @@ -2345,7 +2345,7 @@ Output buffer size: 6144 观察以下过程: -![](https://i.loli.net/2019/01/19/5c42db182cade.png) +![](images/5c42db182cade.png) 假设图中主机 A 是服务器,因为是主机 A 向 B 发送 FIN 消息,故可想象成服务器端在控制台中输入 CTRL+C 。但是问题是,套接字经过四次握手后并没有立即消除,而是要经过一段时间的 Time-wait 状态。当然,只有先断开连接的(先发送 FIN 消息的)主机才经过 Time-wait 状态。因此,若服务器端先断开连接,则无法立即重新运行。套接字处在 Time-wait 过程时,相应端口是正在使用的状态。因此,就像之前验证过的,bind 函数调用过程中会发生错误。 @@ -2357,7 +2357,7 @@ Output buffer size: 6144 Time-wait 状态看似重要,但是不一定讨人喜欢。如果系统发生故障紧急停止,这时需要尽快重启服务器以提供服务,但因处于 Time-wait 状态而必须等待几分钟。因此,Time-wait 并非只有优点,这些情况下容易引发大问题。下图中展示了四次握手时不得不延长 Time-wait 过程的情况。 -![](https://i.loli.net/2019/01/19/5c42dec2ba42b.png) +![](images/5c42dec2ba42b.png) 从图上可以看出,在主机 A 四次握手的过程中,如果最后的数据丢失,则主机 B 会认为主机 A 未能收到自己发送的 FIN 信息,因此重传。这时,收到的 FIN 消息的主机 A 将重启 Time-wait 计时器。因此,如果网络状况不理想, Time-wait 将持续。 @@ -2377,7 +2377,7 @@ setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen); 为了防止因数据包过多而发生网络过载,`Nagle` 算法诞生了。它应用于 TCP 层。它是否使用会导致如图所示的差异: -![](https://i.loli.net/2019/01/19/5c42e12abc5b8.png) +![](images/5c42e12abc5b8.png) 图中展示了通过 `Nagle` 算法发送字符串 `Nagle` 和未使用 `Nagle` 算法的差别。可以得到一个结论。 @@ -2463,7 +2463,7 @@ ps au 通过上面的命令可查看当前运行的所有进程。需要注意的是,该命令同时列出了 PID(进程 ID)。参数 a 和 u 列出了所有进程的详细信息。 -![](https://i.loli.net/2019/01/20/5c43d7c1f2a8b.png) +![](images/5c43d7c1f2a8b.png) #### 10.1.4 通过调用 fork 函数创建进程 @@ -2482,7 +2482,7 @@ fork 函数将创建调用的进程副本。也就是说,并非根据完全不 此处,「父进程」(Parent Process)指原进程,即调用 fork 函数的主体,而「子进程」(Child Process)是通过父进程调用 fork 函数复制出的进程。接下来是调用 fork 函数后的程序运行流程。如图所示: -![](https://i.loli.net/2019/01/20/5c43da5412b90.png) +![](images/5c43da5412b90.png) 从图中可以看出,父进程调用 fork 函数的同时复制出子进程,并分别得到 fork 函数的返回值。但复制前,父进程将全局变量 gval 增加到 11,将局部变量 lval 的值增加到 25,因此在这种状态下完成进程复制。复制完成后根据 fork 函数的返回值区分父子进程。父进程的 lval 的值增加 1,但这不会影响子进程的 lval 值。同样子进程将 gval 的值增加 1 也不会影响到父进程的 gval。因为 fork 函数调用后分成了完全不同的进程,只是二者共享同一段代码而已。接下来给出一个例子: @@ -2519,7 +2519,7 @@ gcc fork.c -o fork 运行结果: -![](https://i.loli.net/2019/01/20/5c43e054e7f6f.png) +![](images/5c43e054e7f6f.png) 可以看出,当执行了 fork 函数之后,此后就相当于有了两个程序在执行代码。对于父进程来说,fork 函数返回的是子进程的 ID,对于子进程来说,fork 函数返回 0。在 fork 之后,父进程对两个变量进行了 -2 操作,而子进程对两个变量进行了 +2 操作,所以结果是这样。 @@ -2594,11 +2594,11 @@ gcc zombie.c -o zombie 结果: -![](https://i.loli.net/2019/01/20/5c443890f1781.png) +![](images/5c443890f1781.png) 因为暂停了 30 秒,所以在这个时间内可以验证一下子进程是否为僵尸进程。 -![](https://i.loli.net/2019/01/20/5c4439a751b11.png) +![](images/5c4439a751b11.png) 通过 `ps au` 命令可以看出,子进程仍然存在,并没有被销毁,僵尸进程在这里显示为 `Z+`.30秒后,红框里面的两个进程会同时被销毁。 @@ -2684,7 +2684,7 @@ gcc wait.c -o wait 结果: -![](https://i.loli.net/2019/01/20/5c4441951df43.png) +![](images/5c4441951df43.png) 此时,系统中并没有上述 PID 对应的进程,这是因为调用了 wait 函数,完全销毁了该子进程。另外两个子进程返回时返回的 3 和 7 传递到了父进程。 @@ -2746,7 +2746,7 @@ gcc waitpid.c -o waitpid 结果: -![](https://i.loli.net/2019/01/20/5c444785a16ae.png) +![](images/5c444785a16ae.png) 可以看出来,在 while 循环中正好执行了 5 次。这也证明了 waitpid 函数并没有阻塞 @@ -2863,11 +2863,11 @@ gcc signal.c -o signal 结果: -![](https://i.loli.net/2019/01/20/5c446c877acb7.png) +![](images/5c446c877acb7.png) 上述结果是没有任何输入的运行结果。当输入 ctrl+c 时: -![](https://i.loli.net/2019/01/20/5c446ce0b1143.png) +![](images/5c446ce0b1143.png) 就可以看到 `CTRL+C pressed` 的字符串。 @@ -3068,7 +3068,7 @@ wait 之前的回声服务器每次只能同时向 1 个客户端提供服务。因此,需要扩展回声服务器,使其可以同时向多个客户端提供服务。下图是基于多进程的回声服务器的模型。 -![](https://i.loli.net/2019/01/21/5c453664cde26.png) +![](images/k453664cde26.png) 从图中可以看出,每当有客户端请求时(连接请求),回声服务器都创建子进程以提供服务。如果请求的客户端有 5 个,则将创建 5 个子进程来提供服务,为了完成这些任务,需要经过如下过程: @@ -3099,11 +3099,11 @@ gcc echo_mpserv.c -o eserver 调用 fork 函数时赋值父进程的所有资源,但是套接字不是归进程所有的,而是归操作系统所有,只是进程拥有代表相应套接字的文件描述符。 -![](https://s2.ax1x.com/2019/01/21/kP7Rjx.png) +![](images/kP7Rjx.png) 如图所示,1 个套接字存在 2 个文件描述符时,只有 2 个文件描述符都终止(销毁)后,才能销毁套接字。如果维持图中的状态,即使子进程销毁了与客户端连接的套接字文件描述符,也无法销毁套接字(服务器套接字同样如此)。因此调用 fork 函数后,要将无关紧要的套接字文件描述符关掉,如图所示: -![](https://s2.ax1x.com/2019/01/21/kPH7ZT.png) +![](images/kPH7ZT.png) ### 10.5 分割 TCP 的 I/O 程序 @@ -3115,13 +3115,13 @@ gcc echo_mpserv.c -o eserver 传输数据后要等待服务器端返回的数据,因为程序代码中重复调用了 read 和 write 函数。只能这么写的原因之一是,程序在 1 个进程中运行,现在可以创建多个进程,因此可以分割数据收发过程。默认分割过程如下图所示: -![](https://s2.ax1x.com/2019/01/21/kPbhkD.png) +![](images/kPbhkD.png) 从图中可以看出,客户端的父进程负责接收数据,额外创建的子进程负责发送数据,分割后,不同进程分别负责输入输出,这样,无论客户端是否从服务器端接收完数据都可以进程传输。 分割 I/O 程序的另外一个好处是,可以提高频繁交换数据的程序性能,如下图所示: -![](https://s2.ax1x.com/2019/01/21/kPbvtg.png) +![](images/kPbvtg.png) @@ -3144,7 +3144,7 @@ gcc echo_mpclient.c -o eclient 结果: -![](https://s2.ax1x.com/2019/01/21/kPOcXn.png) +![](images/kPOcXn.png) 可以看出,基本和以前的一样,但是里面的内部结构却发生了很大的变化 @@ -3176,7 +3176,7 @@ gcc echo_mpclient.c -o eclient 运行截图: - ![](https://s2.ax1x.com/2019/01/21/kPj3Md.png) + ![](images/kPj3Md.png) 从图上可以看出,数值相同。fork 复制文件描述符时,子进程获得的文件描述符整数值与父进程的相同。 @@ -3204,7 +3204,7 @@ gcc echo_mpclient.c -o eclient 下图是基于管道(PIPE)的进程间通信的模型: -![](https://s2.ax1x.com/2019/01/22/kFlk0s.png) +![](images/kFlk0s.png) 可以看出,为了完成进程间通信,需要创建进程。管道并非属于进程的资源,而是和套接字一样,属于操作系统(也就不是 fork 函数的复制对象)。所以,两个进程通过操作系统提供的内存空间进行通信。下面是创建管道的函数。 @@ -3264,13 +3264,13 @@ Who are you? 可以从程序中看出,首先创建了一个管道,子进程通过 fds[1] 把数据写入管道,父进程从 fds[0] 再把数据读出来。可以从下图看出: -![](https://s2.ax1x.com/2019/01/22/kF8A7d.png) +![](images/kF8A7d.png) #### 11.1.2 通过管道进行进程间双向通信 下图可以看出双向通信模型: -![](https://s2.ax1x.com/2019/01/22/kF84De.png) +![](images/kF84De.png) 下面是双向通信的示例: @@ -3327,7 +3327,7 @@ Child proc output: Thank you for your message 当一个管道不满足需求时,就需要创建两个管道,各自负责不同的数据流动,过程如下图所示: -![](https://s2.ax1x.com/2019/01/22/kFJW0e.png) +![](images/kFJW0e.png) 下面采用上述模型改进 `pipe2.c` 。 @@ -3387,9 +3387,9 @@ gcc echo_storeserv.c -o serv 此服务端配合第 10 章的客户端 [echo_mpclient.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch10/echo_mpclient.c) 使用,运行结果如下图: -![](https://s2.ax1x.com/2019/01/22/kFUCct.png) +![](images/kFUCct.png) -![](https://s2.ax1x.com/2019/01/22/kFUAHS.png) +![](images/kFUAHS.png) 从图上可以看出,服务端已经生成了文件,把客户端的消息保存了下来,只保存了10次消息。 @@ -3442,11 +3442,11 @@ I/O 复用技术可以解决这个问题。 上述两种方法的内容完全一致。可以用纸电话模型做一个类比: -![](https://s2.ax1x.com/2019/01/23/kA8H81.png) +![](images/kA8H81.png) 上图是一个纸杯电话系统,为了使得三人同时通话,说话时要同时对着两个纸杯,接听时也需要耳朵同时对准两个纸杯。为了完成 3 人通话,可以进行如下图的改进: -![](https://s2.ax1x.com/2019/01/23/kA8bgx.png) +![](images/kA8bgx.png) 如图做出改进,就是引入了复用技术。 @@ -3465,11 +3465,11 @@ I/O 复用技术可以解决这个问题。 纸杯电话系统引入复用技术之后可以减少纸杯数量和连线长度。服务器端引入复用技术可以减少所需进程数。下图是多进程服务端的模型: -![](https://s2.ax1x.com/2019/01/23/kAGBM6.png) +![](images/kAGBM6.png) 下图是引入复用技术之后的模型: -![](https://s2.ax1x.com/2019/01/23/kAGrqO.png) +![](images/kAGrqO.png) 从图上可以看出,引入复用技术之后,可以减少进程数。重要的是,无论连接多少客户端,提供服务的进程只有一个。 @@ -3489,7 +3489,7 @@ select 函数是最具代表性的实现复用服务器的方法。在 Windows select 函数的使用方法与一般函数的区别并不大,更准确的说,他很难使用。但是为了实现 I/O 复用服务器端,我们应该掌握 select 函数,并运用于套接字编程当中。认为「select 函数是 I/O 复用的全部内容」也并不为过。select 函数的调用过程如下图所示: -![](https://s2.ax1x.com/2019/01/23/kAtdRs.png) +![](images/kAtdRs.png) #### 12.2.2 设置文件描述符 @@ -3497,7 +3497,7 @@ select 函数的使用方法与一般函数的区别并不大,更准确的说 利用 fd_set 数组变量执行此操作,如图所示,该数组是存有0和1的位数组。 -![](https://s2.ax1x.com/2019/01/23/kAt2i4.png) +![](images/kAt2i4.png) 图中最左端的位表示文件描述符 0(所在位置)。如果该位设置为 1,则表示该文件描述符是监视对象。那么图中哪些文件描述符是监视对象呢?很明显,是描述符 1 和 3。在 fd_set 变量中注册或更改值的操作都由下列宏完成。 @@ -3508,7 +3508,7 @@ select 函数的使用方法与一般函数的区别并不大,更准确的说 上述函数中,FD_ISSET 用于验证 select 函数的调用结果,通过下图解释这些函数的功能: -![](https://s2.ax1x.com/2019/01/23/kANR78.png) +![](images/kANR78.png) #### 12.2.3 设置检查(监视)范围及超时 @@ -3554,7 +3554,7 @@ struct timeval select 返回正整数时,怎样获知哪些文件描述符发生了变化?向 select 函数的第二到第四个参数传递的 fd_set 变量中将产生如图所示的变化: -![](https://s2.ax1x.com/2019/01/23/kA06dx.png) +![](images/kA06dx.png) 由图可知,select 函数调用完成后,向其传递的 fd_set 变量将发生变化。原来为 1 的所有位将变成 0,但是发生了变化的文件描述符除外。因此,可以认为值仍为 1 的位置上的文件描述符发生了变化。 @@ -3573,7 +3573,7 @@ gcc select.c -o select 结果: -![](https://s2.ax1x.com/2019/01/23/kAjgW6.png) +![](images/kAjgW6.png) 可以看出,如果运行后在标准输入流输入数据,就会在标准输出流输出数据,但是如果 5 秒没有输入数据,就提示超时。 @@ -3592,7 +3592,7 @@ gcc echo_selectserv.c -o selserv 结果: -![](https://s2.ax1x.com/2019/01/23/kEkV8H.png) +![](images/kEkV8H.png) 从图上可以看出,虽然只用了一个进程,但是却实现了可以和多个客户端进行通信,这都是利用了 select 的特点。 @@ -3690,9 +3690,9 @@ gcc oob_recv.c -o recv 运行结果: -![](https://i.loli.net/2019/01/26/5c4bda167ae08.png) +![](images/5c4bda167ae08.svg) -![](https://i.loli.net/2019/01/26/5c4bdb4d99823.png) +![](images/5c4bdb4d99823.svg) 从运行结果可以看出,send 是客户端,recv 是服务端,客户端给服务端发送消息,服务端接收完消息之后显示出来。可以从图中看出,每次运行的效果,并不是一样的。 @@ -3722,7 +3722,7 @@ fcntl(recv_sock, F_SETOWN, getpid()); MSG_OOB 的真正意义在于督促数据接收对象尽快处理数据。这是紧急模式的全部内容,而 TCP 「保持传输顺序」的传输特性依然成立。TCP 的紧急消息无法保证及时到达,但是可以要求急救。下面是 MSG_OOB 可选项状态下的数据传输过程,如图: -![](https://i.loli.net/2019/01/26/5c4be222845cc.png) +![](images/5c4be222845cc.svg) 上面是: @@ -3736,7 +3736,7 @@ send(sock, "890", strlen("890"), MSG_OOB); 也就是说,实际上只用了一个字节表示紧急消息。这一点可以通过图中用于传输数据的 TCP 数据包(段)的结构看得更清楚,如图: -![](https://i.loli.net/2019/01/26/5c4beeae46b4e.png) +![](images/5c4beeae46b4e.svg) TCP 数据包实际包含更多信息。TCP 头部包含如下两种信息: @@ -3765,7 +3765,7 @@ gcc peek_send.c -o send 结果: -![](https://i.loli.net/2019/01/26/5c4c0d1dc83af.png) +![](images/5c4c0d1dc83af.svg) 可以通过结果验证,仅发送了一次的数据被读取了 2 次,因为第一次调用 recv 函数时设置了 MSG_PEEK 可选项。 @@ -3802,7 +3802,7 @@ struct iovec 下图是该函数的使用方法: -![](https://i.loli.net/2019/01/26/5c4c61b07d207.png) +![](images/5c4c61b07d207.svg) writev 的第一个参数,是文件描述符,因此向控制台输出数据,ptr 是存有待发送数据信息的 iovec 数组指针。第三个参数为 2,因此,从 ptr 指向的地址开始,共浏览 2 个 iovec 结构体变量,发送这些指针指向的缓冲数据。 @@ -3902,7 +3902,7 @@ gcc readv.c -o rv 运行结果: -![](https://i.loli.net/2019/01/26/5c4c718555398.png) +![](images/5c4c718555398.svg) 从图上可以看出,首先截取了长度为 5 的数据输出,然后再输出剩下的。 @@ -3912,7 +3912,7 @@ gcc readv.c -o rv 其意义在于减少数据包个数。假设为了提高效率在服务器端明确禁用了 Nagle 算法。其实 writev 函数在不采用 Nagle 算法时更有价值,如图: -![](https://i.loli.net/2019/01/26/5c4c731323e19.png) +![](images/5c4c731323e19.svg) ### 13.3 基于 Windows 的实现 @@ -4031,7 +4031,7 @@ ioctlsocket(sock, FIONBIO, &mode); 多播是基于 UDP 完成的,也就是说,多播数据包的格式与 UDP 数据包相同。只是与一般的 UDP 数据包不同。向网络传递 1 个多播数据包时,路由器将复制该数据包并传递到多个主机。像这样,多播需要借助路由器完成。如图所示: -![](https://i.loli.net/2019/01/27/5c4d310daa6be.png) +![](images/5c4d310daa6be.png) 若通过 TCP 或 UDP 向 1000 个主机发送文件,则共需要传递 1000 次。但是此时如果用多播网络传输文件,则只需要发送一次。这时由 1000 台主机构成的网络中的路由器负责复制文件并传递到主机。就因为这种特性,多播主要用于「多媒体数据实时传输」。 @@ -4041,7 +4041,7 @@ ioctlsocket(sock, FIONBIO, &mode); 为了传递多播数据包,必须设置 TTL 。TTL 是 Time to Live的简写,是决定「数据包传递距离」的主要因素。TTL 用整数表示,并且每经过一个路由器就减一。TTL 变为 0 时,该数据包就无法再被传递,只能销毁。因此,TTL 的值设置过大将影响网络流量。当然,设置过小,也无法传递到目标。 -![](https://i.loli.net/2019/01/27/5c4d3960001eb.png) +![](images/5c4d3960001eb.png) 接下来是 TTL 的设置方法。TTL 是可以通过第九章的套接字可选项完成的。与设置 TTL 相关的协议层为 IPPROTO_IP ,选项名为 IP_MULTICAST_TTL。因此,可以用如下代码把 TTL 设置为 64 @@ -4101,7 +4101,7 @@ gcc news_receiver.c -o receiver 结果: -![](https://i.loli.net/2019/01/28/5c4e85a9aabcc.png) +![](images/5c4e85a9aabcc.png) 通过结果可以看出,使用 sender 多播信息,通过 receiver 接收广播,如果延迟运行 receiver 将无法接受之前发送的信息。 @@ -4150,7 +4150,7 @@ gcc news_sender_brd.c -o sender 结果: -![](https://i.loli.net/2019/01/28/5c4e9113368dd.png) +![](images/5c4e9113368dd.png) ### 14.3 基于 Windows 的实现 @@ -4259,7 +4259,7 @@ setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (char*)&bcast, sizeof(bcast)); 创建套接字时,操作系统会准备 I/O 缓冲。此缓冲在执行 TCP 协议时发挥着非常重要的作用。此时若使用标准 I/O 函数,将得到额外的缓冲支持。如下图: -![](https://i.loli.net/2019/01/29/5c500e53ad9aa.png) +![](images/5c500e53ad9aa.png) 假设使用 fputs 函数进行传输字符串 「Hello」时,首先将数据传递到标准 I/O 缓冲,然后将数据移动到套接字输出缓冲,最后将字符串发送到对方主机。 @@ -4343,7 +4343,7 @@ cat data.dat 运行结果: -![](https://i.loli.net/2019/01/29/5c5018ff07b29.png) +![](images/5c5018ff07b29.png) 文件描述符转换为 FILE 指针,并可以通过该指针调用标准 I/O 函数。 @@ -4404,7 +4404,7 @@ gcc echo_stdserv.c -o eserver 结果: -![](https://i.loli.net/2019/01/29/5c502001581bc.png) +![](images/5c502001581bc.png) 可以看出,运行结果和第四章相同,这是利用标准 I/O 实现的。 @@ -4488,7 +4488,7 @@ gcc sep_serv.c -o serv 结果: -![](https://i.loli.net/2019/01/30/5c512086a75d9.png) +![](images/5c512086a75d9.png) 从运行结果可以看出,服务端最终没有收到客户端发送的信息。那么这是什么原因呢? @@ -4500,15 +4500,15 @@ gcc sep_serv.c -o serv 下面的图描述的是服务端代码中的两个FILE 指针、文件描述符和套接字中的关系。 -![](https://i.loli.net/2019/01/30/5c5121da89955.png) +![](images/5c5121da89955.png) 从图中可以看到,两个指针都是基于同一文件描述符创建的。因此,针对于任何一个 FILE 指针调用 fclose 函数都会关闭文件描述符,如图所示: -![](https://i.loli.net/2019/01/30/5c51224051802.png) +![](images/5c51224051802.png) 从图中看到,销毁套接字时再也无法进行数据交换。那如何进入可以进入但是无法输出的半关闭状态呢?如下图所示: -![](https://i.loli.net/2019/01/30/5c5122a45c5f1.png) +![](images/5c5122a45c5f1.png) 只需要创建 FILE 指针前先复制文件描述符即可。复制后另外创建一个文件描述符,然后利用各自的文件描述符生成读模式的 FILE 指针和写模式的 FILE 指针。这就为半关闭创造好了环境,因为套接字和文件描述符具有如下关系: @@ -4516,7 +4516,7 @@ gcc sep_serv.c -o serv 也就是说,针对写模式 FILE 指针调用 fclose 函数时,只能销毁与该 FILE 指针相关的文件描述符,无法销毁套接字,如下图: -![](https://i.loli.net/2019/01/30/5c5123ad7df31.png) +![](images/5c5123ad7df31.png) 那么调用 fclose 函数后还剩下 1 个文件描述符,因此没有销毁套接字。那此时的状态是否为半关闭状态?不是!只是准备好了进入半关闭状态,而不是已经进入了半关闭状态。仔细观察,还剩下一个文件描述符。而该文件描述符可以同时进行 I/O。因此,不但没有发送 EOF,而且仍然可以利用文件描述符进行输出。 @@ -4524,7 +4524,7 @@ gcc sep_serv.c -o serv 与调用 fork 函数不同,调用 fork 函数将复制整个进程,此处讨论的是同一进程内完成对文件描述符的复制。如图: -![](https://i.loli.net/2019/01/30/5c512579c45b6.png) +![](images/5c512579c45b6.png) 复制完成后,两个文件描述符都可以访问文件,但是编号不同。 @@ -4583,7 +4583,7 @@ gcc dup.c -o dup 结果: -![](https://i.loli.net/2019/01/30/5c5135574d89a.png) +![](images/5c5135574d89a.png) #### 16.2.4 复制文件描述符后「流」的分离 @@ -4595,7 +4595,7 @@ gcc dup.c -o dup 这个代码可以与 [sep_clnt.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch16/sep_clnt.c) 配合起来使用,编译过程和上面一样,运行结果为: -![](https://i.loli.net/2019/01/30/5c513d54a27e0.png) +![](images/5c513d54a27e0.png) ### 16.3 习题 @@ -4820,7 +4820,7 @@ gcc echo_epollserv.c -o serv 运行结果: -![](https://i.loli.net/2019/02/01/5c53f5b6d4acf.png) +![](images/5c53f5b6d4acf.png) 可以看出运行结果和以前 select 实现的和 fork 实现的结果一样,都可以支持多客户端同时运行。 @@ -4871,7 +4871,7 @@ gcc echo_EPLTserv.c -o serv 运行结果: -![](https://i.loli.net/2019/02/01/5c540825ae415.png) +![](images/5c540825ae415.png) 从结果可以看出,每当收到客户端数据时,都会注册该事件,并因此调用 epoll_wait 函数。 @@ -4894,7 +4894,7 @@ gcc echo_EDGEserv.c -o serv 结果: -![](https://i.loli.net/2019/02/01/5c54097b6469f.png) +![](images/5c54097b6469f.png) 从上面的例子看出,接收到客户端的消息时,只输出一次「return epoll_wait」字符串,这证明仅注册了一次事件。 @@ -4959,7 +4959,7 @@ gcc echo_EPETserv.c -o serv 结果: -![](https://i.loli.net/2019/02/01/5c542149c0cee.png) +![](images/5c542149c0cee.png) #### 17.2.5 条件触发和边缘触发孰优孰劣 @@ -4969,7 +4969,7 @@ gcc echo_EPETserv.c -o serv 下面是边缘触发的图: -![](https://i.loli.net/2019/02/01/5c5421e3b3f2b.png) +![](images/5c5421e3b3f2b.png) 运行流程如下: @@ -5047,7 +5047,7 @@ gcc echo_EPETserv.c -o serv 每个进程的内存空间都由保存全局变量的「数据区」、向 malloc 等函数动态分配提供空间的堆(Heap)、函数运行时间使用的栈(Stack)构成。每个进程都有独立的这种空间,多个进程的内存结构如图所示: -![](https://i.loli.net/2019/02/02/5c55aa57db3c7.png) +![](images/5c55aa57db3c7.png) 但如果以获得多个代码执行流为目的,则不应该像上图那样完全分离内存结构,而只需分离栈区域。通过这种方式可以获得如下优势: @@ -5056,7 +5056,7 @@ gcc echo_EPETserv.c -o serv 实际上这就是线程。线程为了保持多条代码执行流而隔开了栈区域,因此具有如下图所示的内存结构: -![](https://i.loli.net/2019/02/02/5c55ab455e399.png) +![](images/5c55ab455e399.png) 如图所示,多个线程共享数据区和堆。为了保持这种结构,线程将在进程内创建并运行。也就是说,进程和线程可以定义为如下形式: @@ -5065,7 +5065,7 @@ gcc echo_EPETserv.c -o serv 如果说进程在操作系统内部生成多个执行流,那么线程就在同一进程内部创建多条执行流。因此,操作系统、进程、线程之间的关系可以表示为下图: -![](https://i.loli.net/2019/02/02/5c55ac20aa776.png) +![](images/5c55ac20aa776.png) ### 18.2 线程创建及运行 @@ -5143,11 +5143,11 @@ gcc thread1.c -o tr1 -lpthread # 线程相关代码编译时需要添加 -lpthre 运行结果: -![](https://i.loli.net/2019/02/02/5c55b5eb4daf6.png) +![](images/5c55b5eb4daf6.png) 上述程序的执行如图所示: -![](https://i.loli.net/2019/02/02/5c55b6943255b.png) +![](images/5c55b6943255b.png) 可以看出,程序在主进程没有结束时,生成的线程每隔一秒输出一次 `running thread` ,但是如果主进程没有等待十秒,而是直接结束,这样也会强制结束线程,不论线程有没有运行完毕。 @@ -5219,13 +5219,13 @@ gcc thread2.c -o tr2 -lpthread 运行结果: -![](https://i.loli.net/2019/02/02/5c55bd6032f1e.png) +![](images/5c55bd6032f1e.png) 可以看出,线程输出了5次字符串,并且把返回值给了主进程 下面是该函数的执行流程图: -![](https://i.loli.net/2019/02/02/5c55bdd3bb3c8.png) +![](images/5c55bdd3bb3c8.png) #### 18.2.2 可在临界区内调用的函数 @@ -5274,7 +5274,7 @@ gcc -D_REENTRANT mythread.c -o mthread -lpthread 下面的示例是计算从 1 到 10 的和,但并不是通过 main 函数进行运算,而是创建两个线程,其中一个线程计算 1 到 5 的和,另一个线程计算 6 到 10 的和,main 函数只负责输出运算结果。这种方式的线程模型称为「工作线程」。显示该程序的执行流程图: -![](https://i.loli.net/2019/02/03/5c55c330e8b5b.png) +![](images/5c55c330e8b5b.png) 下面是代码: @@ -5322,7 +5322,7 @@ gcc thread3.c -D_REENTRANT -o tr3 -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c55c53d70494.png) +![](images/5c55c53d70494.png) 可以看出计算结果正确,两个线程都用了全局变量 sum ,证明了 2 个线程共享保存全局变量的数据区。 @@ -5387,7 +5387,7 @@ gcc thread4.c -D_REENTRANT -o tr4 -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c55c884e7c11.png) +![](images/5c55c884e7c11.png) 从图上可以看出,每次运行的结果竟然不一样。理论上来说,上面代码的最后结果应该是 0 。原因暂时不得而知,但是可以肯定的是,这对于线程的应用是个大问题。 @@ -5578,7 +5578,7 @@ gcc mutex.c -D_REENTRANT -o mutex -lpthread 运行结果: -![](https://i.loli.net/2019/02/03/5c567e4aafbb8.png) +![](images/5c567e4aafbb8.png) 从运行结果可以看出,通过互斥量机制得出了正确的运行结果。 @@ -5716,7 +5716,7 @@ gcc semaphore.c -D_REENTRANT -o sema -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c568c2717d1e.png) +![](images/5c568c2717d1e.png) 从上述代码可以看出,设置了两个信号量:sem_one 的初始值为 0,sem_two 的初始值为 1,然后在调用函数的时候,「读」的前提是 sem_two 可以减 1,如果不能减 1 就会阻塞在这里,一直等到「计算」操作完毕后,给 sem_two 加 1,然后就可以继续执行下一句输入。对于「计算」函数,也一样。 @@ -5770,7 +5770,7 @@ gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c569b70634ff.png) +![](images/5c569b70634ff.png) ### 18.6 习题 @@ -5830,7 +5830,8 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 无状态的 Stateless 协议 -![](https://i.loli.net/2019/02/07/5c5bc6973a4d0.png) + + 从上图可以看出,服务器端响应客户端请求后立即断开连接。换言之,服务器端不会维持客户端状态。即使同一客户端再次发送请求,服务器端也无法辨认出是原先那个,而会以相同方式处理新请求。因此,HTTP 又称「无状态的 Stateless 协议」。 @@ -5838,7 +5839,8 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 下面是客户端向服务端发起请求消息的结构: -![](https://i.loli.net/2019/02/07/5c5bcbb75202f.png) + + 从图中可以看出,请求消息可以分为请求行、消息头、消息体 3 个部分。其中,请求行含有请求方式(请求目的)信息。典型的请求方式有 GET 和 POST ,GET 主要用于请求数据,POST 主要用于传输数据。为了降低复杂度,我们实现只能响应 GET 请求的 Web 服务器端,下面解释图中的请求行信息。其中「GET/index.html HTTP/1.1」 具有如下含义: @@ -5850,9 +5852,10 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 #### 24.1.4 响应消息(Response Message)的结构 -下面是 Web 服务器端向客户端传递的响应信息的结构。从图中可以看出,该响应消息由状态行、头信息、消息体等 3 个部分组成。状态行中有关于请求的状态信息,这是与请求消息相比最为显著的区别。 +下面是 Web 服务器端向客户端传递的响应信息的结构。从图中可以看出,该响应消息由状态行、头信息、消息体等 3 个部分组成。状态行中有关于请求的状态信息,这是与请求消息相比最为显著的区别。 -![](https://i.loli.net/2019/02/07/5c5bf9ad1b5f9.png) + + 第一个字符串状态行中含有关于客户端请求的处理结果。例如,客户端请求 index.html 文件时,表示 index.html 文件是否存在、服务端是否发生问题而无法响应等不同情况的信息写入状态行。图中的「HTTP/1.1 200 OK」具有如下含义: @@ -6043,9 +6046,11 @@ gcc webserv_linux.c -D_REENTRANT -o web_serv -lpthread 结果: -![](https://i.loli.net/2019/02/07/5c5c107deba11.png) + + -![](https://i.loli.net/2019/02/07/5c5c19cbb3718.png) + + 经过测试,这个简单的 HTTP 服务器可以正常的显示出页面。 diff --git a/ch03/README.md b/ch03/README.md index 829f639..0cc7ae1 100644 --- a/ch03/README.md +++ b/ch03/README.md @@ -19,11 +19,11 @@ IP 是 Internet Protocol(网络协议)的简写,是为收发网络数据 IPv4 标准的 4 字节 IP 地址分为网络地址和主机(指计算机)地址,且分为 A、B、C、D、E 等类型。 -![](https://i.loli.net/2019/01/13/5c3ab0eb17bbe.png) +![](images/5c3ab0eb17bbe.png) 数据传输过程: -![](https://i.loli.net/2019/01/13/5c3ab19174fa4.png) +![](images/5c3ab19174fa4.png) 某主机向 203.211.172.103 和 203.211.217.202 传递数据,其中 203.211.172 和 203.211.217 为该网络的网络地址,所以「向相应网络传输数据」实际上是向构成网络的路由器或者交换机传输数据,然后由路由器或者交换机根据数据中的主机地址向目标主机传递数据。 @@ -171,12 +171,12 @@ CPU 保存数据的方式有两种,这意味着 CPU 解析数据的方式也 - 大端序(Big Endian):高位字节存放到低位地址 - 小端序(Little Endian):低位字节存放到低位地址 -![big.png](https://i.loli.net/2019/01/13/5c3ac9c1b2550.png) -![small.png](https://i.loli.net/2019/01/13/5c3ac9c1c3348.png) +![big.png](images/5c3ac9c1b2550.png) +![small.png](images/5c3ac9c1c3348.png) 两台字节序不同的计算机在数据传递的过程中可能出现的问题: -![zijiexu.png](https://i.loli.net/2019/01/13/5c3aca956c8e9.png) +![zijiexu.png](images/5c3aca956c8e9.png) 因为这种原因,所以在通过网络传输数据时必须约定统一的方式,这种约定被称为网络字节序(Network Byte Order),非常简单,统一为大端序。即,先把数据数组转化成大端序格式再进行网络传输。 diff --git a/ch04/README.md b/ch04/README.md index 3086015..0ea0872 100644 --- a/ch04/README.md +++ b/ch04/README.md @@ -10,7 +10,7 @@ TCP 是 Transmission Control Protocol (传输控制协议)的简写,意为 #### 4.1.1 TCP/IP 协议栈 -![](https://i.loli.net/2019/01/14/5c3c21889db06.png) +![](images/5c3c21889db06.png) TCP/IP 协议栈共分为 4 层,可以理解为数据收发分成了 4 个层次化过程,通过层次化的方式来解决问题 @@ -40,7 +40,7 @@ IP 层只关注一个数据包(数据传输基本单位)的传输过程。 这就是 TCP 的作用。如果交换数据的过程中可以确认对方已经收到数据,并重传丢失的数据,那么即便IP层不保证数据传输,这类通信也是可靠的。 -![](https://i.loli.net/2019/01/14/5c3c268b40be6.png) +![](images/5c3c268b40be6.png) #### 4.1.5 应用层 @@ -50,7 +50,7 @@ IP 层只关注一个数据包(数据传输基本单位)的传输过程。 #### 4.2.1 TCP 服务端的默认函数的调用程序 -![](https://i.loli.net/2019/01/14/5c3c2782a7810.png) +![](images/5c3c2782a7810.png) 调用 socket 函数创建套接字,声明并初始化地址信息的结构体变量,调用 bind 函数向套接字分配地址。 @@ -98,7 +98,7 @@ accept 函数受理连接请求队列中待处理的客户端连接请求。函 #### 4.2.5 TCP 客户端的默认函数调用顺序 -![](https://i.loli.net/2019/01/14/5c3c31d77e86c.png) +![](images/5c3c31d77e86c.png) 与服务端相比,区别就在于「请求连接」,它是创建客户端套接字后向服务端发起的连接请求。服务端调用 listen 函数后创建连接请求等待队列,之后客户端即可请求连接。 @@ -138,7 +138,7 @@ addrlen: 第二个结构体参数 servaddr 变量的字节长度 关系图如下所示: -![](https://i.loli.net/2019/01/14/5c3c35a773b8c.png) +![](images/5c3c35a773b8c.png) - 客户端只能等到服务端调用 listen 函数后才能调用 connect 函数 - 服务器端可能会在客户端调用 connect 之前调用 accept 函数,这时服务器端进入阻塞(blocking)状态,直到客户端调用 connect 函数后接收到连接请求。 @@ -151,7 +151,7 @@ addrlen: 第二个结构体参数 servaddr 变量的字节长度 在 Hello World 的例子中,等待队列的作用没有太大意义。如果想继续处理好后面的客户端请求应该怎样扩展代码?最简单的方式就是插入循环反复调用 accept 函数,如图: -![](https://i.loli.net/2019/01/15/5c3d3c8a283ad.png) +![](images/5c3d3c8a283ad.png) 可以看出,调用 accept 函数后,紧接着调用 I/O 相关的 read write 函数,然后调用 close 函数。这并非针对服务器套接字,而是针对 accept 函数调用时创建的套接字。 @@ -189,10 +189,10 @@ gcc echo_server.c -o eserver 在一个服务端开启后,用另一个终端窗口开启客户端,然后程序会让你输入字符串,然后客户端输入什么字符串,客户端就会返回什么字符串,按 q 退出。这时服务端的运行并没有结束,服务端一共要处理 5 个客户端的连接,所以另外开多个终端窗口同时开启客户端,服务器按照顺序进行处理。 server: -![server.png](https://i.loli.net/2019/01/15/5c3d523d0a675.png) +![server.png](images/5c3d523d0a675.png) client: -![client.png](https://i.loli.net/2019/01/15/5c3d523d336e7.png) +![client.png](images/5c3d523d336e7.png) #### 4.3.3 回声客户端存在的问题 diff --git a/ch05/README.md b/ch05/README.md index 75aff56..3917bbe 100644 --- a/ch05/README.md +++ b/ch05/README.md @@ -81,7 +81,7 @@ gcc My_op_server.c -o myserver 结果: -![](https://i.loli.net/2019/01/15/5c3d966b81c03.png) +![](images/5c3d966b81c03.png) 其实主要是对程序的一点点小改动,只需要在客户端固定好发送的格式,服务端按照固定格式解析,然后返回结果即可。 @@ -108,7 +108,7 @@ gcc op_server.c -o opserver 结果: -![](https://i.loli.net/2019/01/16/5c3ea297c7649.png) +![](images/5c3ea297c7649.png) ### 5.2 TCP 原理 @@ -118,7 +118,7 @@ TCP 套接字的数据收发无边界。服务器即使调用 1 次 write 函数 实际上,write 函数调用后并非立即传输数据,read 函数调用后也并非马上接收数据。如图所示,write 函数调用瞬间,数据将移至输出缓冲;read 函数调用瞬间,从输入缓冲读取数据。 -![](https://i.loli.net/2019/01/16/5c3ea41cd93c6.png) +![](images/5c3ea41cd93c6.png) I/O 缓冲特性可以整理如下: @@ -158,7 +158,7 @@ TCP 套接字从创建到消失所经过的过程分为如下三步: TCP 在实际通信中也会经过三次对话过程,因此,该过程又被称为 **Three-way handshaking(三次握手)**。接下来给出连接过程中实际交换的信息方式: -![](https://i.loli.net/2019/01/16/5c3ecdec9fc04.png) +![](images/5c3ecdec9fc04.png) 套接字是全双工方式工作的。也就是说,它可以双向传递数据。因此,收发数据前要做一些准备。首先请求连接的主机 A 要给主机 B 传递以下信息: @@ -190,7 +190,7 @@ TCP 在实际通信中也会经过三次对话过程,因此,该过程又被 通过第一步三次握手过程完成了数据交换准备,下面就开始正式收发数据,其默认方式如图所示: -![](https://i.loli.net/2019/01/16/5c3ed1a97ce2b.png) +![](images/5c3ed1a97ce2b.png) 图上给出了主机 A 分成 2 个数据包向主机 B 传输 200 字节的过程。首先,主机 A 通过 1 个数据包发送 100 个字节的数据,数据包的 SEQ 为 1200。主机 B 为了确认这一点,向主机 A 发送 ACK 1301 消息。 @@ -200,7 +200,7 @@ TCP 在实际通信中也会经过三次对话过程,因此,该过程又被 与三次握手协议相同,最后 + 1 是为了告知对方下次要传递的 SEQ 号。下面分析传输过程中数据包丢失的情况: -![](https://i.loli.net/2019/01/16/5c3ed371187a6.png) +![](images/5c3ed371187a6.png) 上图表示了通过 SEQ 1301 数据包向主机 B 传递 100 字节数据。但中间发生了错误,主机 B 未收到,经过一段时间后,主机 A 仍然未收到对于 SEQ 1301 的 ACK 的确认,因此试着重传该数据包。为了完成该数据包的重传,TCP 套接字启动计时器以等待 ACK 应答。若相应计时器发生超时(Time-out!)则重传。 @@ -215,7 +215,7 @@ TCP 套接字的结束过程也非常优雅。如果对方还有数据需要传 先由套接字 A 向套接字 B 传递断开连接的信息,套接字 B 发出确认收到的消息,然后向套接字 A 传递可以断开连接的消息,套接字 A 同样发出确认消息。 -![](https://i.loli.net/2019/01/16/5c3ed7503c18c.png) +![](images/5c3ed7503c18c.png) 图中数据包内的 FIN 表示断开连接。也就是说,双方各发送 1 次 FIN 消息后断开连接。此过程经历 4 个阶段,因此又称四次握手(Four-way handshaking)。SEQ 和 ACK 的含义与之前讲解的内容一致,省略。图中,主机 A 传递了两次 ACK 5001,也许这里会有困惑。其实,第二次 FIN 数据包中的 ACK 5001 只是因为接收了 ACK 消息后未接收到的数据重传的。 diff --git a/ch06/README.md b/ch06/README.md index 719007e..0ebff26 100644 --- a/ch06/README.md +++ b/ch06/README.md @@ -18,7 +18,7 @@ TCP 与 UDP 的区别很大一部分来源于流控制。也就是说 TCP 的生 如图所示: -![](https://i.loli.net/2019/01/17/5c3fd29c70bf2.png) +![](images/5c3fd29c70bf2.png) 从图中可以看出,IP 的作用就是让离开主机 B 的 UDP 数据包准确传递到主机 A 。但是把 UDP 数据包最终交给主机 A 的某一 UDP 套接字的过程是由 UDP 完成的。UDP 的最重要的作用就是根据端口号将传到主机的数据包交付给最终的 UDP 套接字。 @@ -41,7 +41,7 @@ UDP 中的服务端和客户端不像 TCP 那样在连接状态下交换数据 TCP 中,套接字之间应该是一对一的关系。若要向 10 个客户端提供服务,除了守门的服务器套接字之外,还需要 10 个服务器套接字。但在 UDP 中,不管是服务器端还是客户端都只需要 1 个套接字。只需要一个 UDP 套接字就可以向任意主机传输数据,如图所示: -![](https://i.loli.net/2019/01/17/5c3fd703f3c40.png) +![](images/5c3fd703f3c40.png) 图中展示了 1 个 UDP 套接字与 2 个不同主机交换数据的过程。也就是说,只需 1 个 UDP 套接字就能和多台主机进行通信。 @@ -103,7 +103,7 @@ gcc uecho_server.c -o userver 结果: -![](https://i.loli.net/2019/01/17/5c3feb85baa83.png) +![](images/5c3feb85baa83.png) TCP 客户端套接字在调用 connect 函数时自动分配IP地址和端口号,既然如此,UDP 客户端何时分配IP地址和端口号? @@ -137,7 +137,7 @@ gcc bound_host2.c -o host2 运行结果: -![](https://i.loli.net/2019/01/17/5c3ff966a8d34.png) +![](images/5c3ff966a8d34.png) host1 是服务端,host2 是客户端,host2 一次性把数据发给服务端后,结束程序。但是因为服务端每隔五秒才接收一次,所以服务端每隔五秒接收一次消息。 diff --git a/ch07/README.md b/ch07/README.md index 261af1c..aeaabfc 100644 --- a/ch07/README.md +++ b/ch07/README.md @@ -12,7 +12,7 @@ TCP 的断开连接过程比建立连接更重要,因为连接过程中一般 Linux 的 close 函数和 Windows 的 closesocket 函数意味着完全断开连接。完全断开不仅指无法传输数据,而且也不能接收数据。因此在某些情况下,通信一方单方面的断开套接字连接,显得不太优雅。如图所示: -![](https://i.loli.net/2019/01/18/5c412a8baa2d8.png) +![](images/5c412a8baa2d8.png) 图中描述的是 2 台主机正在进行双向通信,主机 A 发送完最后的数据后,调用 close 函数断开了最后的连接,之后主机 A 无法再接受主机 B 传输的数据。实际上,是完全无法调用与接受数据相关的函数。最终,由主机 B 传输的、主机 A 必须要接受的数据也销毁了。 @@ -24,7 +24,7 @@ Linux 的 close 函数和 Windows 的 closesocket 函数意味着完全断开连 此处的流可以比作水流。水朝着一个方向流动,同样,在套接字的流中,数据也只能向一个方向流动。因此,为了进行双向通信,需要如图所示的两个流: -![](https://i.loli.net/2019/01/18/5c412c3ba25dd.png) +![](images/5c412c3ba25dd.png) 一旦两台主机之间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。当然,其中一个主机的输入流与另一个主机的输出流相连,而输出流则与另一个主机的输入流相连。另外,本章讨论的「优雅的断开连接方式」只断开其中 1 个流,而非同时断开两个流。Linux 的 close 函数和 Windows 的 closesocket 函数将同时断开这两个流,因此与「优雅」二字还有一段距离。 @@ -70,7 +70,7 @@ howto: 传递断开方式信息 上述文件传输服务器端和客户端的数据流可以整理如图: -![](https://i.loli.net/2019/01/18/5c41326280ab5.png) +![](images/5c41326280ab5.png) 下面的代码为编程简便,省略了大量错误处理代码。 @@ -88,7 +88,7 @@ gcc file_server.c -o fserver 结果: -![](https://i.loli.net/2019/01/18/5c4140bc8db2f.png) +![](images/5c4140bc8db2f.png) 客户端接受完成后,服务器会接收到来自客户端的感谢信息。 diff --git a/ch08/README.md b/ch08/README.md index 5ce601e..817fd0a 100644 --- a/ch08/README.md +++ b/ch08/README.md @@ -14,7 +14,7 @@ DNS 是对IP地址和域名进行相互转换的系统,其核心是 DNS 服务 相当于一个字典,可以查询出某一个域名对应的IP地址 -![](https://i.loli.net/2019/01/18/5c41854859ae3.png) +![](images/5c41854859ae3.png) 如图所示,显示了 DNS 服务器的查询路径。 @@ -59,7 +59,7 @@ struct hostent 调用 gethostbyname 函数后,返回的结构体变量如图所示: -![](https://i.loli.net/2019/01/18/5c41898ae45e8.png) +![](images/5c41898ae45e8.png) 下面的代码通过一个例子来演示 gethostbyname 的应用,并说明 hostent 结构体变量特性。 @@ -74,7 +74,7 @@ gcc gethostbyname.c -o hostname 结果: -![](https://i.loli.net/2019/01/18/5c418faf20495.png) +![](images/5c418faf20495.png) 如图所示,显示出了对百度的域名解析 @@ -98,7 +98,7 @@ inet_ntoa(*(struct in_addr *)host->h_addr_list[i]) 若只看 hostent 的定义,结构体成员 h_addr_list 指向字符串指针数组(由多个字符串地址构成的数组)。但是字符串指针数组保存的元素实际指向的是 in_addr 结构体变量中地址值而非字符串,也就是说`(struct in_addr *)host->h_addr_list[i]`其实是一个指针,然后用`*`符号取具体的值。如图所示: -![](https://i.loli.net/2019/01/18/5c419658a73b8.png) +![](images/5c419658a73b8.png) #### 8.2.3 利用IP地址获取域名 @@ -128,7 +128,7 @@ gcc gethostbyaddr.c -o hostaddr 结果: -![](https://i.loli.net/2019/01/18/5c41a019085d4.png) +![](images/5c41a019085d4.png) 从图上可以看出,`8.8.8.8`这个IP地址是谷歌的。 @@ -151,7 +151,7 @@ gcc gethostbyaddr.c -o hostaddr 2. **阅读如下对话,并说明东秀的方案是否可行。(因为对话的字太多,用图代替)** - ![](https://i.loli.net/2019/01/18/5c41a22f35390.png) + ![](images/5c41a22f35390.png) 答:东秀的方案是可行的。DNS 服务器采用分布式层次结构,具有冗余性和容错性。当一台 DNS 服务器故障时,可以自动切换到其他可用的 DNS 服务器进行查询,不会导致整个域名解析系统瘫痪。此外,DNS 解析结果通常会在本地缓存一段时间,即使 DNS 服务器暂时不可用,已缓存的解析记录仍然可以正常使用。 diff --git a/ch09/README.md b/ch09/README.md index 4b8a1c6..5fb776c 100644 --- a/ch09/README.md +++ b/ch09/README.md @@ -160,7 +160,7 @@ Output buffer size: 6144 观察以下过程: -![](https://i.loli.net/2019/01/19/5c42db182cade.png) +![](images/5c42db182cade.png) 假设图中主机 A 是服务器,因为是主机 A 向 B 发送 FIN 消息,故可想象成服务器端在控制台中输入 CTRL+C 。但是问题是,套接字经过四次握手后并没有立即消除,而是要经过一段时间的 Time-wait 状态。当然,只有先断开连接的(先发送 FIN 消息的)主机才经过 Time-wait 状态。因此,若服务器端先断开连接,则无法立即重新运行。套接字处在 Time-wait 过程时,相应端口是正在使用的状态。因此,就像之前验证过的,bind 函数调用过程中会发生错误。 @@ -172,7 +172,7 @@ Output buffer size: 6144 Time-wait 状态看似重要,但是不一定讨人喜欢。如果系统发生故障紧急停止,这时需要尽快重启服务器以提供服务,但因处于 Time-wait 状态而必须等待几分钟。因此,Time-wait 并非只有优点,这些情况下容易引发大问题。下图中展示了四次握手时不得不延长 Time-wait 过程的情况。 -![](https://i.loli.net/2019/01/19/5c42dec2ba42b.png) +![](images/5c42dec2ba42b.png) 从图上可以看出,在主机 A 四次握手的过程中,如果最后的数据丢失,则主机 B 会认为主机 A 未能收到自己发送的 FIN 信息,因此重传。这时,收到的 FIN 消息的主机 A 将重启 Time-wait 计时器。因此,如果网络状况不理想, Time-wait 将持续。 @@ -192,7 +192,7 @@ setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen); 为了防止因数据包过多而发生网络过载,`Nagle` 算法诞生了。它应用于 TCP 层。它是否使用会导致如图所示的差异: -![](https://i.loli.net/2019/01/19/5c42e12abc5b8.png) +![](images/5c42e12abc5b8.png) 图中展示了通过 `Nagle` 算法发送字符串 `Nagle` 和未使用 `Nagle` 算法的差别。可以得到一个结论。 diff --git a/ch10/README.md b/ch10/README.md index c1f7077..128be7a 100644 --- a/ch10/README.md +++ b/ch10/README.md @@ -32,7 +32,7 @@ ps au 通过上面的命令可查看当前运行的所有进程。需要注意的是,该命令同时列出了 PID(进程 ID)。参数 a 和 u 列出了所有进程的详细信息。 -![](https://i.loli.net/2019/01/20/5c43d7c1f2a8b.png) +![](images/5c43d7c1f2a8b.png) #### 10.1.4 通过调用 fork 函数创建进程 @@ -51,7 +51,7 @@ fork 函数将创建调用的进程副本。也就是说,并非根据完全不 此处,「父进程」(Parent Process)指原进程,即调用 fork 函数的主体,而「子进程」(Child Process)是通过父进程调用 fork 函数复制出的进程。接下来是调用 fork 函数后的程序运行流程。如图所示: -![](https://i.loli.net/2019/01/20/5c43da5412b90.png) +![](images/5c43da5412b90.png) 从图中可以看出,父进程调用 fork 函数的同时复制出子进程,并分别得到 fork 函数的返回值。但复制前,父进程将全局变量 gval 增加到 11,将局部变量 lval 的值增加到 25,因此在这种状态下完成进程复制。复制完成后根据 fork 函数的返回值区分父子进程。父进程的 lval 的值增加 1,但这不会影响子进程的 lval 值。同样子进程将 gval 的值增加 1 也不会影响到父进程的 gval。因为 fork 函数调用后分成了完全不同的进程,只是二者共享同一段代码而已。接下来给出一个例子: @@ -88,7 +88,7 @@ gcc fork.c -o fork 运行结果: -![](https://i.loli.net/2019/01/20/5c43e054e7f6f.png) +![](images/5c43e054e7f6f.png) 可以看出,当执行了 fork 函数之后,此后就相当于有了两个程序在执行代码。对于父进程来说,fork 函数返回的是子进程的 ID,对于子进程来说,fork 函数返回 0。在 fork 之后,父进程对两个变量进行了 -2 操作,而子进程对两个变量进行了 +2 操作,所以结果是这样。 @@ -163,11 +163,11 @@ gcc zombie.c -o zombie 结果: -![](https://i.loli.net/2019/01/20/5c443890f1781.png) +![](images/5c443890f1781.png) 因为暂停了 30 秒,所以在这个时间内可以验证一下子进程是否为僵尸进程。 -![](https://i.loli.net/2019/01/20/5c4439a751b11.png) +![](images/5c4439a751b11.png) 通过 `ps au` 命令可以看出,子进程仍然存在,并没有被销毁,僵尸进程在这里显示为 `Z+`.30秒后,红框里面的两个进程会同时被销毁。 @@ -253,7 +253,7 @@ gcc wait.c -o wait 结果: -![](https://i.loli.net/2019/01/20/5c4441951df43.png) +![](images/5c4441951df43.png) 此时,系统中并没有上述 PID 对应的进程,这是因为调用了 wait 函数,完全销毁了该子进程。另外两个子进程返回时返回的 3 和 7 传递到了父进程。 @@ -315,7 +315,7 @@ gcc waitpid.c -o waitpid 结果: -![](https://i.loli.net/2019/01/20/5c444785a16ae.png) +![](images/5c444785a16ae.png) 可以看出来,在 while 循环中正好执行了 5 次。这也证明了 waitpid 函数并没有阻塞 @@ -432,11 +432,11 @@ gcc signal.c -o signal 结果: -![](https://i.loli.net/2019/01/20/5c446c877acb7.png) +![](images/5c446c877acb7.png) 上述结果是没有任何输入的运行结果。当输入 ctrl+c 时: -![](https://i.loli.net/2019/01/20/5c446ce0b1143.png) +![](images/5c446ce0b1143.png) 就可以看到 `CTRL+C pressed` 的字符串。 @@ -637,7 +637,7 @@ wait 之前的回声服务器每次只能同时向 1 个客户端提供服务。因此,需要扩展回声服务器,使其可以同时向多个客户端提供服务。下图是基于多进程的回声服务器的模型。 -![](https://i.loli.net/2019/01/21/5c453664cde26.png) +![](images/k453664cde26.png) 从图中可以看出,每当有客户端请求时(连接请求),回声服务器都创建子进程以提供服务。如果请求的客户端有 5 个,则将创建 5 个子进程来提供服务,为了完成这些任务,需要经过如下过程: @@ -668,11 +668,11 @@ gcc echo_mpserv.c -o eserver 调用 fork 函数时赋值父进程的所有资源,但是套接字不是归进程所有的,而是归操作系统所有,只是进程拥有代表相应套接字的文件描述符。 -![](https://s2.ax1x.com/2019/01/21/kP7Rjx.png) +![](images/kP7Rjx.png) 如图所示,1 个套接字存在 2 个文件描述符时,只有 2 个文件描述符都终止(销毁)后,才能销毁套接字。如果维持图中的状态,即使子进程销毁了与客户端连接的套接字文件描述符,也无法销毁套接字(服务器套接字同样如此)。因此调用 fork 函数后,要将无关紧要的套接字文件描述符关掉,如图所示: -![](https://s2.ax1x.com/2019/01/21/kPH7ZT.png) +![](images/kPH7ZT.png) ### 10.5 分割 TCP 的 I/O 程序 @@ -684,13 +684,13 @@ gcc echo_mpserv.c -o eserver 传输数据后要等待服务器端返回的数据,因为程序代码中重复调用了 read 和 write 函数。只能这么写的原因之一是,程序在 1 个进程中运行,现在可以创建多个进程,因此可以分割数据收发过程。默认分割过程如下图所示: -![](https://s2.ax1x.com/2019/01/21/kPbhkD.png) +![](images/kPbhkD.png) 从图中可以看出,客户端的父进程负责接收数据,额外创建的子进程负责发送数据,分割后,不同进程分别负责输入输出,这样,无论客户端是否从服务器端接收完数据都可以进程传输。 分割 I/O 程序的另外一个好处是,可以提高频繁交换数据的程序性能,如下图所示: -![](https://s2.ax1x.com/2019/01/21/kPbvtg.png) +![](images/kPbvtg.png) @@ -713,7 +713,7 @@ gcc echo_mpclient.c -o eclient 结果: -![](https://s2.ax1x.com/2019/01/21/kPOcXn.png) +![](images/kPOcXn.png) 可以看出,基本和以前的一样,但是里面的内部结构却发生了很大的变化 @@ -745,7 +745,7 @@ gcc echo_mpclient.c -o eclient 运行截图: - ![](https://s2.ax1x.com/2019/01/21/kPj3Md.png) + ![](images/kPj3Md.png) 从图上可以看出,数值相同。fork 复制文件描述符时,子进程获得的文件描述符整数值与父进程的相同。 diff --git a/ch11/README.md b/ch11/README.md index e282864..76c6952 100644 --- a/ch11/README.md +++ b/ch11/README.md @@ -10,7 +10,7 @@ 下图是基于管道(PIPE)的进程间通信的模型: -![](https://s2.ax1x.com/2019/01/22/kFlk0s.png) +![](images/kFlk0s.png) 可以看出,为了完成进程间通信,需要创建进程。管道并非属于进程的资源,而是和套接字一样,属于操作系统(也就不是 fork 函数的复制对象)。所以,两个进程通过操作系统提供的内存空间进行通信。下面是创建管道的函数。 @@ -70,13 +70,13 @@ Who are you? 可以从程序中看出,首先创建了一个管道,子进程通过 fds[1] 把数据写入管道,父进程从 fds[0] 再把数据读出来。可以从下图看出: -![](https://s2.ax1x.com/2019/01/22/kF8A7d.png) +![](images/kF8A7d.png) #### 11.1.2 通过管道进行进程间双向通信 下图可以看出双向通信模型: -![](https://s2.ax1x.com/2019/01/22/kF84De.png) +![](images/kF84De.png) 下面是双向通信的示例: @@ -133,7 +133,7 @@ Child proc output: Thank you for your message 当一个管道不满足需求时,就需要创建两个管道,各自负责不同的数据流动,过程如下图所示: -![](https://s2.ax1x.com/2019/01/22/kFJW0e.png) +![](images/kFJW0e.png) 下面采用上述模型改进 `pipe2.c` 。 @@ -193,9 +193,9 @@ gcc echo_storeserv.c -o serv 此服务端配合第 10 章的客户端 [echo_mpclient.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch10/echo_mpclient.c) 使用,运行结果如下图: -![](https://s2.ax1x.com/2019/01/22/kFUCct.png) +![](images/kFUCct.png) -![](https://s2.ax1x.com/2019/01/22/kFUAHS.png) +![](images/kFUAHS.png) 从图上可以看出,服务端已经生成了文件,把客户端的消息保存了下来,只保存了10次消息。 diff --git a/ch12/README.md b/ch12/README.md index e66407c..4a67aa8 100644 --- a/ch12/README.md +++ b/ch12/README.md @@ -22,11 +22,11 @@ I/O 复用技术可以解决这个问题。 上述两种方法的内容完全一致。可以用纸电话模型做一个类比: -![](https://s2.ax1x.com/2019/01/23/kA8H81.png) +![](images/kA8H81.png) 上图是一个纸杯电话系统,为了使得三人同时通话,说话时要同时对着两个纸杯,接听时也需要耳朵同时对准两个纸杯。为了完成 3 人通话,可以进行如下图的改进: -![](https://s2.ax1x.com/2019/01/23/kA8bgx.png) +![](images/kA8bgx.png) 如图做出改进,就是引入了复用技术。 @@ -45,11 +45,11 @@ I/O 复用技术可以解决这个问题。 纸杯电话系统引入复用技术之后可以减少纸杯数量和连线长度。服务器端引入复用技术可以减少所需进程数。下图是多进程服务端的模型: -![](https://s2.ax1x.com/2019/01/23/kAGBM6.png) +![](images/kAGBM6.png) 下图是引入复用技术之后的模型: -![](https://s2.ax1x.com/2019/01/23/kAGrqO.png) +![](images/kAGrqO.png) 从图上可以看出,引入复用技术之后,可以减少进程数。重要的是,无论连接多少客户端,提供服务的进程只有一个。 @@ -69,7 +69,7 @@ select 函数是最具代表性的实现复用服务器的方法。在 Windows select 函数的使用方法与一般函数的区别并不大,更准确的说,他很难使用。但是为了实现 I/O 复用服务器端,我们应该掌握 select 函数,并运用于套接字编程当中。认为「select 函数是 I/O 复用的全部内容」也并不为过。select 函数的调用过程如下图所示: -![](https://s2.ax1x.com/2019/01/23/kAtdRs.png) +![](images/kAtdRs.png) #### 12.2.2 设置文件描述符 @@ -77,7 +77,7 @@ select 函数的使用方法与一般函数的区别并不大,更准确的说 利用 fd_set 数组变量执行此操作,如图所示,该数组是存有0和1的位数组。 -![](https://s2.ax1x.com/2019/01/23/kAt2i4.png) +![](images/kAt2i4.png) 图中最左端的位表示文件描述符 0(所在位置)。如果该位设置为 1,则表示该文件描述符是监视对象。那么图中哪些文件描述符是监视对象呢?很明显,是描述符 1 和 3。在 fd_set 变量中注册或更改值的操作都由下列宏完成。 @@ -88,7 +88,7 @@ select 函数的使用方法与一般函数的区别并不大,更准确的说 上述函数中,FD_ISSET 用于验证 select 函数的调用结果,通过下图解释这些函数的功能: -![](https://s2.ax1x.com/2019/01/23/kANR78.png) +![](images/kANR78.png) #### 12.2.3 设置检查(监视)范围及超时 @@ -134,7 +134,7 @@ struct timeval select 返回正整数时,怎样获知哪些文件描述符发生了变化?向 select 函数的第二到第四个参数传递的 fd_set 变量中将产生如图所示的变化: -![](https://s2.ax1x.com/2019/01/23/kA06dx.png) +![](images/kA06dx.png) 由图可知,select 函数调用完成后,向其传递的 fd_set 变量将发生变化。原来为 1 的所有位将变成 0,但是发生了变化的文件描述符除外。因此,可以认为值仍为 1 的位置上的文件描述符发生了变化。 @@ -153,7 +153,7 @@ gcc select.c -o select 结果: -![](https://s2.ax1x.com/2019/01/23/kAjgW6.png) +![](images/kAjgW6.png) 可以看出,如果运行后在标准输入流输入数据,就会在标准输出流输出数据,但是如果 5 秒没有输入数据,就提示超时。 @@ -172,7 +172,7 @@ gcc echo_selectserv.c -o selserv 结果: -![](https://s2.ax1x.com/2019/01/23/kEkV8H.png) +![](images/kEkV8H.png) 从图上可以看出,虽然只用了一个进程,但是却实现了可以和多个客户端进行通信,这都是利用了 select 的特点。 diff --git a/ch13/README.md b/ch13/README.md index a2d4c05..e655193 100644 --- a/ch13/README.md +++ b/ch13/README.md @@ -62,9 +62,9 @@ gcc oob_recv.c -o recv 运行结果: -![](https://i.loli.net/2019/01/26/5c4bda167ae08.png) +![](images/5c4bda167ae08.svg) -![](https://i.loli.net/2019/01/26/5c4bdb4d99823.png) +![](images/5c4bdb4d99823.svg) 从运行结果可以看出,send 是客户端,recv 是服务端,客户端给服务端发送消息,服务端接收完消息之后显示出来。可以从图中看出,每次运行的效果,并不是一样的。 @@ -94,7 +94,7 @@ fcntl(recv_sock, F_SETOWN, getpid()); MSG_OOB 的真正意义在于督促数据接收对象尽快处理数据。这是紧急模式的全部内容,而 TCP 「保持传输顺序」的传输特性依然成立。TCP 的紧急消息无法保证及时到达,但是可以要求急救。下面是 MSG_OOB 可选项状态下的数据传输过程,如图: -![](https://i.loli.net/2019/01/26/5c4be222845cc.png) +![](images/5c4be222845cc.svg) 上面是: @@ -108,7 +108,7 @@ send(sock, "890", strlen("890"), MSG_OOB); 也就是说,实际上只用了一个字节表示紧急消息。这一点可以通过图中用于传输数据的 TCP 数据包(段)的结构看得更清楚,如图: -![](https://i.loli.net/2019/01/26/5c4beeae46b4e.png) +![](images/5c4beeae46b4e.svg) TCP 数据包实际包含更多信息。TCP 头部包含如下两种信息: @@ -137,7 +137,7 @@ gcc peek_send.c -o send 结果: -![](https://i.loli.net/2019/01/26/5c4c0d1dc83af.png) +![](images/5c4c0d1dc83af.svg) 可以通过结果验证,仅发送了一次的数据被读取了 2 次,因为第一次调用 recv 函数时设置了 MSG_PEEK 可选项。 @@ -174,7 +174,7 @@ struct iovec 下图是该函数的使用方法: -![](https://i.loli.net/2019/01/26/5c4c61b07d207.png) +![](images/5c4c61b07d207.svg) writev 的第一个参数,是文件描述符,因此向控制台输出数据,ptr 是存有待发送数据信息的 iovec 数组指针。第三个参数为 2,因此,从 ptr 指向的地址开始,共浏览 2 个 iovec 结构体变量,发送这些指针指向的缓冲数据。 @@ -274,7 +274,7 @@ gcc readv.c -o rv 运行结果: -![](https://i.loli.net/2019/01/26/5c4c718555398.png) +![](images/5c4c718555398.svg) 从图上可以看出,首先截取了长度为 5 的数据输出,然后再输出剩下的。 @@ -284,7 +284,7 @@ gcc readv.c -o rv 其意义在于减少数据包个数。假设为了提高效率在服务器端明确禁用了 Nagle 算法。其实 writev 函数在不采用 Nagle 算法时更有价值,如图: -![](https://i.loli.net/2019/01/26/5c4c731323e19.png) +![](images/5c4c731323e19.svg) ### 13.3 基于 Windows 的实现 diff --git a/ch14/README.md b/ch14/README.md index fbef5b4..0f8d164 100644 --- a/ch14/README.md +++ b/ch14/README.md @@ -20,7 +20,7 @@ 多播是基于 UDP 完成的,也就是说,多播数据包的格式与 UDP 数据包相同。只是与一般的 UDP 数据包不同。向网络传递 1 个多播数据包时,路由器将复制该数据包并传递到多个主机。像这样,多播需要借助路由器完成。如图所示: -![](https://i.loli.net/2019/01/27/5c4d310daa6be.png) +![](images/5c4d310daa6be.png) 若通过 TCP 或 UDP 向 1000 个主机发送文件,则共需要传递 1000 次。但是此时如果用多播网络传输文件,则只需要发送一次。这时由 1000 台主机构成的网络中的路由器负责复制文件并传递到主机。就因为这种特性,多播主要用于「多媒体数据实时传输」。 @@ -30,7 +30,7 @@ 为了传递多播数据包,必须设置 TTL 。TTL 是 Time to Live的简写,是决定「数据包传递距离」的主要因素。TTL 用整数表示,并且每经过一个路由器就减一。TTL 变为 0 时,该数据包就无法再被传递,只能销毁。因此,TTL 的值设置过大将影响网络流量。当然,设置过小,也无法传递到目标。 -![](https://i.loli.net/2019/01/27/5c4d3960001eb.png) +![](images/5c4d3960001eb.png) 接下来是 TTL 的设置方法。TTL 是可以通过第九章的套接字可选项完成的。与设置 TTL 相关的协议层为 IPPROTO_IP ,选项名为 IP_MULTICAST_TTL。因此,可以用如下代码把 TTL 设置为 64 @@ -90,7 +90,7 @@ gcc news_receiver.c -o receiver 结果: -![](https://i.loli.net/2019/01/28/5c4e85a9aabcc.png) +![](images/5c4e85a9aabcc.png) 通过结果可以看出,使用 sender 多播信息,通过 receiver 接收广播,如果延迟运行 receiver 将无法接受之前发送的信息。 @@ -139,7 +139,7 @@ gcc news_sender_brd.c -o sender 结果: -![](https://i.loli.net/2019/01/28/5c4e9113368dd.png) +![](images/5c4e9113368dd.png) ### 14.3 基于 Windows 的实现 diff --git a/ch15/README.md b/ch15/README.md index b4d0a7c..51c5038 100644 --- a/ch15/README.md +++ b/ch15/README.md @@ -13,7 +13,7 @@ 创建套接字时,操作系统会准备 I/O 缓冲。此缓冲在执行 TCP 协议时发挥着非常重要的作用。此时若使用标准 I/O 函数,将得到额外的缓冲支持。如下图: -![](https://i.loli.net/2019/01/29/5c500e53ad9aa.png) +![](images/5c500e53ad9aa.png) 假设使用 fputs 函数进行传输字符串 「Hello」时,首先将数据传递到标准 I/O 缓冲,然后将数据移动到套接字输出缓冲,最后将字符串发送到对方主机。 @@ -97,7 +97,7 @@ cat data.dat 运行结果: -![](https://i.loli.net/2019/01/29/5c5018ff07b29.png) +![](images/5c5018ff07b29.png) 文件描述符转换为 FILE 指针,并可以通过该指针调用标准 I/O 函数。 @@ -158,7 +158,7 @@ gcc echo_stdserv.c -o eserver 结果: -![](https://i.loli.net/2019/01/29/5c502001581bc.png) +![](images/5c502001581bc.png) 可以看出,运行结果和第四章相同,这是利用标准 I/O 实现的。 diff --git a/ch16/README.md b/ch16/README.md index 48f1813..1de169b 100644 --- a/ch16/README.md +++ b/ch16/README.md @@ -52,7 +52,7 @@ gcc sep_serv.c -o serv 结果: -![](https://i.loli.net/2019/01/30/5c512086a75d9.png) +![](images/5c512086a75d9.png) 从运行结果可以看出,服务端最终没有收到客户端发送的信息。那么这是什么原因呢? @@ -64,15 +64,15 @@ gcc sep_serv.c -o serv 下面的图描述的是服务端代码中的两个FILE 指针、文件描述符和套接字中的关系。 -![](https://i.loli.net/2019/01/30/5c5121da89955.png) +![](images/5c5121da89955.png) 从图中可以看到,两个指针都是基于同一文件描述符创建的。因此,针对于任何一个 FILE 指针调用 fclose 函数都会关闭文件描述符,如图所示: -![](https://i.loli.net/2019/01/30/5c51224051802.png) +![](images/5c51224051802.png) 从图中看到,销毁套接字时再也无法进行数据交换。那如何进入可以进入但是无法输出的半关闭状态呢?如下图所示: -![](https://i.loli.net/2019/01/30/5c5122a45c5f1.png) +![](images/5c5122a45c5f1.png) 只需要创建 FILE 指针前先复制文件描述符即可。复制后另外创建一个文件描述符,然后利用各自的文件描述符生成读模式的 FILE 指针和写模式的 FILE 指针。这就为半关闭创造好了环境,因为套接字和文件描述符具有如下关系: @@ -80,7 +80,7 @@ gcc sep_serv.c -o serv 也就是说,针对写模式 FILE 指针调用 fclose 函数时,只能销毁与该 FILE 指针相关的文件描述符,无法销毁套接字,如下图: -![](https://i.loli.net/2019/01/30/5c5123ad7df31.png) +![](images/5c5123ad7df31.png) 那么调用 fclose 函数后还剩下 1 个文件描述符,因此没有销毁套接字。那此时的状态是否为半关闭状态?不是!只是准备好了进入半关闭状态,而不是已经进入了半关闭状态。仔细观察,还剩下一个文件描述符。而该文件描述符可以同时进行 I/O。因此,不但没有发送 EOF,而且仍然可以利用文件描述符进行输出。 @@ -88,7 +88,7 @@ gcc sep_serv.c -o serv 与调用 fork 函数不同,调用 fork 函数将复制整个进程,此处讨论的是同一进程内完成对文件描述符的复制。如图: -![](https://i.loli.net/2019/01/30/5c512579c45b6.png) +![](images/5c512579c45b6.png) 复制完成后,两个文件描述符都可以访问文件,但是编号不同。 @@ -147,7 +147,7 @@ gcc dup.c -o dup 结果: -![](https://i.loli.net/2019/01/30/5c5135574d89a.png) +![](images/5c5135574d89a.png) #### 16.2.4 复制文件描述符后「流」的分离 @@ -159,7 +159,7 @@ gcc dup.c -o dup 这个代码可以与 [sep_clnt.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch16/sep_clnt.c) 配合起来使用,编译过程和上面一样,运行结果为: -![](https://i.loli.net/2019/01/30/5c513d54a27e0.png) +![](images/5c513d54a27e0.png) ### 16.3 习题 diff --git a/ch17/README.md b/ch17/README.md index b21f8d6..a0966cf 100644 --- a/ch17/README.md +++ b/ch17/README.md @@ -199,7 +199,7 @@ gcc echo_epollserv.c -o serv 运行结果: -![](https://i.loli.net/2019/02/01/5c53f5b6d4acf.png) +![](images/5c53f5b6d4acf.png) 可以看出运行结果和以前 select 实现的和 fork 实现的结果一样,都可以支持多客户端同时运行。 @@ -250,7 +250,7 @@ gcc echo_EPLTserv.c -o serv 运行结果: -![](https://i.loli.net/2019/02/01/5c540825ae415.png) +![](images/5c540825ae415.png) 从结果可以看出,每当收到客户端数据时,都会注册该事件,并因此调用 epoll_wait 函数。 @@ -273,7 +273,7 @@ gcc echo_EDGEserv.c -o serv 结果: -![](https://i.loli.net/2019/02/01/5c54097b6469f.png) +![](images/5c54097b6469f.png) 从上面的例子看出,接收到客户端的消息时,只输出一次「return epoll_wait」字符串,这证明仅注册了一次事件。 @@ -338,7 +338,7 @@ gcc echo_EPETserv.c -o serv 结果: -![](https://i.loli.net/2019/02/01/5c542149c0cee.png) +![](images/5c542149c0cee.png) #### 17.2.5 条件触发和边缘触发孰优孰劣 @@ -348,7 +348,7 @@ gcc echo_EPETserv.c -o serv 下面是边缘触发的图: -![](https://i.loli.net/2019/02/01/5c5421e3b3f2b.png) +![](images/5c5421e3b3f2b.png) 运行流程如下: diff --git a/ch18/README.md b/ch18/README.md index bab7993..0fd78ac 100644 --- a/ch18/README.md +++ b/ch18/README.md @@ -28,7 +28,7 @@ 每个进程的内存空间都由保存全局变量的「数据区」、向 malloc 等函数动态分配提供空间的堆(Heap)、函数运行时间使用的栈(Stack)构成。每个进程都有独立的这种空间,多个进程的内存结构如图所示: -![](https://i.loli.net/2019/02/02/5c55aa57db3c7.png) +![](images/5c55aa57db3c7.png) 但如果以获得多个代码执行流为目的,则不应该像上图那样完全分离内存结构,而只需分离栈区域。通过这种方式可以获得如下优势: @@ -37,7 +37,7 @@ 实际上这就是线程。线程为了保持多条代码执行流而隔开了栈区域,因此具有如下图所示的内存结构: -![](https://i.loli.net/2019/02/02/5c55ab455e399.png) +![](images/5c55ab455e399.png) 如图所示,多个线程共享数据区和堆。为了保持这种结构,线程将在进程内创建并运行。也就是说,进程和线程可以定义为如下形式: @@ -46,7 +46,7 @@ 如果说进程在操作系统内部生成多个执行流,那么线程就在同一进程内部创建多条执行流。因此,操作系统、进程、线程之间的关系可以表示为下图: -![](https://i.loli.net/2019/02/02/5c55ac20aa776.png) +![](images/5c55ac20aa776.png) ### 18.2 线程创建及运行 @@ -124,11 +124,11 @@ gcc thread1.c -o tr1 -lpthread # 线程相关代码编译时需要添加 -lpthre 运行结果: -![](https://i.loli.net/2019/02/02/5c55b5eb4daf6.png) +![](images/5c55b5eb4daf6.png) 上述程序的执行如图所示: -![](https://i.loli.net/2019/02/02/5c55b6943255b.png) +![](images/5c55b6943255b.png) 可以看出,程序在主进程没有结束时,生成的线程每隔一秒输出一次 `running thread` ,但是如果主进程没有等待十秒,而是直接结束,这样也会强制结束线程,不论线程有没有运行完毕。 @@ -200,13 +200,13 @@ gcc thread2.c -o tr2 -lpthread 运行结果: -![](https://i.loli.net/2019/02/02/5c55bd6032f1e.png) +![](images/5c55bd6032f1e.png) 可以看出,线程输出了5次字符串,并且把返回值给了主进程 下面是该函数的执行流程图: -![](https://i.loli.net/2019/02/02/5c55bdd3bb3c8.png) +![](images/5c55bdd3bb3c8.png) #### 18.2.2 可在临界区内调用的函数 @@ -255,7 +255,7 @@ gcc -D_REENTRANT mythread.c -o mthread -lpthread 下面的示例是计算从 1 到 10 的和,但并不是通过 main 函数进行运算,而是创建两个线程,其中一个线程计算 1 到 5 的和,另一个线程计算 6 到 10 的和,main 函数只负责输出运算结果。这种方式的线程模型称为「工作线程」。显示该程序的执行流程图: -![](https://i.loli.net/2019/02/03/5c55c330e8b5b.png) +![](images/5c55c330e8b5b.png) 下面是代码: @@ -303,7 +303,7 @@ gcc thread3.c -D_REENTRANT -o tr3 -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c55c53d70494.png) +![](images/5c55c53d70494.png) 可以看出计算结果正确,两个线程都用了全局变量 sum ,证明了 2 个线程共享保存全局变量的数据区。 @@ -368,7 +368,7 @@ gcc thread4.c -D_REENTRANT -o tr4 -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c55c884e7c11.png) +![](images/5c55c884e7c11.png) 从图上可以看出,每次运行的结果竟然不一样。理论上来说,上面代码的最后结果应该是 0 。原因暂时不得而知,但是可以肯定的是,这对于线程的应用是个大问题。 @@ -559,7 +559,7 @@ gcc mutex.c -D_REENTRANT -o mutex -lpthread 运行结果: -![](https://i.loli.net/2019/02/03/5c567e4aafbb8.png) +![](images/5c567e4aafbb8.png) 从运行结果可以看出,通过互斥量机制得出了正确的运行结果。 @@ -697,7 +697,7 @@ gcc semaphore.c -D_REENTRANT -o sema -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c568c2717d1e.png) +![](images/5c568c2717d1e.png) 从上述代码可以看出,设置了两个信号量:sem_one 的初始值为 0,sem_two 的初始值为 1,然后在调用函数的时候,「读」的前提是 sem_two 可以减 1,如果不能减 1 就会阻塞在这里,一直等到「计算」操作完毕后,给 sem_two 加 1,然后就可以继续执行下一句输入。对于「计算」函数,也一样。 @@ -751,7 +751,7 @@ gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread 结果: -![](https://i.loli.net/2019/02/03/5c569b70634ff.png) +![](images/5c569b70634ff.png) ### 18.6 习题 diff --git a/ch24/README.md b/ch24/README.md index c47be51..c874dbe 100644 --- a/ch24/README.md +++ b/ch24/README.md @@ -14,7 +14,8 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 无状态的 Stateless 协议 -![](https://i.loli.net/2019/02/07/5c5bc6973a4d0.png) + + 从上图可以看出,服务器端响应客户端请求后立即断开连接。换言之,服务器端不会维持客户端状态。即使同一客户端再次发送请求,服务器端也无法辨认出是原先那个,而会以相同方式处理新请求。因此,HTTP 又称「无状态的 Stateless 协议」。 @@ -22,7 +23,8 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 下面是客户端向服务端发起请求消息的结构: -![](https://i.loli.net/2019/02/07/5c5bcbb75202f.png) + + 从图中可以看出,请求消息可以分为请求行、消息头、消息体 3 个部分。其中,请求行含有请求方式(请求目的)信息。典型的请求方式有 GET 和 POST ,GET 主要用于请求数据,POST 主要用于传输数据。为了降低复杂度,我们实现只能响应 GET 请求的 Web 服务器端,下面解释图中的请求行信息。其中「GET/index.html HTTP/1.1」 具有如下含义: @@ -34,9 +36,10 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 #### 24.1.4 响应消息(Response Message)的结构 -下面是 Web 服务器端向客户端传递的响应信息的结构。从图中可以看出,该响应消息由状态行、头信息、消息体等 3 个部分组成。状态行中有关于请求的状态信息,这是与请求消息相比最为显著的区别。 +下面是 Web 服务器端向客户端传递的响应信息的结构。从图中可以看出,该响应消息由状态行、头信息、消息体等 3 个部分组成。状态行中有关于请求的状态信息,这是与请求消息相比最为显著的区别。 -![](https://i.loli.net/2019/02/07/5c5bf9ad1b5f9.png) + + 第一个字符串状态行中含有关于客户端请求的处理结果。例如,客户端请求 index.html 文件时,表示 index.html 文件是否存在、服务端是否发生问题而无法响应等不同情况的信息写入状态行。图中的「HTTP/1.1 200 OK」具有如下含义: @@ -227,9 +230,11 @@ gcc webserv_linux.c -D_REENTRANT -o web_serv -lpthread 结果: -![](https://i.loli.net/2019/02/07/5c5c107deba11.png) + + -![](https://i.loli.net/2019/02/07/5c5c19cbb3718.png) + + 经过测试,这个简单的 HTTP 服务器可以正常的显示出页面。 diff --git a/images/5c3ab0eb17bbe.png b/images/5c3ab0eb17bbe.png new file mode 100644 index 0000000..aec9e37 Binary files /dev/null and b/images/5c3ab0eb17bbe.png differ diff --git a/images/5c3ab19174fa4.png b/images/5c3ab19174fa4.png new file mode 100644 index 0000000..0dc3096 Binary files /dev/null and b/images/5c3ab19174fa4.png differ diff --git a/images/5c3ac9c1b2550.png b/images/5c3ac9c1b2550.png new file mode 100644 index 0000000..7c8b503 Binary files /dev/null and b/images/5c3ac9c1b2550.png differ diff --git a/images/5c3ac9c1c3348.png b/images/5c3ac9c1c3348.png new file mode 100644 index 0000000..0a78d0d Binary files /dev/null and b/images/5c3ac9c1c3348.png differ diff --git a/images/5c3aca956c8e9.png b/images/5c3aca956c8e9.png new file mode 100644 index 0000000..ceccd29 Binary files /dev/null and b/images/5c3aca956c8e9.png differ diff --git a/images/5c3c21889db06.png b/images/5c3c21889db06.png new file mode 100644 index 0000000..aca0789 Binary files /dev/null and b/images/5c3c21889db06.png differ diff --git a/images/5c3c268b40be6.png b/images/5c3c268b40be6.png new file mode 100644 index 0000000..cae4885 Binary files /dev/null and b/images/5c3c268b40be6.png differ diff --git a/images/5c3c2782a7810.png b/images/5c3c2782a7810.png new file mode 100644 index 0000000..31340a9 Binary files /dev/null and b/images/5c3c2782a7810.png differ diff --git a/images/5c3c31d77e86c.png b/images/5c3c31d77e86c.png new file mode 100644 index 0000000..0968579 Binary files /dev/null and b/images/5c3c31d77e86c.png differ diff --git a/images/5c3c35a773b8c.png b/images/5c3c35a773b8c.png new file mode 100644 index 0000000..939d8c9 Binary files /dev/null and b/images/5c3c35a773b8c.png differ diff --git a/images/5c3d3c8a283ad.png b/images/5c3d3c8a283ad.png new file mode 100644 index 0000000..28c6ce2 Binary files /dev/null and b/images/5c3d3c8a283ad.png differ diff --git a/images/5c3d523d0a675.png b/images/5c3d523d0a675.png new file mode 100644 index 0000000..e42f1d2 Binary files /dev/null and b/images/5c3d523d0a675.png differ diff --git a/images/5c3d523d336e7.png b/images/5c3d523d336e7.png new file mode 100644 index 0000000..bde1202 Binary files /dev/null and b/images/5c3d523d336e7.png differ diff --git a/images/5c3d966b81c03.png b/images/5c3d966b81c03.png new file mode 100644 index 0000000..7b04ebe Binary files /dev/null and b/images/5c3d966b81c03.png differ diff --git a/images/5c3ea297c7649.png b/images/5c3ea297c7649.png new file mode 100644 index 0000000..67603ed Binary files /dev/null and b/images/5c3ea297c7649.png differ diff --git a/images/5c3ea41cd93c6.png b/images/5c3ea41cd93c6.png new file mode 100644 index 0000000..9570ecd Binary files /dev/null and b/images/5c3ea41cd93c6.png differ diff --git a/images/5c3ecdec9fc04.png b/images/5c3ecdec9fc04.png new file mode 100644 index 0000000..4fa8278 Binary files /dev/null and b/images/5c3ecdec9fc04.png differ diff --git a/images/5c3ed1a97ce2b.png b/images/5c3ed1a97ce2b.png new file mode 100644 index 0000000..7479d64 Binary files /dev/null and b/images/5c3ed1a97ce2b.png differ diff --git a/images/5c3ed371187a6.png b/images/5c3ed371187a6.png new file mode 100644 index 0000000..46569cd Binary files /dev/null and b/images/5c3ed371187a6.png differ diff --git a/images/5c3ed7503c18c.png b/images/5c3ed7503c18c.png new file mode 100644 index 0000000..c1677df Binary files /dev/null and b/images/5c3ed7503c18c.png differ diff --git a/images/5c3fd29c70bf2.png b/images/5c3fd29c70bf2.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c3fd29c70bf2.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c3fd703f3c40.png b/images/5c3fd703f3c40.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c3fd703f3c40.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c3feb85baa83.png b/images/5c3feb85baa83.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c3feb85baa83.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c3ff966a8d34.png b/images/5c3ff966a8d34.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c3ff966a8d34.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c412a8baa2d8.png b/images/5c412a8baa2d8.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c412a8baa2d8.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c412c3ba25dd.png b/images/5c412c3ba25dd.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c412c3ba25dd.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c41326280ab5.png b/images/5c41326280ab5.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c41326280ab5.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c4140bc8db2f.png b/images/5c4140bc8db2f.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c4140bc8db2f.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c41854859ae3.png b/images/5c41854859ae3.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c41854859ae3.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c41898ae45e8.png b/images/5c41898ae45e8.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c41898ae45e8.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c418faf20495.png b/images/5c418faf20495.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c418faf20495.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c419658a73b8.png b/images/5c419658a73b8.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c419658a73b8.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c41a019085d4.png b/images/5c41a019085d4.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c41a019085d4.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c41a22f35390.png b/images/5c41a22f35390.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c41a22f35390.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c42db182cade.png b/images/5c42db182cade.png new file mode 100644 index 0000000..119dbc9 Binary files /dev/null and b/images/5c42db182cade.png differ diff --git a/images/5c42dec2ba42b.png b/images/5c42dec2ba42b.png new file mode 100644 index 0000000..910062a Binary files /dev/null and b/images/5c42dec2ba42b.png differ diff --git a/images/5c42e12abc5b8.png b/images/5c42e12abc5b8.png new file mode 100644 index 0000000..3f2ae42 Binary files /dev/null and b/images/5c42e12abc5b8.png differ diff --git a/images/5c43d7c1f2a8b.png b/images/5c43d7c1f2a8b.png new file mode 100644 index 0000000..3cce7d5 Binary files /dev/null and b/images/5c43d7c1f2a8b.png differ diff --git a/images/5c43da5412b90.png b/images/5c43da5412b90.png new file mode 100644 index 0000000..4896346 Binary files /dev/null and b/images/5c43da5412b90.png differ diff --git a/images/5c43e054e7f6f.png b/images/5c43e054e7f6f.png new file mode 100644 index 0000000..8a5d04f Binary files /dev/null and b/images/5c43e054e7f6f.png differ diff --git a/images/5c443890f1781.png b/images/5c443890f1781.png new file mode 100644 index 0000000..e50df58 Binary files /dev/null and b/images/5c443890f1781.png differ diff --git a/images/5c4439a751b11.png b/images/5c4439a751b11.png new file mode 100644 index 0000000..9b070e9 Binary files /dev/null and b/images/5c4439a751b11.png differ diff --git a/images/5c4441951df43.png b/images/5c4441951df43.png new file mode 100644 index 0000000..a57dead Binary files /dev/null and b/images/5c4441951df43.png differ diff --git a/images/5c444785a16ae.png b/images/5c444785a16ae.png new file mode 100644 index 0000000..7d79760 Binary files /dev/null and b/images/5c444785a16ae.png differ diff --git a/images/5c446c877acb7.png b/images/5c446c877acb7.png new file mode 100644 index 0000000..445cc70 Binary files /dev/null and b/images/5c446c877acb7.png differ diff --git a/images/5c446ce0b1143.png b/images/5c446ce0b1143.png new file mode 100644 index 0000000..019c799 Binary files /dev/null and b/images/5c446ce0b1143.png differ diff --git a/images/5c4bda167ae08.svg b/images/5c4bda167ae08.svg new file mode 100644 index 0000000..c8d65c6 --- /dev/null +++ b/images/5c4bda167ae08.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + oob_send.c 和 oob_recv.c 运行结果 (1) + + + 原始链接: https://i.loli.net/2019/01/26/5c4bda167ae08.png + + + 显示 MSG_OOB 紧急消息传输的服务端和客户端交互结果 + + \ No newline at end of file diff --git a/images/5c4bdb4d99823.svg b/images/5c4bdb4d99823.svg new file mode 100644 index 0000000..2dfbfec --- /dev/null +++ b/images/5c4bdb4d99823.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + oob_send.c 和 oob_recv.c 运行结果 (2) + + + 原始链接: https://i.loli.net/2019/01/26/5c4bdb4d99823.png + + + 显示不同运行次序下的 MSG_OOB 消息接收效果 + + \ No newline at end of file diff --git a/images/5c4be222845cc.svg b/images/5c4be222845cc.svg new file mode 100644 index 0000000..fcdc7a7 --- /dev/null +++ b/images/5c4be222845cc.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + MSG_OOB 紧急模式缓冲区状态 + + + 原始链接: https://i.loli.net/2019/01/26/5c4be222845cc.png + + + 显示 send(sock, "890", strlen("890"), MSG_OOB) 调用后的缓冲状态 + + \ No newline at end of file diff --git a/images/5c4beeae46b4e.svg b/images/5c4beeae46b4e.svg new file mode 100644 index 0000000..e4e51d5 --- /dev/null +++ b/images/5c4beeae46b4e.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + TCP 数据包结构示意图 + + + 原始链接: https://i.loli.net/2019/01/26/5c4beeae46b4e.png + + + 显示 TCP 头部中的 URG 标志和紧急指针位置 + + \ No newline at end of file diff --git a/images/5c4c0d1dc83af.svg b/images/5c4c0d1dc83af.svg new file mode 100644 index 0000000..dd1efab --- /dev/null +++ b/images/5c4c0d1dc83af.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + MSG_PEEK 可选项运行结果 + + + 原始链接: https://i.loli.net/2019/01/26/5c4c0d1dc83af.png + + + 显示 peek_send.c 和 peek_recv.c 的运行结果,演示数据被读取两次 + + \ No newline at end of file diff --git a/images/5c4c61b07d207.svg b/images/5c4c61b07d207.svg new file mode 100644 index 0000000..6c600d8 --- /dev/null +++ b/images/5c4c61b07d207.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + writev 函数使用示意图 + + + 原始链接: https://i.loli.net/2019/01/26/5c4c61b07d207.png + + + 显示 writev 函数如何通过 iovec 结构体数组整合多个缓冲区数据 + + \ No newline at end of file diff --git a/images/5c4c718555398.svg b/images/5c4c718555398.svg new file mode 100644 index 0000000..a8b05ba --- /dev/null +++ b/images/5c4c718555398.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + readv 函数运行结果 + + + 原始链接: https://i.loli.net/2019/01/26/5c4c718555398.png + + + 显示 readv 函数如何将输入数据分散读取到多个缓冲区 + + \ No newline at end of file diff --git a/images/5c4c731323e19.svg b/images/5c4c731323e19.svg new file mode 100644 index 0000000..d2c9d60 --- /dev/null +++ b/images/5c4c731323e19.svg @@ -0,0 +1,15 @@ + + + + 图像占位符 + + + writev 函数在禁用 Nagle 算法时的优势 + + + 原始链接: https://i.loli.net/2019/01/26/5c4c731323e19.png + + + 对比使用 writev 和多次 write 调用在数据包数量上的差异 + + \ No newline at end of file diff --git a/images/5c4d310daa6be.png b/images/5c4d310daa6be.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c4d310daa6be.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c4d3960001eb.png b/images/5c4d3960001eb.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c4d3960001eb.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c4e85a9aabcc.png b/images/5c4e85a9aabcc.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c4e85a9aabcc.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c4e9113368dd.png b/images/5c4e9113368dd.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c4e9113368dd.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c500e53ad9aa.png b/images/5c500e53ad9aa.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c500e53ad9aa.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c5018ff07b29.png b/images/5c5018ff07b29.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c5018ff07b29.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c502001581bc.png b/images/5c502001581bc.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c502001581bc.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c512086a75d9.png b/images/5c512086a75d9.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c512086a75d9.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c5121da89955.png b/images/5c5121da89955.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c5121da89955.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c51224051802.png b/images/5c51224051802.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c51224051802.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c5122a45c5f1.png b/images/5c5122a45c5f1.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c5122a45c5f1.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c5123ad7df31.png b/images/5c5123ad7df31.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c5123ad7df31.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c512579c45b6.png b/images/5c512579c45b6.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c512579c45b6.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c5135574d89a.png b/images/5c5135574d89a.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c5135574d89a.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c513d54a27e0.png b/images/5c513d54a27e0.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c513d54a27e0.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c53f5b6d4acf.png b/images/5c53f5b6d4acf.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c53f5b6d4acf.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c540825ae415.png b/images/5c540825ae415.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c540825ae415.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c54097b6469f.png b/images/5c54097b6469f.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c54097b6469f.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c542149c0cee.png b/images/5c542149c0cee.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c542149c0cee.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c5421e3b3f2b.png b/images/5c5421e3b3f2b.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c5421e3b3f2b.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55aa57db3c7.png b/images/5c55aa57db3c7.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55aa57db3c7.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55ab455e399.png b/images/5c55ab455e399.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55ab455e399.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55ac20aa776.png b/images/5c55ac20aa776.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55ac20aa776.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55b5eb4daf6.png b/images/5c55b5eb4daf6.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55b5eb4daf6.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55b6943255b.png b/images/5c55b6943255b.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55b6943255b.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55bd6032f1e.png b/images/5c55bd6032f1e.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55bd6032f1e.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55bdd3bb3c8.png b/images/5c55bdd3bb3c8.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55bdd3bb3c8.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55c330e8b5b.png b/images/5c55c330e8b5b.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55c330e8b5b.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55c53d70494.png b/images/5c55c53d70494.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55c53d70494.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c55c884e7c11.png b/images/5c55c884e7c11.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c55c884e7c11.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c567e4aafbb8.png b/images/5c567e4aafbb8.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c567e4aafbb8.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c568c2717d1e.png b/images/5c568c2717d1e.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c568c2717d1e.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/5c569b70634ff.png b/images/5c569b70634ff.png new file mode 100644 index 0000000..99ea9fa --- /dev/null +++ b/images/5c569b70634ff.png @@ -0,0 +1,7 @@ + +403 Forbidden + +

403 Forbidden

+
nginx
+ + diff --git a/images/k453664cde26.png b/images/k453664cde26.png new file mode 100644 index 0000000..1b148ef Binary files /dev/null and b/images/k453664cde26.png differ diff --git a/images/kA06dx.png b/images/kA06dx.png new file mode 100644 index 0000000..d718c27 Binary files /dev/null and b/images/kA06dx.png differ diff --git a/images/kA8H81.png b/images/kA8H81.png new file mode 100644 index 0000000..d21926a Binary files /dev/null and b/images/kA8H81.png differ diff --git a/images/kA8bgx.png b/images/kA8bgx.png new file mode 100644 index 0000000..f26efd5 Binary files /dev/null and b/images/kA8bgx.png differ diff --git a/images/kAGBM6.png b/images/kAGBM6.png new file mode 100644 index 0000000..67450ee Binary files /dev/null and b/images/kAGBM6.png differ diff --git a/images/kAGrqO.png b/images/kAGrqO.png new file mode 100644 index 0000000..fb05f6a Binary files /dev/null and b/images/kAGrqO.png differ diff --git a/images/kANR78.png b/images/kANR78.png new file mode 100644 index 0000000..8e50c0f Binary files /dev/null and b/images/kANR78.png differ diff --git a/images/kAjgW6.png b/images/kAjgW6.png new file mode 100644 index 0000000..db5bf64 Binary files /dev/null and b/images/kAjgW6.png differ diff --git a/images/kAt2i4.png b/images/kAt2i4.png new file mode 100644 index 0000000..68209eb Binary files /dev/null and b/images/kAt2i4.png differ diff --git a/images/kAtdRs.png b/images/kAtdRs.png new file mode 100644 index 0000000..ebe75d7 Binary files /dev/null and b/images/kAtdRs.png differ diff --git a/images/kEkV8H.png b/images/kEkV8H.png new file mode 100644 index 0000000..0b3ef0f Binary files /dev/null and b/images/kEkV8H.png differ diff --git a/images/kF84De.png b/images/kF84De.png new file mode 100644 index 0000000..95e154f Binary files /dev/null and b/images/kF84De.png differ diff --git a/images/kF8A7d.png b/images/kF8A7d.png new file mode 100644 index 0000000..300a2e7 Binary files /dev/null and b/images/kF8A7d.png differ diff --git a/images/kFJW0e.png b/images/kFJW0e.png new file mode 100644 index 0000000..16f53b1 Binary files /dev/null and b/images/kFJW0e.png differ diff --git a/images/kFUAHS.png b/images/kFUAHS.png new file mode 100644 index 0000000..1b2f9df Binary files /dev/null and b/images/kFUAHS.png differ diff --git a/images/kFUCct.png b/images/kFUCct.png new file mode 100644 index 0000000..52f1175 Binary files /dev/null and b/images/kFUCct.png differ diff --git a/images/kFlk0s.png b/images/kFlk0s.png new file mode 100644 index 0000000..d3ff1f4 Binary files /dev/null and b/images/kFlk0s.png differ diff --git a/images/kP7Rjx.png b/images/kP7Rjx.png new file mode 100644 index 0000000..62a12d1 Binary files /dev/null and b/images/kP7Rjx.png differ diff --git a/images/kPH7ZT.png b/images/kPH7ZT.png new file mode 100644 index 0000000..1aed364 Binary files /dev/null and b/images/kPH7ZT.png differ diff --git a/images/kPOcXn.png b/images/kPOcXn.png new file mode 100644 index 0000000..89c5a86 Binary files /dev/null and b/images/kPOcXn.png differ diff --git a/images/kPbhkD.png b/images/kPbhkD.png new file mode 100644 index 0000000..513c3d3 Binary files /dev/null and b/images/kPbhkD.png differ diff --git a/images/kPbvtg.png b/images/kPbvtg.png new file mode 100644 index 0000000..f538a57 Binary files /dev/null and b/images/kPbvtg.png differ diff --git a/images/kPj3Md.png b/images/kPj3Md.png new file mode 100644 index 0000000..e00e1b5 Binary files /dev/null and b/images/kPj3Md.png differ