《TCP/IP网络编程》课后练习答案第三+四部分19~24章 尹圣雨

第十九章 Windows平台下线程的使用

  1. bcd

  2. bd

  3. 请比较从内存中完全销毁Windows线程和Linux线程的方法

    Windows上的线程销毁是随其线程main函数的返回,在内存中自动销毁的。但是linux的线程销毁必须经过pthread_join函数或者pthread_detach函数的响应才能在内存空间中完全销毁

  4. 通过线程创建过程解释内核对象、线程、句柄之间的关系

    线程也属于操作系统的资源,因此会伴随着内核对象的创建,并为了引用内核对象而返回句柄。整理的话,可以通过句柄区分内核对象,通过内核对象区分线程

  5. √ × ×

  6. 请解释"auto-reset模式"和"manual-reset模式"的内核对象。区分二者的内核对象特征是什么?

    WaitForSingleObject函数针对单个内核对象验证signaled状态时,由于发生事件(变为signaled状态)返回时,有时会把相应内核对象再次改为non-signaled状态。这种可以再次进入non-signaled状态的内核对象称为"auto-reset模式"的内核对象,而不会自动跳转到non-signaled状态的内核对象称为"manual-reset模式"的内核对象。

第二十章 Windows中的线程同步

  1. c

  2. × √ × √

  3. 本章示例SyncSema_win.c的Read函数中,退出临界区需要较长时间,请给出解决方案并实现

    对可能超过一定时间处于阻塞状态的函数,如scanf函数的响应,应尽量不纳入临界区

    unsigned WINAPI Read(void * arg)
    {
       int i, rdData;
       for(i=0; i<5; i++)
       {
           fputs("Input num: ", stdout);
           scanf("%d", &rdData);
           
           WaitForSingleObject(semTwo, INFINITE);
           num=rdData;
           ReleaseSemaphore(semOne, 1, NULL);
       }
       return 0;	
    }
    
    
  4. 更改程序,并运行结果

    #include <stdio.h>
    #include <windows.h>
    #include <process.h> 
    #define STR_LEN		100
    
    unsigned WINAPI NumberOfA(void *arg);
    unsigned WINAPI NumberOfOthers(void *arg);
    
    static char str[STR_LEN];
    static HANDLE hSema;
    
    int main(int argc, char *argv[]) 
    {	
    	HANDLE  hThread1, hThread2;
    
    	hSema=CreateSemaphore(NULL, 0, 2, NULL);
    	hThread1=(HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
    	hThread2=(HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
    
    	fputs("Input string: ", stdout); 
    	fgets(str, STR_LEN, stdin);
    	ReleaseSemaphore(hSema, 2, NULL);
    
    	WaitForSingleObject(hThread1, INFINITE);
    	WaitForSingleObject(hThread2, INFINITE);
    	ResetEvent(hSema);
     	CloseHandle(hSema);
        return 0;
    }
    
    unsigned WINAPI NumberOfA(void *arg) 
    {
    	int i, cnt=0;
    	WaitForSingleObject(hSema, INFINITE);
    	for(i=0; str[i]!=0; i++)
    	{
    		if(str[i]=='A')
    			cnt++;
    	}
    	printf("Num of A: %d \n", cnt);
    	return 0;
    }
    unsigned WINAPI NumberOfOthers(void *arg) 
    {
    	int i, cnt=0;
    	WaitForSingleObject(hSema, INFINITE);
    	for(i=0; str[i]!=0; i++) 
    	{
    		if(str[i]!='A')
    			cnt++;
    	}
    	printf("Num of others: %d \n", cnt-1);
    	return 0;
    }
    

第二十一章 异步通知I/O模型

  1. 结合send & recv 函数解释同步和异步方式的I/O。并请说明同步I/O的缺点,以及怎样通过异步I/O进行解决

    同步I/O是指数据发送的时间点和函数返回的时间一致的I/O方式。即,send函数返还的时间是数据传输完毕的时间,recv函数返还的时间是数据接收完毕的时间。但是异步I/O是指I/O函数的返回时刻与数据收发的完成时刻不一致。同步I/O的缺点是,直到输入输出完成为止,都处于阻塞状态。在这段时间里,CPU实际上是没有被利用的,但由于阻塞状态,无法进行其他工作。从这一点看,异步I/O对CPU的使用效率优于同步I/O

  2. 异步I/O并不是所有状况下的最佳选择。它具有哪些缺点?何种情况下同步I/O更优?可以参考异步I/O相关源代码,亦可结合线程进行说明

    异步I/O有优点也有缺点,缺点在于要在完成输入输出以后去确认。如果服务器的服务非常简单,回复所需的数据量很小,就会很不方便。特别是在每一个用户都生成一个线程的服务器中,没有必要一定使用异步I/O

  3. 全√

  4. 请从源代码的角度说明select函数和WSAEventSelect函数再使用上的差异

    使用select函数时,要循环将观察的文件描述符传到select函数中。而如果使用WSAEventSelect函数,观察的对象会被操作系统记录下俩,无需每次都注册。而且,如果是用select函数,则到事件发生为止,应保持阻塞状态;而如果使用WSAEventSelect函数,到事件发生为止,都不应该进入阻塞。

  5. 第17章的epoll可以在条件触发和边缘触发这2中方式下工作。那种方式更适合异步I/O模型?为什么?请概括说明

    边缘触发很好地配合异步I/O模型。边缘触发模式并不会因为输入缓冲中有数据而发起消息。即在边缘触发中,对异步的存取发生过程,不会发起消息,而只注册新的输入事件。但是在条件触发中,当输入缓冲中有有数据时,仍然会发起消息

  6. Linux中的epoll同样属于异步I/O模型。请说明原因

    epoll方式将观察的对象的连接过程和事件发生过程分离成两种处理模式。因此,可以在事件发生后,在希望的时间确认活动是否发生。因此epoll可以说是一种异步I/O模式

  7. 如何获取WSAWaitForMultipleEvents可以监视的最大句柄数?请编写代码读取该值

    获取WSA_MAXIMUM_WAIT_EVENTS的值即可

  8. 为何异步通知I/O模型中的事件对象必须是manual-reset模式

    事件的发生,需要由WSAWaitForMultipleEvents函数调用寻找相应的句柄调用执行的;首先通过调用WSAWaitForMultipleEvents函数寻找事件发生的句柄数目,然后遍历找到具体发生的事件进行处理。如果不是manual-reset模式,就会自动转换为signaled状态,无法用第二次的WSAWaitForMultipleEvents函数进行操作处理

  9. 代码

    /*****************************AysnNotiChatServ.c*****************************/
    #include <stdio.h>
    #include <string.h>
    #include <winsock2.h>
    
    #define BUF_SIZE 100
    
    void SendMsg(SOCKET clntSocks[], int clntCnt, char * msg, int len);
    void CompressSockets(SOCKET hSockArr[], int idx, int total);
    void CompressEvents(WSAEVENT hEventArr[], int idx, int total);
    void ErrorHandling(char *msg);
    
    int main(int argc, char *argv[])
    {
    	WSADATA wsaData;
    	SOCKET hServSock, hClntSock;
    	SOCKADDR_IN servAdr, clntAdr;
    
    	SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS]; 
    	WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];
    	WSAEVENT newEvent;
    	WSANETWORKEVENTS netEvents;
    
    	int numOfClntSock=0;
    	int strLen, i;
    	int posInfo, startIdx;
    	int clntAdrLen;
    	char msg[BUF_SIZE];
    	
    	if(argc!=2) {
    		printf("Usage: %s <port>\n", argv[0]);
    		exit(1);
    	}
    	if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    		ErrorHandling("WSAStartup() error!");
    
    	hServSock=socket(PF_INET, SOCK_STREAM, 0);
    	servAdr.sin_family=AF_INET;
    	servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
    	servAdr.sin_port=htons(atoi(argv[1]));
    
    	if(bind(hServSock, (SOCKADDR*) &servAdr, sizeof(servAdr))==SOCKET_ERROR)
    		ErrorHandling("bind() error");
    
    	if(listen(hServSock, 5)==SOCKET_ERROR)
    		ErrorHandling("listen() error");
    
    	newEvent=WSACreateEvent();
    	if(WSAEventSelect(hServSock, newEvent, FD_ACCEPT)==SOCKET_ERROR)
    		ErrorHandling("WSAEventSelect() error");
    
    	hSockArr[numOfClntSock]=hServSock;
    	hEventArr[numOfClntSock]=newEvent;
    	numOfClntSock++;
    
    	while(1)
    	{
    		posInfo=WSAWaitForMultipleEvents(
    			numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);
    		startIdx=posInfo-WSA_WAIT_EVENT_0;
    
    		for(i=startIdx; i<numOfClntSock; i++)
    		{
    			int sigEventIdx=
    				WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);
    			if((sigEventIdx==WSA_WAIT_FAILED || sigEventIdx==WSA_WAIT_TIMEOUT))
    			{
    				continue;
    			}
    			else
    			{
    				sigEventIdx=i;
    				WSAEnumNetworkEvents(
    					hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents);
    				if(netEvents.lNetworkEvents & FD_ACCEPT)
    				{
    					clntAdrLen=sizeof(clntAdr);
    					hClntSock=accept(
    						hSockArr[sigEventIdx], (SOCKADDR*)&clntAdr, &clntAdrLen);
    					newEvent=WSACreateEvent();
    					WSAEventSelect(hClntSock, newEvent, FD_READ|FD_CLOSE);
    
    					hEventArr[numOfClntSock]=newEvent;
    					hSockArr[numOfClntSock]=hClntSock;
    					numOfClntSock++;
    					puts("connected new client...");
    				}
    
    				if(netEvents.lNetworkEvents & FD_READ)
    				{
    					strLen=recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);
    					SendMsg(hSockArr, numOfClntSock, msg, strLen);
    				}
    
    				if(netEvents.lNetworkEvents & FD_CLOSE)
    				{
    					WSACloseEvent(hEventArr[sigEventIdx]);
    					closesocket(hSockArr[sigEventIdx]);
    					
    					numOfClntSock--;
    					CompressSockets(hSockArr, sigEventIdx, numOfClntSock);
    					CompressEvents(hEventArr, sigEventIdx, numOfClntSock);
    				}
    			}
    		}
    	}
    	WSACleanup();
    	return 0;
    }
    
    void SendMsg(SOCKET clntSocks[], int clntCnt, char * msg, int len)   // send to all
    {
    	int i;
    	for(i=0; i<clntCnt; i++)
    		send(clntSocks[i], msg, len, 0);
    }
    void CompressSockets(SOCKET hSockArr[], int idx, int total)
    {
    	int i;
    	for(i=idx; i<total; i++)
    		hSockArr[i]=hSockArr[i+1];
    }
    void CompressEvents(WSAEVENT hEventArr[], int idx, int total)
    {
    	int i;
    	for(i=idx; i<total; i++)
    		hEventArr[i]=hEventArr[i+1];
    }
    void ErrorHandling(char *msg)
    {	
    	fputs(msg, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    

第二十二章 重叠I/O模型

  1. 异步通知I/O模型和重叠I/O模型在异步处理方面有哪些区别?

    在异步I/O模型下,以异步的方式处理IO事件发生的过程

    在重叠I/O模型下,还需要异步确认I/O完成的过程

  2. 请分析非阻塞I/O、异步I/O、重叠I/O之间的关系

    异步I/O意味着异步处理,确认I/O完成的过程。为了实现这一过程,I/O必须在非阻塞模式下运行。当I/O以非阻塞模式运行并且I/O以异步方式进行时,I/O会重叠

  3. 阅读下面代码,指出问题并给出解决方案

    若accpet函数返回失败,WSARecv传入的overlapped会报错,因为overlapped事先没有任何传入。若accept返回成功,因为所有的OVERLAPPED结构都相同,可以使用同一个OVERLAPPED结构体变量给多个WSARecv函数调用

  4. 请从源代码角度说明调用WSASend函数后如何验证进入Pending状态

    WSASend函数再返回SOCKET_ERROR的状态下,利用WSAGetLastError函数是否返回WSA_IO_PENDING来验证

  5. 线程的"alertable wait状态"的含义是什么?说出能使线程进入这种状态的两个函数

    所谓"alertable wait状态"是指等待接收操作系统信息的状态,在接下来的函数调用中,线程会进入alertable wait状态

    • WaitForSingleObjectEx
    • WaitForMultipleObjectEx
    • WSAWaitForMultipleEvents
    • SleepEx

第二十三章 IOCP

  1. 完成端口对象将分配多个线程用于处理I/O。如何创建这些线程?如何分配?请从源码级别进行说明

    Completion Port对象所分摊的线程由程序员创建。这些线程为了确认I/O的完成会调用GetQueuedCompletionStatus函数。虽然任何线程都能调用GetQueuedCompletionStatus函数,但实际得到I/O完成信息的线程数不会超过调用CreateIoCompletionPort函数时指定的最大线程数。

  2. CreateIoCompletionPort函数与其他函数不同,提供2种功能。请问是哪2种?

    Completion Port对象的产生,CP对象与套接字进行连接

  3. 完成端口对象和套接字之间的连接意味着什么?如何连接?

    首先通过CreateIoCompletionPort生成CP对象。之后再次使用CreateIoCompletionPort函数将CP对象与套接字进行连接。

  4. cd

  5. √ √ ×

  6. 代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <process.h>
    #include <winsock2.h>
    #include <windows.h>
    
    #define BUF_SIZE 100
    #define MAX_CLNT 256
    #define READ	3
    #define	WRITE	5
    
    typedef struct    // socket info
    {
    	SOCKET hClntSock;
    	SOCKADDR_IN clntAdr;
    } PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
    
    typedef struct    // buffer info
    {
    	OVERLAPPED overlapped;
    	WSABUF wsaBuf;
    	char buffer[BUF_SIZE];
    } PER_IO_DATA, *LPPER_IO_DATA;
    
    DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
    void SendMsg(char * msg, DWORD len);
    void ErrorHandling(char *message);
    
    int clntCnt=0;
    SOCKET clntSocks[MAX_CLNT];
    
    int main(int argc, char* argv[])
    {
    	WSADATA	wsaData;
    	HANDLE hComPort;	
    	SYSTEM_INFO sysInfo;
    	LPPER_IO_DATA ioInfo;
    	LPPER_HANDLE_DATA handleInfo;
    
    	SOCKET hServSock;
    	SOCKADDR_IN servAdr;
    	int recvBytes, i, flags=0;
    
    	if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    		ErrorHandling("WSAStartup() error!"); 
    
    	hComPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    	GetSystemInfo(&sysInfo);
    
    	for(i=0; i<sysInfo.dwNumberOfProcessors; i++)
    		_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);
    
    	hServSock=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    	servAdr.sin_family=AF_INET;
    	servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
    	servAdr.sin_port=htons(atoi(argv[1]));
    
    	bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
    	listen(hServSock, 5);
    	
    	while(1)
    	{	
    		SOCKET hClntSock;
    		SOCKADDR_IN clntAdr;		
    		int addrLen=sizeof(clntAdr);
    		
    		hClntSock=accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
    		clntSocks[clntCnt++]=hClntSock;
    
    		handleInfo=(LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));		
    		handleInfo->hClntSock=hClntSock;
    		memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
    
    		CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
    		
    		ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
    		memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));		
    		ioInfo->wsaBuf.len=BUF_SIZE;
    		ioInfo->wsaBuf.buf=ioInfo->buffer;
    
    		WSARecv(handleInfo->hClntSock,	&(ioInfo->wsaBuf),	
    			1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);			
    	}
    	return 0;
    }
    
    DWORD WINAPI EchoThreadMain(LPVOID pComPort)
    {
    	HANDLE hComPort=(HANDLE)pComPort;
    	SOCKET sock;
    	DWORD bytesTrans;
    	LPPER_HANDLE_DATA handleInfo;
    	LPPER_IO_DATA ioInfo;
    	DWORD flags=0;
    	int i;
    	
    	while(1)
    	{ 
    		GetQueuedCompletionStatus(hComPort, &bytesTrans, 
    			(LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
    		sock=handleInfo->hClntSock;
    
    		puts("message received!");
    		if(bytesTrans==0)    // EOF
    		{
    			for(i=0; i<clntCnt; i++)   // remove disconnected client
    			{
    				if(sock==clntSocks[i])
    				{
    					while(i++<clntCnt-1)
    					clntSocks[i]=clntSocks[i+1];
    					break;
    				}
    			}
    			clntCnt--;
    
    			closesocket(sock);
    			free(handleInfo); free(ioInfo);
    			continue;		
    		}
    
    		SendMsg(ioInfo->buffer, bytesTrans);
    
    		memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));			
    		WSARecv(sock, &(ioInfo->wsaBuf), 
    			1, NULL, &flags, &(ioInfo->overlapped), NULL);
    	}
    	return 0;
    }
    
    void SendMsg(char * msg, DWORD len)   // send to all
    {
    	int i;
    	for(i=0; i<clntCnt; i++)
    		send(clntSocks[i], msg, len, 0);
    }
    void ErrorHandling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    

第二十四章 制作HTTP服务器端

  1. abe

  2. a

  3. IOCP和epoll是可以保证高性能的典型服务器端模型,但如果在基于HTTP协议的Web服务器端使用这些模型,则无法保证一定能得到高性能。请说明原因

    IOCP和epoll都是可以管理两个以上socket的服务器模型。即在称为观察对象的端口中,感知与IO相关的事件发生的端口,并处理相关的I/O服务器模式。如实对网络服务器来说,并不要管理两个以上的socket。因为只要完成一次请求和回答的过程,连接就会结束。因此,用IOCP和epoll提高性能,有一定的局限性

  • 12
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值