《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中的线程同步

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

  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 
    #include 
    #include  
    #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 
    #include 
    #include 
    
    #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 \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

第二十二章 重叠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 
    #include 
    #include 
    #include 
    #include 
    
    #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; ihClntSock=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; ibuffer, 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

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

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

  1. abe

  2. a

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

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

你可能感兴趣的:(网络编程,TCP/IP,网络编程)