Windows平台网络编程

windows socket分模式、模型两种不同的描述方式:
阻塞模式:用于当一个套接字被调用时,决定调用函数的阻塞行为(阻塞、非阻塞)
I/O模型:描述了一个应用程序如何对套接字上的I/O进行管理(select模型、WSAAsyncSelect模型、WSAEventSelect模型、重叠I/O模型(事件通知、完成例程)、完成端口模型)

模式和模型是两个不同概念,且相互无关。

阻塞模式:
当使用socket()函数和WSASocket()函数创建套接字时,默认的套接字都是阻塞的.
对于阻塞模式套接字,只有部分socket api会发生阻塞,如:accept,connect,read,send等;bind,listen则会立即完成.
阻塞模式用来开发比较简单,容易实现,发送数据量较少的网络开发.
不足是难以管理大量已经连接的套接字.

非阻塞模式:
可以通过ioctlsocket()将阻塞套接字设置为非阻塞模式.

unsigned long ul = 1;
SOCKET s=socket(AF_INET, SOCK_STREAM, 0);
int nRet = ioctrlsocket(s, FIONBIO, &ul);
if(nRet == SOCKET_ERROR)
{
     // 设置套接字非阻塞模式,失败处理
}

除了ioctrlsocket外,当在调用WSAAsyncSelect或者WSAEventSelect函数时,会自动将套接字设置为非阻塞模式.
非阻塞模式需要编写更多的代码,以便处理可能出现的失败返回.相对来说,更难使用.
但是,非阻塞套接字在控制建立的多个连接,在数据的收发量不均,时间不定等方面明显具有优势.通常情况下非阻塞套接字的使用需要借助套接字的I/O模型.

阻塞模式的套接字执行I/O操作时,如果执行操作的条件没有得到满足,线程就会被阻塞在该调用的函数上。程序不得不处于等待状态。该调用函数什么时候返回,不得而知。
非阻塞模式套接字执行I/O操作时,在任何情况下,调用函数都会立刻返回。软件开发人员必须编写更多的代码,对该函数返回的错误进行处理,这无疑增加了开发windows sockes应用程序的难度。另外,应用程序中往往需要在一个循环内反复调用该函数,直到返回成功指示为止,这不是一种很好的做法。


---------------------------------------------------------------------------------------------------------

select I/O模型
使用select()函数检测多个套接字中准备就绪的套接字。其中,需要使用fd_set存放多个套接字,传入select函数。可以传入三组,分别检测是否可读、是否可写、是否connect失败或者有外带数据:
int select(int nfds, fd_set FAR * readfds, fd_ser FAR * writefds, fd_ser FAR * exceptfds, const struct timeval FAR * timeout);
nfds:忽略,只是为了兼容早起版本
readfds:具有可读性套接字集合的指针。符合下列条件的将被保留在fd_set内:
--有数据可以读入
--连接已经关闭、重置、中止
--已经调用listen()函数的套接字,有客户端正在连接
writefds:具有可写性套接字集合的指针。符合下列条件的将被保留在fd_ser内:
--可以发送数据
--一个非阻塞套接字执行connect成功
exceptfds:检查错误套接字集合的指针。满足下列条件的将被保留在fd_set内:
--一个非阻塞套接字执行connect操作失败
--有外带数据可供读取
timeout:用于设置调用select()函数时的等待时间。当此值为NULL时,select将等到有满足条件的套接字之后才返回。如果此值指向的结构体中的值为0,则此函数无论是否含有满足条件的套机子,都会立刻返回。

// select等待时间
struct timeval{
     long tv_sec;           // 秒
     long tv_usecl          // 微妙(不足1秒的部分)
}

与fd_set相关的几个宏有:
FD_ZERO:清空fd_set数组
FD_SET:将一个套接字设置到fd_set中
FD_ISSET:检测一个套接字是否包含在fd_set中

select函数对套接字的阻塞非阻塞模式没有要求和影响。两种模式的套接字都可以使用select I/O模型。

select模型的优势在于可以同时对多个建立起来的套接字进行有序的管理。可以防止应用程序在一次I/O调用的过程中,使阻塞模式套接字被迫进入阻塞状态;使非阻塞套接字产生WSAWOULDBLOCK错误。
是用select函数的windows sockets程序,其效率可能受损。在CPU的使用率不是关键因素时,这种效率可以接受。但是,当需要高效率时,肯定会产生问题。


WSAAsyncSelect
WSAAsyncSelect模型是windows sockets的一个异步模型。利用该模型应用程序可以再一个套接字上,接收以windows消息为基础的网络事件。
WSAAsyncSelect模型是异步的。通过调用WSAAsyncSelect()函数,通知系统对指定套接字感兴趣的网络时间,该函数立即返回,应用程序继续运行。
发生网络事件后,系统将向套接字绑定的窗口发送消息。
调用WSAAsyncSelect()后,套接字自动被设置为非阻塞模式。
int WSAAsyncSelect(SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
-- s:目的套接字
-- hWnd:接收网络事件的窗口
-- wMsg:发送给hWnd的消息(相当于自定义窗口消息,要比WM_USER大)
-- lEvent:感兴趣的网络事件(FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_OOB、FD_CLOSE、FD_QOS、FD_GROUP_QOS、FD_ROUTING_INTERFACE_CHANGE、FD_ADDRESS_LIST_CHANGE)
开发人员注册哪种网络事件,取决于实际的需要。如果应用程序同时对多个网络事件感兴趣,需要对网络事件类型执行按位或运算,然后将运算结果作为lEvent参数传给函数。
注意事项:
--多次调用WSAAsyncSelect函数,最后一次函数调用取消了前面注册的网络事件。
--如果要取消所有请求的网络事件通知,要以参数lEvent值为0调用WSAASycSelect函数。
--改变或者取消所有请求的网络事件请求之后,可能在消息队列中已经排队了网络消息,仍然会触发消息处理函数。
--通过accept返回的套接字与监听套接字拥有同样的属性,通常情况下,通过accept返回的套接字需要立刻再次调用WSAAsyncSelect,重新注册感兴趣的消息。
--每次收到FD_READ事件,不必recv所有的数据。recv后,如果还有未读数据,还会收到FD_READ事件。
--不要在一个FD_READ事件中调用多次recv函数。否则会导致收到多个FD_READ事件。
--如果在一个FD_READ事件中需要调用多次recv函数,则应该在recv之前先关闭FD_READ事件。
--通过FD_CLOSE事件判断套接字是否已经关闭。
--如果套接字从容关闭,且数据已经全部读出,则会只收到FD_CLOSE消息。
--调用closesocket函数后不会投递FD_CLOSE事件。
--收到FD_WRITE事件后可以在套接字上发送数据。如果发送数据返回WSAEWOULDBLOCK错误,则应该停止发送数据,直到再次收到FD_WRITE事件才能发送数据。


WSAEventSelect
WSAEventSelect是Windows Sockets提供的另外一个异步I/O模型。WSAEventSelect与WSAAsyncSelect很相似。不同的地方是两者网络事件发生时,通知应用程序的方式不同。WSAASyncSelect以窗口消息的形式通知,WSAEventSelect以事件的形式通知。
调用WSAEvnetSelect之后,套接字自动被设置为非阻塞模式。如果应用程序要将套接字设置为阻塞模式,必须设置lNetworkEvents参数为0,再次调用WSAEventSelect。如果已经调用WSAEventSelect注册了网络事件,再通过ioctlsocket函数改变套接字为阻塞模式,将会失败,并返回WSAEINAL错误。
相关函数:WSAEventSelect,WSACreateEvent,WSAResetEvent,WSAWaitForMultipleEvents,WSAEnumNetworkEvents。
--WSAEventSelect:对指定套接字注册要处理的网络事件
----网络事件:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_OOB,FD_CLOSE,FD_QOS,FD_GROUP_QOS,FD_ROUTING_INTERFACE_CHANGE,FD_ADDRESS_LIST_CHANGE。
--WSACreateEvent:创建要与套接字绑定,接收网络事件的事件对象
--WSAResetEvent:将事件对象置为无信号状态
--WSAWaitForMultipleEvents:监控一个或多个网络套接字的网络事件
--WSAEnumNetworkEvents:获取套接字发生的网络事件。该函数第二个参数为可选,可以指定与套接字绑定的事件对象(由WSACreateEvent创建)。如果这里不指定事件对象,用户需要调用WSAResetEvent来使事件对象置为无信号。如果这里指定了,则该函数将被重置。除非该函数出错,返回SOCKET_ERROR,则对象不会被重置,网络事件也不会被清除。


重叠I/O模型
相比select、WSAAsyncSelect、WSAEventSelect,重叠I/O模型是真正意义上的异步I/O模型。前面的几种I/O模型当发送数据或接收数据时,都是要把数据的I/O完成之后才返回(用户数据copy到套接字缓冲区或者数据从套接字缓冲区中copy到用户内存)。但重叠I/O模型,是在应用程序中调用输入或者输出函数后,立即返回。当I/O操作完成后,系统通知应用程序。系统通知应用程序的方式有两种:事件通知、完成例程。
相关的函数主要包括:WSASocket()、WSASend()、WSASendTo()、WSARecv()、WSARecvFrom()、WSAIoctl()、AcceptEx()
--创建
     要使用重叠I/O模型功能,必须使用WSA_FLAG_OVERLAPPED标志创建套接字。在WSASocket()中需要显示指定,但当使用socket()函数创建套接字时,会默认设置WSA_FLAG_OVERLAPPED标志。
--接收数据
     调用WSARecv()或者WSARecvFrom()函数接收数据。其中可以指定指向WSAOVERLAPPED结构体指针或者完成例程。
     一次调用可以指定一个或者多个缓冲区接收数据。如果接收操作立即完成,改函数返回0,lpNumberOfBytesRecvd参数指明接收数据的字节数。如果重叠操作未能立即完成,函数返回SOCKET_ERROR值,错误代码为WSA_IO_PENDING。在这种情况下lpNumberOfBytesRecvd值不被更新。应用程序可以利用完成例程或者事件通知来获知操作的最终结果。
     如果lpCompletionRoutine和lpOverLapped参数都为NULL,则该套接字作为非重叠套接字使用。
     如果lpCompleRoute参数不为NULL,则lpOverLapped参数的事件对象将被忽略。应用程序使用完成例程传递重叠操作结果。
     如果lpCompleRoute参数为NULL,lpOverLapped参数不为NULL,当接收数据完成时,lpOverLapped参数中的事件对象变为 有信号 状态。可以通过WSAWaitForMutipleEvents或者WSAGetOverlappedResult函数等待该事件。
--发送数据
     调用WSASend()或者WSASendTo()函数发送数据。
     一次调用可以指定一个或者多个发送缓冲区。
     如果发送立即完成,函数返回0,lpNumberOfBytesSend参数指明被发送数据的字节数。如果操作未能完成........(同上)

套接字的重叠属性,不会对套接字的当前工作模式产生影响。创建具有重叠属性的套接字被用于执行重叠I/O操作,这种操作不会改变套接字的阻塞模式。套接字的阻塞模式与重叠I/O操作不相关。
接收数据时,如果应用程序调用输入函数的时机上先于数据到达的时间,则当数据到达时,该数据被立即放入用户的接收数据缓冲区。这就减少了数据的复制过程。如果时机晚于数据到达的时间,则数据被立即复制到用户缓冲区。


完成端口
完成端口是Win32一种核心对象。使用它,能在一个程序内管理数百个甚至上千个套接字。它往往可以达到最好的系统性能。它是一种真正意义上的异步模型。解决了“one-thread-per-client”的问题。当应用程序需要管理成千上百个套接字,并且希望随着系统安装的CPU数量的增加,应用程序的性能得到提升时,I/O完成端口模型是最好的选择。
--创建完成端口对象
     使用CreateIoCompletionPort()函数创建完成端口对象。
     HANDLE hIoCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
     最后一个参数0表示并发线程数量为CPU的数量。
--创建服务线程
     CreateThread(NULL, 0, ThreadFun, CreateIoCompletionPort, 0, &threadId);
--套接字与完成端口关联
     还是通过调用CreateIoCompletionPort实现,不过,参数设置如下:
     FileHandle:套接字句柄
     ExitingCompletionPort:已经被创建好的完成端口句柄
     CompletionKey:完成键。通常应用程序自定义一个数据结构,保存与套接字相关的信息。将该结构地址作为完成键传进去
     NumerOfConcurrentThreads:设置为0
--发起重叠I/O操作
     通过下列函数发起重叠I/O操作:WSASend、WSASendTO、WSARecv、WSARecvFrom
--等待重叠I/O操作结果
     在服务线程中,调用GetQueuedCompletionStatus函数等待重叠I/O操作的完成结果。当重叠I/O操作完成时,I/O操作完成通知包被发送到完成端口上,此时该函数返回。完成通知包包含的信息有传输的字节数、完成键、和重叠结构。
--取消异步操作
     当关闭套接字应用程序时,如果此时系统中还有未完成的异步操作,那么应用程序可以调用Cancello函数取消等待执行的异步操作。
--投递完成通知包
     当服务线程退出,应用程序可以调用PostQueuedCompletionStatus函数向服务线程发送一个特殊的完成通知包。在服务线程中,收到这个通知包,线程退出。每个线程,都要调用一次该函数。当要多个线程退出时,需要调用该函数多次。

(完毕)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值