实验五 I/O 模型网络程序实验

一、 实验目的

        掌握 Winsock I/O 模型工作原理;
        熟悉 I/O 模型中使用的 Winsock 接口函数;
        掌握使用 I/O 模型进行网络程序设计的编程步骤;

二、实验设计

        1、背景知识
        Windows套接字工作模式
        Windows 套接字工作模式分为两类:阻塞(Blocking)模式和非阻塞(NonBlocking)模式。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回(将控制权交还给程序),这就意味着任一个线程在某一时刻只能执行一个输入/输出(I/O)操作,而且应用程序很难同时通过多个建好连接的套接字进行通信。正如我们在以前的实验中看到的,服务端或客户端在运行到recv()函数时会进入阻塞状态,直到对方响应时(即运行了send()函数后)才能继续执行下去。在默认的情况下,套接字工作在阻塞模式。在非阻塞模式下,Winsock函数会立即返回,并交出程序的控制权,这就为我们实际需要中同时管理多个连接、并维持与每个连接的及时通信提供了基础。在实际问题中,Winsock编程经常需要使用多线程的方法使程序对用户的动作进行及时响应,但会增加一些开销,并且扩展性比较差。尽管非阻塞模式套接字在使用上不如阻塞套接字简单,但它在功能上还是非常强大的,同时简化了我们针对实际问题的编程过程。
        WinsockI/O模型
1)选择模型(SelectModel)
2)异步选择模型(WSAAsyncSelectModel)
3)事件选择模型(WSAEventSelectModel)
4)重叠模型(OverlappedModel)
5)完成端口模型(CompletionPortModel
        本次实验使用了事件选择模型,所以下面介绍事件选择模型:
        WSAEventSelect模型是WinSock提供的另一个异步I/O模型,与WSAAsyncSelect模型类似,也允许应用程序在一个或多个套接字上接收以事件为基础的网络事件通知,并且支持的网络事件与WSAAsyncSelect模型一样。与WSAAsyncSelect模型的主要区别在于网络事件会被发送到一个事件对象句柄,而不是发送到一个窗口。WSAEventSelect函数原型如下:intWSAEventSelect(SOCKETs,WSAEVENThEventObject,longlNetworkEvents);
        使用WSAEventSelect模型编程的基本步骤:
(1).创建一个事件句柄表和一个对应的套接字句柄表。
(2).每创建一个套接字,就创建一个事件对象,把它们的句柄分别放入上面的两个表中,并调用WSAEventSelect将二者关联起来。
(3).调用WSAWaitForMultipleEvents在所有事件对象上等待(bWaitAll=FALSE),函数返回后,从第一个有信号的事件对象开始检查事件对象表中的事件对象是否有信号(再次调用WSAWaitForMultipleEvents)。
(4).调用WSAEnumNetworkEvents(),获取套接字上相应的网络事件并处理,然后继续在事件对象上等待。
        2、实验设计
        (1)、利用事件选择模型,构建一个 TCP 服务器,该服务器能:
        接受客户端连接时显示客户端的 IP,PORT信息;
        接收客户端连接时显示其连接编号,客户端退出时显示关闭的连接编号;
        能显示客户端发来的数据;
        能从键盘输入数据并发到客户端;
        其他数据传送功能(可选)。
        (2)、 编写客户端程序,使之能:
        从键盘输入数据并发送到服务器;
        能接收服务器发来的数据;
        当输入“bye”时退出程序。

三、实验过程

        (一)实验步骤
        1、编写TCP回显服务器。
        2、编写TCP客户端。
        3、运行、调试。

        (二)实验过程

实验五 I/O 模型网络程序实验_第1张图片

        运行服务器和客户端,建立连接之后,服务器会显示客户端的信息。

实验五 I/O 模型网络程序实验_第2张图片

        客户端与服务器之间能进行通信而且能互相发消息。

实验五 I/O 模型网络程序实验_第3张图片

        服务器能选择客户端进行发消息。

实验五 I/O 模型网络程序实验_第4张图片

        当某一客户端输入“bye”之后,关闭连接。

四、讨论与分析

        1. 你所选用的I/O模型是如何判断套接字上何时可以收发数据的或者数据收发已完成的? 
        答:事件选择(WSAEventSelect)模型是另一个有用的异步 I/O 模型。和 WSAAsyncSelect 模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知,最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递到一个窗口例程。WSACreateEvent 函数的返回值很简单,就是一个创建好的事件对象句柄,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型 (FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE等):

int WSAEventSelect( 

__in SOCKET s,                       //代表感兴趣的套接字

__in WSAEVENT hEventObject,         //指定要与套接字关联在一起的事件对象,即用 WSACreateEvent 创建的那一个

__in long lNetworkEvents            //对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。

);

        其中参数 lNetworkEvents可以用以下数值进行OR操作, FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据, FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据,FD_ACCEPT 应用程序想接收与进入连接有关的通知, FD_CONNECT 应用程序想接收与一次连接完成的通知, FD_CLOSE 应用程序想接收与套接字关闭的通知
        2. 简述你所使用的I/O模型的编程步骤。
        (1).创建一个事件句柄表和一个对应的套接字句柄表。
        (2).每创建一个套接字,就创建一个事件对象,把它们的句柄分别放入上面的两个表中,并调用WSAEventSelect将二者关联起来。
        (3).调用WSAWaitForMultipleEvents在所有事件对象上等待(bWaitAll=FALSE),函数返回后,从第一个有信号的事件对象开始检查事件对象表中的事件对象是否有信号(再次调用WSAWaitForMultipleEvents)。
        (4).调用WSAEnumNetworkEvents(),获取套接字上相应的网络事件并处理,然后继续在事件对象上等待。
        3. 在你所使用的I/O中如何判断发生网络事件或者IO完成的套接字? 
        答:WSAWaitForMultipeEvent判断,WSAWaitForMultipeEvent函数会等待网络事件的发生。

五、总结

        Socket编程可以分为阻塞和非阻塞模式两种开发模式,阻塞模式是在指定的socket上调用函数执行操作时,在没有完成操作之前,函数不会立即返回,而非阻塞模式是无论是否完成都立即返回。本次实验最重要的是熟悉了事件选择模型,该模型的核心是WSAEventSelect()函数,它可以为socket注册网络事件,并将指定的事件对象关联到指定的网络事件集合。WSAEnumNetworkEvents()获取套接字上相应的网络事件并处理,然后继续在事件对象上等待。根据判断网络事件类型 FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE来进行下一步操作。

六、附录:关键代码

1、服务器端:

int _tmain(int argc, _TCHAR* argv[])
{
	unsigned short dWsa = MAKEWORD(2, 2);
	WSADATA lpwsadata;  
	if (0 != WSAStartup(dWsa, &lpwsadata))   
		cout << "error";     
	else    cout << "-------------------服务器已启动----------------------" << endl;  
	SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
	if (sListen == INVALID_SOCKET)  
	{ 
		cout << "FAILED SOCKET()"; 
		return 0; 
	}  
	sockaddr_in saddr;  
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(5555);  
	saddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  
	if (bind(sListen, (LPSOCKADDR)&saddr, sizeof(saddr)) == SOCKET_ERROR)  
	{ 
		cout << "failed bind()";   
		return 0;
	}  
	listen(sListen, 5);     
	//数组保存;  
	int totalevent=0;  
	SOCKET SOCKETARRAY[WSA_MAXIMUM_WAIT_EVENTS];  //socket数组,WSAWaitForMultipleEvents 只能支持由 WSA_MAXIMUM_WAIT_EVENTS 对象规定的一个最大值,在此定义成64个
	WSAEVENT EVENTARRAY[WSA_MAXIMUM_WAIT_EVENTS]; //事件数组  
	WSAEVENT event=WSACreateEvent();  
	WSAEventSelect(sListen,event,FD_ACCEPT|FD_CLOSE);  
	EVENTARRAY[totalevent]=event;  
	SOCKETARRAY[totalevent]=sListen;  
	totalevent++;  
	while(1)
	{ 
		int index=WSAWaitForMultipleEvents(totalevent,EVENTARRAY,FALSE,WSA_INFINITE,NULL);   
		index=index-WSA_WAIT_EVENT_0;       
		cout<<"发生事件对象索引:"<0)      
					{       
						buffer[ret]='\0';       
						cout<<"收到消息: "<> sSend;
						cout <<"请输入要发送的信息" << endl;
						cin>>buffer;
						SOCKETARRAY[i] = sSend;
						send(SOCKETARRAY[i],buffer,strlen(buffer),0);       //
						send(SOCKETARRAY[i],buffer,ret,0); 
						system("color 0F");
						if (WSAGetLastError()==0)
						cout<<"消息成功发送!"<
2、客户端:

int _tmain(int argc, _TCHAR* argv[])
{
	unsigned short dWsa = MAKEWORD(2, 2);//无符号短整型  宏创建一个被指定变量连接而成的WORD变量。返回一个WORD变量。  
	WSADATA lpwsadata;//用来存储 被WSAStartup函数调用后返回的 Windows Sockets 数据       
	if( 0  != WSAStartup(dWsa,&lpwsadata))      
		cout<<"error";      
	else cout<<"-------------------客户端已启动----------------------"<>buf;     
		if(!strcmp(buf,"bye"))     
		{      
			closesocket(sClient);      
			return 0;
		}      
		send(sClient,buf,strlen(buf),0);
		//system("color 0F");
		int num=recv(sClient,buf,1024,0);     
		if(num>0)     
		{     
			buf[num]='\0'; 
			cout<

          注:本博客源代码下载地址:http://download.csdn.net/detail/dmxexcalibur/9904524
 


你可能感兴趣的:(Windows网络编程)