网络编程与分层协议设计部分习题答案

更新时间:2023-11-09 14:52:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第2章 Linux下C编程环境

习题4

信号忽略是指不对该信号做出任何响应,就犹如该信号没有发生过一样,进程不对其做任何处理.

信号阻塞是指若在某一时刻该信号发生了,此时内核不会将该信号发送给进程,而是将该信号保存起来,待到该函数解除对该信号的阻塞之后,再发送给该进程进行处理.

在信号受到阻塞和进程解除对该信号的阻塞,但信号还未到达进程之间的时间段,进程可以任意改变对该信号的处理.当然如果到该进程结束之时如果还没有解除阻塞的话对则该信号的处理和忽略差不多

习题5

参见程序2_5.c

习题6

参见程序2_6.c

习题7

参见程序2_7.c和程序2_7_withmutex.c

习题8

此处第8行和第10行是可以交换的.当将其交换以后,首先执行pthread_cond_signal(&mqlock_ready),此时对于另一个之前因条件不满足的线程thread2_run感知到条件变量的变化,开始获取锁.类似于调用pthread_mutex_lock(&mqlock),而此时线程thread1_run尚未解除锁,因此前一个线程thread2_run因无法获取锁而阻塞.随后线程thread1_run调用thread_mutex_unlock(&mqlock)释放锁.此时线程thread2_run得到锁,继续执行.

当然对于其他的程序环境是否能够交换视具体环境而定.总得来说都对,不过都有缺陷(参看《Unix环境高级编程》P697-11.4)。

习题9

参见程序2_9.c

1

第3章 网络编程中常用的典型知识

习题2

参见程序3_2.c

习题4

struct len_and_flag {

unsigned short reserved:4, };

hlen:4, fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, ece:1, cwr:1;

习题5

可以将链表节点置于宿主的固定位置,例如宿主的首个元素位置,然后通过将指向链表节点的指针强制类型转换为宿主节点的地址。

习题6

参见程序3_6.c

习题7

先分析为什么要使用双向链表,而不使用单链表: 考虑普通的单链表,如下: struct node { }

假定a是某个单链表中的节点(struct node a),而b是刚定义的节点(struct node b)

2

struct node*next;

对于这样的链表,在指定的节点a后插入b很容易:b.next=a.next;a.next=&b; 然而在指定的节点a前插入b却很麻烦; struct node head; 设head为头结点 struct node *temp; temp=&head;

while(temp->next!=&a)temp=temp->next;得到a的前一个节点 temp->next=&b;b.next=&a;将b插入temp与a之间

这就花费了一定的时间来搜索a的前一个节点,而linux要节省这部分时间.因此使用双向链表,这就决定一个节点应该含有两个指针(一个向前,一个向后)

然后分析为什么prev要是用二级指针,而不使用一级指针:

Linux内核中,除了有通用了双向链表,还有通用的哈希链表。后者定义与前者有些不同。因为通常一个哈希表的表头要占用很大空间,而如果每个表头都用一个双向链表来做的话,就显得太浪费了。只用一个指针可以实现相同的功能,并且可以节省一半的表头存储空间.因此这就决定表头只含有一个指针(向前)

哈希链表定义如下: struct hlist_head {

struct hlist_node *first; }

struct hlist_node {

struct hlist_node *next, **pprev; }

由于表头结构体hlist_head与节点结构体hlist_node的定义不一样,将使得我们的pprev指针无法直接指向hlist_head,那我们可以让其指向first。而要指向first则pprev必定为二级指针.

习题8

参见程序3_8.c

习题9

为了让notifier_chain_register函数的实参能够向主程序返回该函数的处理后结果,该函数在设计上使用了2级指针。如果使用1级指针则无法通过实参带回改变后的结果。

习题10

在3.2中的程序可以按8比特进行加运算,这两种运算在逻辑上都是可行的,但是按

3

8bit进行加运算,其加的次数差不多是按16bit的2倍,浪费了时间

第4章 基础套接字

习题1

参见程序4_1.c

习题2

参见程序4_2.c

习题3

参见程序4_3.c

习题4

参见程序4_4.c

习题5

参见程序4_5.c

习题6

fprintf(stdout,\ inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

习题7

第二个客户端可以连接到服务器,但是客户端程序将阻塞于read函数,直到第一个客户端请求结束,因为服务器程序为迭代的,而并非并行的,accept函数接收第一个连接请求后,没有fork出子进程来处理客户端请求,因此套接口一直被占用,直到客户端关闭连接,服务器才执行close关闭连接,这是服务器端才能接收第二个新的连接。所以,若第二个客户端也发出请求,则必须等到第一个客户端请求处理完成,才能够获得服务器的应答。

习题8

注释掉程序第65~66行后,3次使用4.2节的客户端访问此服务器后,退出客户端,执行ps -a命令后会出现如下现象;

3912 pts/0 00:00:00 4.3 3926 pts/0 00:00:00 4.3 4006 pts/0 00:00:00 4.3

4

这表明三个子进程仍作为僵死进程存在着。其所占的内存空间和其他资源没有被回收。 这是由于注释掉65~66行后,程序将不在捕捉SIGCHLD信号,而内核对此信号的默认动作是忽略。由此可以看出,对并发服务器进行SIGCHLD信号捕捉是必要的,因为我们不愿看到僵死进程的出现。

习题9

用set follow-fork-mode child进入gdb调试工具后,在160行处设置断点后,如题所述,启动第一个客户端无法立刻得到回答,当启动第二个客户端时,可以看到,发出的请求立刻就得到了服务器的应答。这是由于,fork后,父进程关闭套接口描述符后,又返回到主循环执行,并阻塞于accept函数,等待接收新的连接请求。当第二个客户端发来连接请求后,服务器接收,并fork一个新的子进程来处理客户端的请求。这个子进程不会阻塞于write。因此,第二个客户端可以立刻得到服务器的应答。

习题10

不启动服务器,而单独执行客户端程序时,会发现客户端程序永远阻塞于它的recvfrom调用。即键入格式化要求后不会收到任何应答。程序也不会退出。该错误由sendto引起,但是sendto本身却成功返回,该ICMP错误直到后来才返回,因此称其为异步错误。要使客户端能够发现该错误而退出,有两个方法。第一个方法是为recvfrom设置一个超时,调用函数alarm就可实现超时的设置。第二个方法是为UDP使用connect函数,使其成为连接的UDP套接口,但是使用connect后的UDP套接口,不能给输出操作指定目的ip和端口号,也就是说,我们不再使用sendto,而改用write或send。写到已连接的UDP套接口上的内容都会自动发送到由connect函数指定的协议地址。同时,我们也不必使用recvfrom函数,而是改用read或recv。此时由内核为输入操作返回的数据报仅仅是那些来自connect所指定的协议地址的数据报。

因此我们可以将4.5的客户端程序作如下修改。首先注释掉86~91行和107~113行。然后在第85行后添加以下代码

if(connect(s,(struct sockaddr *)&adr_srvr,len_inet)==-1){ printf(\ exit(1);} 然后再在114行前添加

z=read(s,dgram,sizeof dgram);

最后重新编译,运行(仍然不启动服务器)程序就不会永远阻塞于recvfrom调用,而是返回一个“Connection refused: recvfrom()”错误后退出。

5

本文来源:https://www.bwwdw.com/article/hxcv.html

Top