diff --git a/README.md b/README.md index e373233..9634f0d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 编译器版本:`g++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0` 和 `gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0` -所以本笔记中只学习有关于 Linux 的部分。 +所以本笔记中只学习有关于 Linux 的部分。 本项目在 GitHub 地址为:[TCP-IP-NetworkNote](https://github.com/riba2534/TCP-IP-NetworkNote) @@ -286,7 +286,7 @@ file descriptor 3: 16 3. Linux 中,对套接字数据进行 I/O 时可以直接使用文件 I/O 相关函数;而在 Windows 中则不可以。原因为何? - > 答:暂略。 + > 答:Linux把套接字也看作是文件,所以可以用文件I/O相关函数;而Windows要区分套接字和文件,所以设置了特殊的函数。 4. 创建套接字后一般会给他分配地址,为什么?为了完成地址分配需要调用哪个函数? @@ -590,9 +590,9 @@ AF_LOACL 只是为了说明具有多种地址族而添加的。 - 成员 sin_zero - 无特殊含义。只是为结构体 sockaddr_in 结构体变量地址值将以如下方式传递给 bind 函数。 - - 在之前的代码中 + 无特殊含义。只是为结构体 sockaddr_in 的大小与sockaddr结构体保持一致而插入的成员。填充零。 + + 在之前的代码中 sockaddr_in 结构体变量地址值将以如下方式传递给 bind 函数。 ```c if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) @@ -608,6 +608,7 @@ AF_LOACL 只是为了说明具有多种地址族而添加的。 char sa_data[14]; //地址信息 } ``` + sockaddr_in结构体作用:由于直接让结构体sockadd包含所需信息比较麻烦,所以用sockaddr_in结构体强制转换 此结构体 sa_data 保存的地址信息中需要包含IP地址和端口号,剩余部分应该填充 0 ,但是这样对于包含地址的信息非常麻烦,所以出现了 sockaddr_in 结构体,然后强制转换成 sockaddr 类型,则生成符合 bind 条件的参数。 @@ -666,7 +667,7 @@ unsigned long ntohl(unsigned long); 下面的代码是示例,说明以上函数调用过程: [endian_conv.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch03/endian_conv.c) - + ```cpp #include #include @@ -943,7 +944,8 @@ addr.sin_port = htons(atoi(serv_port)); //基于字符串的IP地址端口号 9. **大端序计算机希望把 4 字节整数型 12 传递到小端序计算机。请说出数据传输过程中发生的字节序变换过程。** - 答:0x12->0x21 + 答:'0x12->0x21' + 更正:0x0000000c->0x0000000c->0x0c000000 10. **怎样表示回送地址?其含义是什么?如果向会送地址处传输数据将会发生什么情况?** @@ -977,7 +979,7 @@ IP 是面向消息的、不可靠的协议。每次传输数据时会帮我们 #### 4.1.4 TCP/UDP 层 -IP 层解决数据传输中的路径选择问题,秩序照此路径传输数据即可。TCP 和 UDP 层以 IP 层提供的路径信息为基础完成实际的数据传输,故该层又称为传输层。UDP 比 TCP 简单,现在我们只解释 TCP 。 TCP 可以保证数据的可靠传输,但是它发送数据时以 IP 层为基础(这也是协议栈层次化的原因) +IP 层解决数据传输中的路径选择问题,只需照此路径传输数据即可。TCP 和 UDP 层以 IP 层提供的路径信息为基础完成实际的数据传输,故该层又称为传输层。UDP 比 TCP 简单,现在我们只解释 TCP 。 TCP 可以保证数据的可靠传输,但是它发送数据时以 IP 层为基础(这也是协议栈层次化的原因) IP 层只关注一个数据包(数据传输基本单位)的传输过程。因此,即使传输多个数据包,每个数据包也是由 IP 层实际传输的,也就是说传输顺序及传输本身是不可靠的。若只利用IP层传输数据,则可能导致后传输的数据包B比先传输的数据包A提早到达。另外,传输的数据包A、B、C中可能只收到A和C,甚至收到的C可能已经损毁 。反之,若添加 TCP 协议则按照如下对话方式进行数据交换。 @@ -1168,7 +1170,7 @@ client: 3. **为何需要把 TCP/IP 协议栈分成 4 层(或7层)?开放式回答。** - 答:ARPANET 的研制经验表明,对于复杂的计算机网络协议,其结构应该是层次式的。分册的好处:①隔层之间是独立的②灵活性好③结构上可以分隔开④易于实现和维护⑤能促进标准化工作。 + 答:ARPANET 的研制经验表明,对于复杂的计算机网络协议,其结构应该是层次式的。分层的好处:①隔层之间是独立的②灵活性好③结构上可以分隔开④易于实现和维护⑤能促进标准化工作。 4. **客户端调用 connect 函数向服务器端发送请求。服务器端调用哪个函数后,客户端可以调用 connect 函数?** @@ -1206,7 +1208,7 @@ write(sock, message, strlen(message)); str_len = read(sock, message, BUF_SIZE - 1); ``` -二者都在村换调用 read 和 write 函数。实际上之前的回声客户端将 100% 接受字节传输的数据,只不过接受数据时的单位有些问题。扩展客户端代码回顾范围,下面是,客户端的代码: +二者都在村换调用 read 和 write 函数。实际上之前的回声客户端将 100% 接受自己传输的数据,只不过接受数据时的单位有些问题。扩展客户端代码回顾范围,下面是,客户端的代码: ```c while (1) @@ -1241,7 +1243,7 @@ while (1) 现在写一个小程序来体验应用层协议的定义过程。要求: 1. 服务器从客户端获得多个数组和运算符信息。 -2. 服务器接收到数字候对齐进行加减乘运算,然后把结果传回客户端。 +2. 服务器接收到数字后对其进行加减乘运算,然后把结果传回客户端。 例: @@ -1300,7 +1302,7 @@ gcc op_server.c -o opserver TCP 套接字的数据收发无边界。服务器即使调用 1 次 write 函数传输 40 字节的数据,客户端也有可能通过 4 次 read 函数调用每次读取 10 字节。但此处也有一些一问,服务器一次性传输了 40 字节,而客户端竟然可以缓慢的分批接受。客户端接受 10 字节后,剩下的 30 字节在何处等候呢? -实际上,write 函数调用后并非立即传输数据, read 函数调用后也并非马上接收数据。如图所示,write 函数滴啊用瞬间,数据将移至输出缓冲;read 函数调用瞬间,从输入缓冲读取数据。 +实际上,write 函数调用后并非立即传输数据, read 函数调用后也并非马上接收数据。如图所示,write 函数调用瞬间,数据将移至输出缓冲;read 函数调用瞬间,从输入缓冲读取数据。 ![](https://i.loli.net/2019/01/16/5c3ea41cd93c6.png) @@ -1413,7 +1415,7 @@ TCP 套接字的结束过程也非常优雅。如果对方还有数据需要传 1. **请说明 TCP 套接字连接设置的三次握手过程。尤其是 3 次数据交换过程每次收发的数据内容。** - 答:三次握手主要分为:①与对方套接字建立连接②与对方套接字进行数据交换③断开与对方套接字的连接。每次收发的数据内容主要有:①由主机1给主机2发送初始的SEQ,首次连接请求是关键字是SYN,表示收发数据前同步传输的消息。②主机2收到报文以后,给主机 1 传递信息,用一个新的SEQ表示自己的序号,然后ACK代表已经接受到主机1的消息,希望接受下一个消息③主机1收到主机2的确认以后,还需要给主机2给出确认,此时再发送一次SEQ和ACK。 + 答:三次握手主要分为:①A通知B有数据,希望建立链接②B回复A可以建立连接③A回应了解,并开始建立连接。每次收发的数据内容主要有:①由主机1给主机2发送初始的SEQ:1000,ACK:- (为空),首次连接请求也成为SYN,表示收发数据前同步传输的消息。②主机2收到报文以后,给主机 1 传递信息,用一个新的SEQ:2000表示自己的序号,然后ACK:1001代表已经接受到主机1的消息,希望接受下一个消息。这种类型的消息又称为SYN+ACK③主机1收到主机2的确认以后,还需要给主机2给出确认,此时再发送一次SEQ:1001和ACK:2001。 2. **TCP 是可靠的数据传输协议,但在通过网络通信的过程中可能丢失数据。请通过 ACK 和 SEQ 说明 TCP 通过和何种机制保证丢失数据的可靠传输。** @@ -1501,9 +1503,9 @@ ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); /* 成功时返回传输的字节数,失败是返回 -1 -sock: 用于传输数据的 UDP 套接字 -buff: 保存待传输数据的缓冲地址值 -nbytes: 待传输的数据长度,以字节为单位 +sock: 用于接收数据的UDP套接字文件描述符 +buff: 保存接收数据的缓冲地址值 +nbytes: 可接收的最大字节数,故无法超过参数buff所指的缓冲大小 flags: 可选项参数,若没有则传递 0 from: 存有发送端地址信息的 sockaddr 结构体变量的地址值 addrlen: 保存参数 from 的结构体变量长度的变量地址值。 @@ -1550,7 +1552,7 @@ UDP 程序中,调用 sendto 函数传输数据前应该完成对套接字的 前面说得 TCP 数据传输中不存在数据边界,这表示「数据传输过程中调用 I/O 函数的次数不具有任何意义」 -相反,UDP 是具有数据边界的下一,传输中调用 I/O 函数的次数非常重要。因此,输入函数的调用次数和输出函数的调用次数完全一致,这样才能保证接收全部已经发送的数据。例如,调用 3 次输出函数发送的数据必须通过调用 3 次输入函数才能接收完。通过一个例子来进行验证: +相反,UDP 是具有数据边界的协议,传输中调用 I/O 函数的次数非常重要。因此,输入函数的调用次数和输出函数的调用次数完全一致,这样才能保证接收全部已经发送的数据。例如,调用 3 次输出函数发送的数据必须通过调用 3 次输入函数才能接收完。通过一个例子来进行验证: - [bound_host1.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/bound_host1.c) - [bound_host2.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch06/bound_host2.c) @@ -1568,7 +1570,7 @@ gcc bound_host2.c -o host2 ![](https://i.loli.net/2019/01/17/5c3ff966a8d34.png) -host1 是服务端,host2 是客户端,host2 一次性把数据发给服务端后,结束程序。但是因为服务端每隔五秒才接收一次,所以服务端每隔五秒接收一次消息。 +host1 是服务端,host2 是客户端,host2 一次性把数据发给服务端后,结束程序。此时数据已经在服务端了。UDP I/O 调用次数必须一致,所以服务器端也需要调用三次。但是因为服务端每隔五秒才接收一次,所以服务端每隔五秒输出一条消息。 **从运行结果也可以证明 UDP 通信过程中 I/O 的调用次数必须保持一致** @@ -1669,7 +1671,7 @@ Linux 和 Windows 的 closesocket 函数意味着完全断开连接。完全断 两台主机通过套接字建立连接后进入可交换数据的状态,又称「流形成的状态」。也就是把建立套接字后可交换数据的状态看作一种流。 -此处的流可以比作水流。水朝着一个方向流动,同样,在套接字的流中,数据也止呕能向一个方向流动。因此,为了进行双向通信,需要如图所示的两个流: +此处的流可以比作水流。水朝着一个方向流动,同样,在套接字的流中,数据也只能向一个方向流动。因此,为了进行双向通信,需要如图所示的两个流: ![](https://i.loli.net/2019/01/18/5c412c3ba25dd.png) @@ -1703,7 +1705,7 @@ howto: 传递断开方式信息 > 一旦客户端连接到服务器,服务器将约定的文件传输给客户端,客户端收到后发送字符串「Thank you」给服务器端。 -此处「Thank you」的传递是多余的,这只是用来模拟客户端断开连接前还有数据要传输的情况。此时程序的还嫌难度并不小,因为传输文件的服务器端只需连续传输文件数据即可,而客户端无法知道需要接收数据到何时。客户端也没办法无休止的调用输入函数,因为这有可能导致程序**阻塞**。 +此处「Thank you」的传递是多余的,这只是用来模拟客户端断开连接前还有数据要传输的情况。此时程序的实现难度并不小,因为传输文件的服务器端只需连续传输文件数据即可,而客户端无法知道需要接收数据到何时。客户端也没办法无休止的调用输入函数,因为这有可能导致程序**阻塞**。 > 是否可以让服务器和客户端约定一个代表文件尾的字符? @@ -1857,10 +1859,15 @@ gcc gethostbyname.c -o hostname inet_ntoa(*(struct in_addr *)host->h_addr_list[i]) ``` -若只看 hostent 的定义,结构体成员 h_addr_list 指向字符串指针数组(由多个字符串地址构成的数组)。但是字符串指针数组保存的元素实际指向的是 in_addr 结构体变量中地址值而非字符串,也就是说`(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) +>**为什么是「cha\*」而不是「in_addr\*」** + 「hostent」结构体的成员「h_addr_st」指向的数组类型并不是「in_addr」结构体的指针数组,而是用了「char」指针。「hostent」结构体并非只为IPV4准备。「h_addr_list」指向的数组中也可以保存Pv6地址信息。考虑到通用性,声明为「char」指针类型的数组 +>**声明为「void」指针类型是否更合理?** + 当然如此。指针对象不明确时,更适合使用vod指针类型。此处不用是版本问题 + #### 8.2.3 利用IP地址获取域名 请看下面的函数定义: @@ -1905,7 +1912,7 @@ gcc gethostbyaddr.c -o hostaddr 答:字体加粗的表示正确答案。 - 1. **因为DNS从存在,故可以使用域名代替IP** + 1. **因为DNS存在,故可以使用域名代替IP** 2. DNS服务器实际上是路由器,因为路由器根据域名决定数据的路径 3. **所有域名信息并非集中与 1 台 DNS 服务器,但可以获取某一 DNS 服务器中未注册的所有地址** 4. DNS 服务器根据操作系统进行区分,Windows 下的 DNS 服务器和 Linux 下的 DNS 服务器是不同的。 @@ -1989,8 +1996,8 @@ int setsockopt(int sock, int level, int optname, const void *optval, socklen_t o sock: 用于更改选项套接字文件描述符 level: 要更改的可选项协议层 optname: 要更改的可选项名 -optval: 保存更改结果的缓冲地址值 -optlen: 向第四个参数传递的缓冲大小。调用函数候,该变量中保存通过第四个参数返回的可选项信息的字节数。 +optval: 保存要更改的选项信息的缓冲地址值 +optlen: 向第四个参数传递的可选信息的字节数。 */ ``` @@ -2122,7 +2129,7 @@ setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen); TCP 套接字默认使用 `Nagle` 算法交换数据,因此最大限度的进行缓冲,直到收到 ACK 。左图也就是说一共传递 4 个数据包以传输一个字符串。从右图可以看出,发送数据包一共使用了 10 个数据包。由此可知,不使用 `Nagle` 算法将对网络流量产生负面影响。即使只传输一个字节的数据,其头信息都可能是几十个字节。因此,为了提高网络传输效率,必须使用 `Nagle` 算法。 - `Nagle` 算法并不是什么情况下都适用,网络流量未受太大影响时,不使用 `Nagle` 算法要比使用它时传输速度快。最典型的就是「传输大文数据」。将文件数据传入输出缓冲不会花太多时间,因此,不使用 `Nagle` 算法,也会在装满输出缓冲时传输数据包。这不仅不会增加数据包的数量,反而在无需等待 ACK 的前提下连续传输,因此可以大大提高传输速度。 + `Nagle` 算法并不是什么情况下都适用,网络流量未受太大影响时,不使用 `Nagle` 算法要比使用它时传输速度快。最典型的就是「传输大文件数据」。将文件数据传入输出缓冲不会花太多时间,因此,不使用 `Nagle` 算法,也会在装满输出缓冲时传输数据包。这不仅不会增加数据包的数量,反而在无需等待 ACK 的前提下连续传输,因此可以大大提高传输速度。 所以,未准确判断数据性质时不应禁用 `Nagle` 算法。 @@ -2463,7 +2470,7 @@ int main(int argc, char *argv[]) } else { - //调用waitpid 传递参数 WNOHANG ,这样之前有没有终止的子进程则返回0 + //调用waitpid 传递参数 WNOHANG ,这样之前存在没有终止的子进程则返回0 while (!waitpid(-1, &status, WNOHANG)) { sleep(3); @@ -2497,7 +2504,7 @@ gcc waitpid.c -o waitpid #### 10.3.1 向操作系统求助 -子进程终止的识别主题是操作系统,因此,若操作系统能把如下信息告诉正忙于工作的父进程,将有助于构建更高效的程序 +子进程终止的识别主体是操作系统,因此,若操作系统能把如下信息告诉正忙于工作的父进程,将有助于构建更高效的程序 为了实现上述的功能,引入信号处理机制(Signal Handing)。此处「信号」是在特定事件发生时由操作系统向进程发送的消息。另外,为了响应该消息,执行与消息相关的自定义操作的过程被称为「处理」或「信号处理」。 @@ -3206,6 +3213,11 @@ I/O 复用技术可以解决这个问题。 从图上可以看出,引入复用技术之后,可以减少进程数。重要的是,无论连接多少客户端,提供服务的进程只有一个。 +**知识补给站**「关于复用服务器端的另一种理解」 +>某教室中有10名学生和1位教师,这些孩子并非等闲之辈,上课时不停地提问。学校没办法,只能给毎个学生都配1位教师,也就是说教室中现有10位教师。此后,只要有新的转校生,就会增加1位教师,因为转校生也喜欢提问。这个故事中,如果把学生当作客户端,把教师当作与客户端进行数据交换的服务器端进程,则该教室的运营方式为多进程服务器端方式。 +有一天,该校来了位具有超能力的教师。这位教师可以应对所有学生的提问,而且回答速度很快,不会让学生等待。因此,学校为了提高教师效率,将其他老师转移到了别的班。现在,学生提问前必须举手,教师确认举手学生的提问后再回答问题。也就是说,现在的教室以I/O复用方式运行。 +虽然例子有些奇怪,但可以通过它理解I/O复用技法:教师必须确认有无举手学生 +同样,I/O复用服务器端的进程需要确认举手(收到数据)的套接字,并通过举手的套接字接收数据。 ### 12.2 理解 select 函数并实现服务端 select 函数是最具代表性的实现复用服务器的方法。在 Windows 平台下也有同名函数,所以具有很好的移植性。 @@ -3260,7 +3272,7 @@ readset: 将所有关注「是否存在待读取数据」的文件描述符注 writeset: 将所有关注「是否可传输无阻塞数据」的文件描述符注册到 fd_set 型变量,并传递其地址值。 exceptset: 将所有关注「是否发生异常」的文件描述符注册到 fd_set 型变量,并传递其地址值。 timeout: 调用 select 函数后,为防止陷入无限阻塞的状态,传递超时(time-out)信息 -返回值: 发生错误时返回 -1,超时时返回0,。因发生关注的时间返回时,返回大于0的值,该值是发生事件的文件描述符数。 +返回值: 发生错误时返回 -1,超时时返回0,。因发生关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数。 */ ``` @@ -3354,7 +3366,7 @@ gcc echo_selectserv.c -o selserv 1. 调用 select 函数前需要集中 I/O 监视对象的文件描述符 2. **若已通过 select 函数注册为监视对象,则后续调用 select 函数时无需重复注册** 3. 复用服务器端同一时间只能服务于 1 个客户端,因此,需要服务的客户端接入服务器端后只能等待 - 4. **与多线程服务端不同,基于 select 的复用服务器只需要 1 个进程。因此,可以减少因创建多进程产生的服务器端的负担**。 + 4. **与多进程服务端不同,基于 select 的复用服务器只需要 1 个进程。因此,可以减少因创建多进程产生的服务器端的负担**。 4. **select 函数的观察对象中应包含服务端套接字(监听套接字),那么应将其包含到哪一类监听对象集合?请说明原因**。 @@ -3368,7 +3380,7 @@ gcc echo_selectserv.c -o selserv #### 13.1.1 Linux 中的 send & recv -首先看 sned 函数定义: +首先看 send 函数定义: ```c #include @@ -3479,7 +3491,8 @@ TCP 数据包实际包含更多信息。TCP 头部包含如下两种信息: 指定 MSG_OOB 选项的数据包本身就是紧急数据包,并通过紧急指针表示紧急消息所在的位置。 -紧急消息的意义在于督促消息处理,而非紧急传输形式受限的信息。 +如前所述,除紧急指针的前面1个字节外,数据接收方将通过调用常用输入函数读取剩余部分。 +换言之,**紧急消息的意义在于督促消息处理,而非紧急传输形式受限的消息**。 #### 13.1.4 检查输入缓冲 @@ -3539,6 +3552,8 @@ struct iovec ![](https://i.loli.net/2019/01/26/5c4c61b07d207.png) writev 的第一个参数,是文件描述符,因此向控制台输出数据,ptr 是存有待发送数据信息的 iovec 数组指针。第三个参数为 2,因此,从 ptr 指向的地址开始,共浏览 2 个 iovec 结构体变量,发送这些指针指向的缓冲数据。 +`例如`ptr[0](数组第一个元素)的 iov_base指向以A开头的字符串,同时 iov_len为3,故发送ABC。 +而ptr[1](数组的第二个元素)的iov_base指向数字1,同时 iov_len为4,故发送1234。 下面是 writev 函数的使用方法: @@ -3897,6 +3912,7 @@ gcc news_sender_brd.c -o sender - 不容易进行双向通信 - 有时可能频繁调用 fflush 函数 +>fflush—切换读写工作状态 - 需要以 FILE 结构体指针的形式返回文件描述符。 ### 15.2 使用标准 I/O 函数 @@ -4185,11 +4201,14 @@ gcc dup.c -o dup 下面更改 [sep_clnt.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch16/sep_clnt.c) 和 [sep_serv.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch16/sep_serv.c) 可以使得让它正常工作,正常工作是指通过服务器的半关闭状态接收客户端最后发送的字符串。 +**该例子得出结论** +>无论复制出多少文件描述符,均应调用shutdown函数发送EOF并进入半关闭状态。 + 下面是代码: - [sep_serv2.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch16/sep_serv2.c) -这个代码可以与 [sep_clnt.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch16/sep_clnt.c) 配合起来食用,编译过程和上面一样,运行结果为: +这个代码可以与 [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) @@ -4212,7 +4231,7 @@ gcc dup.c -o dup 答:以下加粗内容代表说法正确。 1. 终止文件描述符时发送 EOF - 2. **即使未完成终止文件描述符,关闭输出流时也会发送 EOF** + 2. **即使未完全终止文件描述符,关闭输出流时也会发送 EOF** 3. 如果复制文件描述符,则包括复制的文件描述符在内,所有文件描述符都终止时才会发送 EOF 4. **即使复制文件描述符,也可以通过调用 shutdown 函数进入半关闭状态并发送 EOF** @@ -4239,7 +4258,7 @@ select 性能上最大的弱点是:每次传递监视对象信息,准确的 这样就无需每次调用 select 函数时都想操作系统传递监视对象信息,但是前提操作系统支持这种处理方式。Linux 的支持方式是 epoll ,Windows 的支持方式是 IOCP。 -#### 17.1.2 select 也有有点 +#### 17.1.2 select 也有优点 select 的兼容性比较高,这样就可以支持很多的操作系统,不受平台的限制,使用 select 函数满足以下两个条件: @@ -4349,7 +4368,7 @@ epoll_ctl(A,EPOLL_CTL_DEL,B,NULL); - EPOLL_CTL_DEL:从 epoll 例程中删除文件描述符 - EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况 -epoll_event 结构体用于保存事件的文件描述符结合。但也可以在 epoll 例程中注册文件描述符时,用于注册关注的事件。该函数中 epoll_event 结构体的定义并不显眼,因此通过掉英语剧说明该结构体在 epoll_ctl 函数中的应用。 +epoll_event 结构体用于保存事件的文件描述符集合。但也可以在 epoll 例程中注册文件描述符时,用于注册关注的事件。该函数中 epoll_event 结构体的定义并不显眼,因此通过调用语句说明该结构体在 epoll_ctl 函数中的应用。 ```c struct epoll_event event; @@ -4360,7 +4379,7 @@ epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event); ... ``` -上述代码将 epfd 注册到 epoll 例程 epfd 中,并在需要读取数据的情况下产生相应事件。接下来给出 epoll_event 的成员 events 中可以保存的常量及所指的事件类型。 +上述代码将 sockfd 注册到 epoll 例程 epfd 中,并在需要读取数据的情况下产生相应事件。接下来给出 epoll_event 的成员 events 中可以保存的常量及所指的事件类型。 - EPOLLIN:需要读取数据的情况 - EPOLLOUT:输出缓冲为空,可以立即发送数据的情况 @@ -4603,7 +4622,7 @@ gcc echo_EPETserv.c -o serv 答:select 函数每次调用都要传递所有的监视对象信息,而 epoll 函数仅向操作系统传递 1 次监视对象,监视范围或内容发生变化时只通知发生变化的事项。select 采用这种方法是为了保持兼容性。 -4. 虽然 epoll 是 select 的改进反感,但 select 也有自己的优点。在何种情况下使用 select 更加合理。 +4. 虽然 epoll 是 select 的改进方案,但 select 也有自己的优点。在何种情况下使用 select 更加合理。 答:①服务器端接入者少②程序应具有兼容性。 @@ -4692,6 +4711,7 @@ thread : 保存新创建线程 ID 的变量地址值。线程与进程相同, attr : 用于传递线程属性的参数,传递 NULL 时,创建默认属性的线程 start_routine : 相当于线程 main 函数的、在单独执行流中执行的函数地址值(函数指针) arg : 通过第三个参数传递的调用函数时包含传递参数信息的变量地址值 + //传递参数变量的地址给start_routine函数 */ ``` @@ -4757,7 +4777,7 @@ int pthread_join(pthread_t thread, void **status); /* 成功时返回 0 ,失败时返回 -1 thread : 该参数值 ID 的线程终止后才会从该函数返回 -status : 保存线程的 main 函数返回值的指针的变量地址值 +status : 保存线程的 main 函数返回值的指针变量地址值 */ ``` @@ -4807,6 +4827,8 @@ void *thread_main(void *arg) //传入的参数是 pthread_create 的第四个 return (void *)msg; //返回值是 thread_main 函数中内部动态分配的内存空间地址值 } ``` +**关于`void *msg`和`void *thr_ret`** +>将`pointer thr_ret`的地址改成`pointer msg`的地址,即 `*thr_ret`获得的是`msg`指向的数据,即`*msg`。也可以看作引用。 编译运行: @@ -5215,7 +5237,7 @@ int sem_destroy(sem_t *sem); /* 成功时返回 0 ,失败时返回其他值 sem : 创建信号量时保存信号量的变量地址值,销毁时传递需要销毁的信号量变量地址值 -pshared : 传递其他值时,创建可由多个继承共享的信号量;传递 0 时,创建只允许 1 个进程内部使用的信号量。需要完成同一进程的线程同步,故为0 +pshared : 传递其他值时,创建可由多个进程共享的信号量;传递 0 时,创建只允许 1 个进程内部使用的信号量。需要完成同一进程的线程同步,故为0 value : 指定创建信号量的初始值 */ ``` @@ -5408,7 +5430,7 @@ gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread ## 第 19 章 Windows 平台下线程的使用 -暂略 + ## 第 20 章 Windows 中的线程同步 @@ -5446,6 +5468,9 @@ web服务器端就是要基于 HTTP 协议,将网页对应文件传输给客 从上图可以看出,服务器端相应客户端请求后立即断开连接。换言之,服务器端不会维持客户端状态。即使同一客户端再次发送请求,服务器端也无法辨认出是原先那个,而会以相同方式处理新请求。因此,HTTP 又称「无状态的 Stateless 协议」 +**Cookie & Session** +>为了弥补HTTP无法保持连接的缺点,Web编程中通常会使用Cookie和Session技术。相信各位都接触过购物网站的购物车功能,即使关闭浏览器也不会丢失购物车内的信息(甚至不用登录)。这种保持状态的功能都是通过Cookie和 Session技术实现的。 + #### 24.1.3 请求消息(Request Message)的结构 下面是客户端向服务端发起请求消息的结构: