《TCP/IP网络编程》课后练习答案第二部分15~18章 尹圣雨

第十五章 套接字和标准IO

  1. 请说明标准I/O函数的2个优点。它为何拥有这2个优点?

    • 基于ANSIX标准具有良好的一致性

    • 可以利用缓冲提高性能

  2. 标准IO中,“调用fputs函数传输数据时,调用后应立即开始发送!”,为何这种想法是错误的?为了达到这种效果应添加哪些处理过程?

    通过标准输出函数的传输的数据不直接通过套接字的输出缓冲区发送,而是保存在标准输出函数的缓冲中,然后再用fflush函数进行输出。因此,即使调用“fputs"函数,也不能立即发送数据。如果想保障数据传输的时效性,必须经过fflush函数的调用过程

第十六章 关于I/O流分离的其他内容

  1. abcd
  2. 全部都是正确的,没有错误选项

第十七章 优于select的epoll

  1. 利用select函数实现服务器端时,代码层面存在的2个缺点是?

    • 调用select函数后常见的针对所有文件描述符的循环语句
    • 每次调用select函数时都需要向该函数传递监视对象信息
  2. 无论是select方式还是epoll方式,都需要将监视对象文件描述符信息通过函数调用传递给操作系统。请解释传递该信息的原因

    select和epoll是系统函数,准确地说,是要求观察套接字变化的方式的。套接字是受操作系统进行管理的。既,select和epoll是一个有操作系统执行的函数。因此,应该将监视对象的文件描述符传递给操作系统

  3. select方式和epoll方式的最大差异在于监视对象文件描述符传递给操作系统的方式。请说明具体的差异,并解释为何存在这种差异。

    epoll不同于select的地方是只要将监视对象文件描述符的信息传递一个给操作系统就可以了。因此epoll方式克服了select方式的缺点,体现在linux内核上保存监视对象信息的方式。

  4. 虽然epoll是select的改进方式,但select也有自己的缺点。在何种情况下使用select方式更合理

    如果连接服务器的人数不多(不需要高性能),而且需要在多种操作系统(windows和linux)下进行操作,在兼容性方面,使用select会比epoll更合理

  5. epoll以条件触发或边缘触发方式工作。二者有何区别?从输入缓冲的角度说明这2中方式通知事件的时间点的差异

    在条件触发方式中,只要输入缓冲有数据,就会持续进行事件通知;而在边缘触发中,只有当输入缓冲数据为空时才进行通知

  6. 采用边缘触发时可以分离数据的接收和处理时间点。说明其原因及优点。

    如果使用边缘触发方式,在输入缓冲中接收数据时,只会发生一次事件通知,而且输入缓冲中仍有数据时,不会进行通知,因此可以在数据被接收后,在想要的时间内处理数据。而且,如果分离数据的接收和处理时间点,在服务器中会有更大的灵活性

  7. 实现聊天服务器,条件触发和边缘触发两种epoll方式实现

    /**********************************char_EPLTserv.c*************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    
    #define BUF_SIZE 100
    #define MAX_CLNT 256
    #define EPOLL_SIZE 50
    void error_handling(char *buf);
    void send_msg(char * msg, int len);
    
    int clnt_cnt=0;
    int clnt_socks[MAX_CLNT];
    
    int main(int argc, char *argv[])
    {
    	int serv_sock, clnt_sock;
    	struct sockaddr_in serv_adr, clnt_adr;
    	socklen_t adr_sz;
    	int str_len, i;
    	char buf[BUF_SIZE];
    
    	struct epoll_event *ep_events;
    	struct epoll_event event;
    	int epfd, event_cnt;
    
    	if(argc!=2) {
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_adr.sin_port=htons(atoi(argv[1]));
    	
    	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
    		error_handling("bind() error");
    	if(listen(serv_sock, 5)==-1)
    		error_handling("listen() error");
    
    	epfd=epoll_create(EPOLL_SIZE);
    	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
    
    	event.events=EPOLLIN;
    	event.data.fd=serv_sock;	
    	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
    
    	while(1)
    	{
    		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
    		if(event_cnt==-1)
    		{
    			break;
    		}
    
    		for(i=0; i<event_cnt; i++)
    		{
    			if(ep_events[i].data.fd==serv_sock)
    			{
    				adr_sz=sizeof(clnt_adr);
    				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
    				event.events=EPOLLIN;
    				event.data.fd=clnt_sock;
    				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
    				clnt_socks[clnt_cnt++]=clnt_sock;
    				printf("connected client: %d \n", clnt_sock);
    			}
    			else
    			{
    				str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
    				if(str_len==0)  
    				{
    					epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
    					close(ep_events[i].data.fd);
    					printf("closed client: %d \n", ep_events[i].data.fd);
    
    					for(i=0; i<clnt_cnt; i++) 
    					{
    						if(clnt_sock==clnt_socks[i])
    						{
    							while(i++<clnt_cnt-1)
    								clnt_socks[i]=clnt_socks[i+1];
    							break;
    						}
    					}
    					clnt_cnt--;
    				}
    				else
    				{
    					send_msg(buf, str_len);
    				}
    			}
    		}
    	}
    	close(serv_sock);
    	close(epfd);
    	return 0;
    }
    
    void send_msg(char * msg, int len)   // send to all
    {
    	int i;
    	for(i=0; i<clnt_cnt; i++)
    		write(clnt_socks[i], msg, len);
    }
    
    void error_handling(char *buf)
    {
    	fputs(buf, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    /**********************************char_EPETserv.c*************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    
    #define BUF_SIZE 100
    #define MAX_CLNT 256
    #define EPOLL_SIZE 50
    
    void setnonblockingmode(int fd);
    void error_handling(char *buf);
    void send_msg(char * msg, int len);
    
    int clnt_cnt=0;
    int clnt_socks[MAX_CLNT];
    
    int main(int argc, char *argv[])
    {
    	int serv_sock, clnt_sock;
    	struct sockaddr_in serv_adr, clnt_adr;
    	socklen_t adr_sz;
    	int str_len, i;
    	char buf[BUF_SIZE];
    
    	struct epoll_event *ep_events;
    	struct epoll_event event;
    	int epfd, event_cnt;
    
    	if(argc!=2) {
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_adr.sin_port=htons(atoi(argv[1]));
    	
    	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
    		error_handling("bind() error");
    	if(listen(serv_sock, 5)==-1)
    		error_handling("listen() error");
    
    	epfd=epoll_create(EPOLL_SIZE);
    	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
    
    	setnonblockingmode(serv_sock);
    	event.events=EPOLLIN;
    	event.data.fd=serv_sock;	
    	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
    
    	while(1)
    	{
    		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
    		if(event_cnt==-1)
    		{
    			puts("epoll_wait() error");
    			break;
    		}
    
    		puts("return epoll_wait");
    		for(i=0; i<event_cnt; i++)
    		{
    			if(ep_events[i].data.fd==serv_sock)
    			{
    				adr_sz=sizeof(clnt_adr);
    				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
    				setnonblockingmode(clnt_sock);
    				event.events=EPOLLIN|EPOLLET;
    				event.data.fd=clnt_sock;
    				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
    				clnt_socks[clnt_cnt++]=clnt_sock;
    				printf("connected client: %d \n", clnt_sock);
    			}
    			else
    			{
    					while(1)
    					{
    						str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
    						if(str_len==0)    // close request!
    						{
    							epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
    							close(ep_events[i].data.fd);
    
    							for(i=0; i<clnt_cnt; i++) 
    							{
    								if(ep_events[i].data.fd==clnt_socks[i])
    								{
    									while(i++<clnt_cnt-1)
    										clnt_socks[i]=clnt_socks[i+1];
    									break;
    								}
    							}
    							clnt_cnt--;
    							printf("closed client: %d \n", ep_events[i].data.fd);
    							break;
    						}
    						else if(str_len<0)
    						{
    							if(errno==EAGAIN)
    								break;
    						}
    						else
    						{
    							send_msg(buf, str_len);
    						}
    				}
    			}
    		}
    	}
    	close(serv_sock);
    	close(epfd);
    	return 0;
    }
    
    
    void send_msg(char * msg, int len)   // send to all
    {
    	int i;
    	for(i=0; i<clnt_cnt; i++)
    		write(clnt_socks[i], msg, len);
    }
    void setnonblockingmode(int fd)
    {
    	int flag=fcntl(fd, F_GETFL, 0);
    	fcntl(fd, F_SETFL, flag|O_NONBLOCK);
    }
    void error_handling(char *buf)
    {
    	fputs(buf, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    

第十八章 多线程服务器的实现

  1. 单CPU系统中如何同时执行多个进程?请解释该过程中发生的上下文切换

    因为系统将CPU切分成多个微小的块后分配给多个进程,为了分时使用CPU,需要”上下文切换“过程。”上下文切换“是指,在CPU改变运行对象的过程中农,执行准备的过程将之前执行的进程数据从换出内存,并将待执行额进程数据传到内存的工作区域

  2. 为何线程的上下文切换速度相对更快?线程间数据交换为何不需要类似IPC的特别技术?

    因为线程进行上下文切换时不需要切换数据区和堆区。同时,可以利用数据区和堆区进行数据交换

  3. 请从执行流角度说明进程和线程的区别

    • 进程:在操作系统中构成单独执行流的单位
    • 线程:在进程内构成单独执行流的单位
  4. bcd

  5. d

  6. 请说明完全销毁Linux线程的两种方法

    pthread_join函数和pthread_detach函数

  7. 利用多线程实现回声客户端,要求所有线程共享保存客户端消息的内存空间

    //echo_thrserv.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <pthread.h>
    
    #define BUF_SIZE 100
    void * handle_clnt(void * arg);
    void error_handling(char *buf);
    
    char buf[BUF_SIZE];
    pthread_mutex_t mutx;
    
    int main(int argc, char *argv[])
    {
    	int serv_sock, clnt_sock;
    	struct sockaddr_in serv_adr, clnt_adr;
    	int clnt_adr_sz;
    	pthread_t t_id;
    
    	if(argc!=2) {
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    
    	pthread_mutex_init(&mutx, NULL);
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_adr.sin_port=htons(atoi(argv[1]));
    	
    	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
    		error_handling("bind() error");
    	if(listen(serv_sock, 5)==-1)
    		error_handling("listen() error");
    
    	while(1)
    	{
    		clnt_adr_sz=sizeof(clnt_adr);
    		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&clnt_adr_sz);
    		pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
    		pthread_detach(t_id);
    		printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
    	}
    
    	close(serv_sock);
    	return 0;
    }
    
    void * handle_clnt(void * arg)
    {
    	int clnt_sock=*((int*)arg);
    	int str_len=0;
    	
    	while(1)
    	{
    		pthread_mutex_lock(&mutx);
    		str_len=read(clnt_sock, buf, sizeof(buf));
    		if(str_len<=0)
    			break;
    		else
    			write(clnt_sock, buf, str_len);
    		pthread_mutex_unlock(&mutx);
    	}
    	
    	close(clnt_sock);
    	return NULL;
    }
    void error_handling(char *buf)
    {
    	fputs(buf, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
  8. 上一题有什么问题?

    如果不同步,两个以上线程会同时访问内存空间,从而引发问题。相反,如果同步,由于read函数中调用了临界区的参数,可能会发生无法读取其他客户端发送过来的字符串而必须等待的情况

  • 10
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
编辑推荐 本书是网络构建原型——4.4BSD的建造者的倾力之作,《TCP/IP解析》中的第1卷,主要讲述TCP/IP协议方面的内容。与其他的TCP/IP书藉的最大不同在于,本书不仅仅讲述了RFCS的标准协议,而且结合大量实例讲述了TCP/IP协议包的定义原因及在各种不同的操作系统中(如Sunos4.1.3、Soloris2.2、AIX3.2.2)的应用与工作方式,这样可以以动态方式讲述TCP/IP的知识,使读者可以轻松掌握TCP/IP的知识:路由协议、寻址协议、组控制协议、简单邮件传输协议等。全书内容实用性强,是在校生学习TCP/IP知识的良师益友。 内容简介  《TCP/IP详解,卷1:协议》是一本完整而详细的TCP/IP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作。 本书适合作为计算机专业学生学习网络的教材和教师参考书。也适用于研究网络的技术人员。 作者简介 W.Richard Stevens(1951-1999)是一位非常受人尊敬的专家,除了《TCP/IP详解》三卷本外,他还有其他两部最为畅销的作品:《UNIX环境高级编程》和《UNIX网络编程》(两卷本)。 目录 译者序 前言 第1 概述 1 1.1 引言 1 1.2 分层 1 1.3 TCP/IP的分层 4 1.4 互联网的地址 5 1.5 域名系统 6 1.6 封装 6 1.7 分用 8 1.8 客户-服务器模型 8 1.9 端口号 9 1.10 标准化过程 10 1.11 RFC 10 1.12 标准的简单服务 11 1.13 互联网 12 1.14 实现 12 1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口 20 2.8 最大传输单元MTU 21 2.9 路径MTU 21 2.10 串行线路吞吐量计算 21 2.11 小结 22 第3 IP:网际协议 24 3.1 引言 24 3.2 IP首部 24 3.3 IP路由选择 27 3.4 子网寻址 30 3.5 子网掩码 32 3.6 特殊情况的IP地址 33 3.7 一个子网的例子 33 3.8 ifconfig命令 35 3.9 netstat命令 36 3.10 IP的未来 36 3.11 小结 37 第4 ARP:地址解析协议 38 4.1 引言 38 4.2 一个例子 38 4.3 ARP高速缓存 40 4.4 ARP的分组格式 40 4.5 ARP举例 41 4.5.1 一般的例子 41 4.5.2 对不存在主机的ARP请求 42 4.5.3 ARP高速缓存超时设置 43 4.6 ARP代理 43 4.7 免费ARP 45 4.8 arp命令 45 4.9 小结 46 第5 RARP:逆地址解析协议 47 5.1 引言 47 5.2 RARP的分组格式 47 5.3 RARP举例 47 5.4 RARP服务器的设计 48 5.4.1 作为用户进程的RARP服务器 49 5.4.2 每个网络有多个RARP服务器 49 5.5 小结 49 第6 ICMP:Internet控制报文协议 50 6.1 引言 50 6.2 ICMP报文的类型 50 6.3 ICMP地址掩码请求与应答 52 6.4 ICMP时间戳请求与应答 53 6.4.1 举例 54 6.4.2 另一种方法 55 6.5 ICMP端口不可达差错 56 6.6 ICMP报文的4.4BSD处理 59 6.7 小结 60 第7 Ping程序 61 7.1 引言 61 7.2 Ping程序 61 7.2.1 LAN输出 62 7.2.2 WAN输出 63 7.2.3 线路SLIP链接 64 7.2.4 拨号SLIP链路 65 7.3 IP记录路由选项 65 7.3.1 通常的例子 66 7.3.2 异常的输出 68 7.4 IP时间戳选项 69 7.5 小结 70 第8 Traceroute程序 71 8.1 引言 71 8.2 Traceroute 程序的操作 71 8.3 局域网输出 72 8.4 广域网输出 75 8.5 IP源站选路选项 76 8.5.1 宽松的源站选路的traceroute程序示例 78 8.5.2 严格的源站选路的traceroute程序示例

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值