利用windows api进行最原始的socket编程:
服务端:
客户端:
使用非阻塞模式编程:
int PASCAL FAR WSAAsyncSelect ( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent )
s 标识一个需要事件通知的套接口的描述符.
hWnd 标识一个在网络事件发生时需要接收消息的窗口句柄.
wMsg 在网络事件发生时要接收的消息.
lEvent 位屏蔽码,用于指明应用程序感兴趣的网络事件集合.
注释:
本函数用来请求Windows Sockets DLL为窗口句柄发一条消息-无论它何时检测到由lEvent参数指明的网络事件.要发送的消息由wMsg参数标明.被通知的套接口由s标识.
本函数自动将套接口设置为非阻塞模式.
lEvent参数由下表中列出的值组成.
值 意义
FD_READ 欲接收读准备好的通知.
FD_WRITE 欲接收写准备好的通知.
FD_OOB 欲接收带边数据到达的通知.
FD_ACCEPT 欲接收将要连接的通知.
FD_CONNECT 欲接收已连接好的通知.
FD_CLOSE 欲接收套接口关闭的通知.
启动一个WSAAsyncSelect()将使为同一个套接口启动的所有先前的WSAAsyncSelect()作废. 例如,要接收读写通知,应用程序必须同时用FD_READ和FD_WRITE调用WSAAsyncSelect(),如下:
rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ|FD_WRITE);
对不同的事件区分不同的消息是不可能的.下面的代码将不会工作;第二个调用将会使第一次调用的作用失效,只有FD_WRITE会通过wMsg2消息通知到.
rc = WSAAsyncSelect(s, hWnd, wMsg1, FD_READ);
rc = WSAAsyncSelect(s, hWnd, wMsg2, FD_WRITE);
如果要取消所有的通知,也就是指出Windows Sockets的实现不再在套接口上发送任何和网络事件相关的消息,则lEvent应置为0.
rc = WSAAsyncSelect(s, hWnd, 0, 0);
尽管在本例中,WSAAsyncSelect()立即使传给该套接口的事件消息无效, 仍有可能有消息等在应用程序的消息队列中.应用程序因此也必须仍准备好接收网络消息-即使消息作废.用closesocket() 关闭一个套接口也同样使WSAAsyncSelect()发送的消息作废,但在closesocke()之前队列中的消息仍然起作用.
由于一个已调用accept() 的 套接口和用来接收它的侦听套接口有同样的属性, 任何为侦听套接口设置的的WSAAsyncSelect()事件也同样对已接收的套接口起作用.例如, 如果一个侦听的套接口有WSAAsyncSelect()事件FD_ACCEPT,FD_READ,FD_WRITE, 则任何在那个侦听的套接口上接收的套接口将也有FD_ACCEPT,FD_READ,FD_WRITE事件,以及同样的wMsg的值.若需要不同的 wMsg及事件,应用程序应调用WSAAsyncSelect(),将已接收的套接口和想要发送的新消息作为参数传递.
当某一套接口s上发生了一个已命名的网络事件,应用程序窗口hWnd会接收到消息wMsg.wParam参数标识了网络事件发生的套接口.lParam的 低字指明了发生的网络事件.lParam的高字则含有一个错误代码.该错误代码可以是winsock.h中定义的任何错误.
错误代码和事件可以通过WSAGETSELECTERRORH和WSAGETSELECTEVENT宏从lParam中取出.定义如下:
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
注意:在accept()调用和为改变事件或wMsg的WSAAsyncSelect()调用 中有一个计时窗口.应用程序如果需要给侦听的和调用过accept()的套接口以不同的wMsg,它就应该在侦听的套接口上请求FD_ACCEPT事件, 然后在accept()调用后设置相应的事件.由于FD_ACCEPT从不发送给已连接的套接口,而FD_READ,FD_WRITE,FD_OOB及 FD_CLOSE也从不发送给侦听套接口,所以不会产生困难.
使用以上的宏将最大限度的提高应用程序的可移植性.
返回的可能网络事件如下:
值 意义
FD_READ 套接口s准备读
FD_WRITE 套接口s准备写
FD_OOB 带外数据 准备好在套接口s上读.
FD_ACCEPT 套接口s准备接收新的将要到来的连接.
FD_CONNECT 套接口s上的连接完成.
FD_CLOSE 由套接口s标识的连接已关闭.
返回值:
0 若应用程序感兴趣的网络事件的声明成功.
SOCKET_ERROR 否则.可通过调用WSAGetLastError()返回特定的错误代码.
评价:
尽管WSAAsyncSelect()可以以多个事件的组合来调用,应用程序窗口还是会为每个网络事件接收一条消息.
如同select() 函数,WSAAsyncSelect()会被频繁地调用来决定,何时一次数据转移操作(send()或recv() )可以启动,并且可以立刻成功.尽管如此,健壮的应用程序必须做好这样的准备, 即它可能接收到消息及启动了一个会立即返回WSAEWOULDBLOCK的Windows Sockets API调用.例如,下列的事件序列是可能的:
(i) 数据到达套接口s;Windows Sockets传递WSAAsyncSelect消息.
(ii) 应用程序处理其它一些消息.
(iii) 在处理过程中,应用程序启动了ioctlsocket(s,FIONREAD...)并且注意到有数据准备好读.
(iv) 应用程序启动recv(s,...)来读数据.
(v) 应用程序循环处理下一条消息,最终到达WSAAsyncSelect消息,表示数据已准备好读.
(vi) 应用程序启动recv(s,...),但失败并有错误WSAEWOULDBLOCK.
其它的事件序列也是可能的.
Windows Sockets DLL不会不断地为某一特定的网络事件向一个应用程序发送消息. 如果已成功地向应用程序窗口发送了一特定事件的通知,对该应用程序窗口将不再为该网络事件发消息,直到应用程序调用函数隐含地重新通知该网络事件.
事件 重新通知函数
FD_READ recv()或recvfrom()
FD_WRITE send()或sendto()
FD_OOB recv()
FD_ACCEPT accept()
FD_CONNECT 无
FD_CLOSE 无
任何对重新通知函数的调用,即使失败,也会达到为相关事件发重新通知消息的效果.
对FD_READ,FD_OOB和FD_ACCEPT事件,消息传递是"水平触发"(level-triggered)的.这意味着,若调用了重新通知函 数并且相关的事件对该调用仍有效,WSAAsyncSelect()消息就将传给应用程序.这为应用程序提供了事件驱动以及不必考虑在任一时刻到达的数据 量的能力.考虑下列序列:
(i) Windows Sockets DLL在套接口s上接收100字节的数据并传递一个FD_READ消息.
(ii) 应用程序启动recv(s,buffptr,50,0)接收50字节.
(iii) 由于仍有数据未读,Windows Sockets DLL发送另一个FD_READ消息.
根据以上语义,应用程序不必在收到FD_READ消息时读进所有可读的数据-对应于每一FD_READ消息进行一次recv()调用是恰当的.如果应用程 序为一个FD_READ消息而启动了多个recv()调用,它将接收到多个FD_READ消息.这样的应用程序可能希望在开始recv()调用( 通过不为FD_READ事件置位的WSAAsyncSelect()函数调用)之前关闭FD_READ消息.
如果在应用程序初次调用WSAAsyncSelect()或当调用了重新通知函数时,有一个事件为真, 则会发送一个相应的消息.例如,若应用程序调用listen(),就会试图进行连接,然后应用程序调用WSAAsyncSelect()声明它需要为套接 口接收FD_ACCEPT消息,Windows Sockets的实现就会立即传递一个FD_ACCEPT消息.
FD_WRITE事件处理起来稍有不同.FD_WRITE消息是在套接口第一次用connect() 连 接或由accept()接受,并且在send()或sendto()以WSAWOULDBLOCK错误失败后缓冲区空闲时发送的.因此,应用程序可以假设 发送可能在第一次FD_WRITE消息时开始,并持续到一次返回WSAEWOULDBLOCK的发送. 在这样的失败后,应用程序将被通知,FD_WRITE消息的发送又将可能.
FD_OOB事件只用在当套接口配置成独立接收带外数据时.如果一个套接口被配置成接收感兴趣的带外数据状态,带外数据将和普通数据等同视之,并且应用程 序应该注册它感兴趣的方面,然后将接收FD_READ事件,而不是FD_OOB事件.应用程序可以设置或监控带外数据处理的方法(通过使用setsockopt() 或getsockopt() 函数,及SO_OOBINLINE选项).
在FD_CLOSE消息中的错误代码指出套接口的关闭是正常的还是异常的.如果错误代码是0,则关闭是正常的;若错误代码是WSAECONNRESET,则套接口的虚套接口将被重置.这些只对SOCK_STREAM类型的套接口起作用.
FD_CLOSE消息在相应套接口的虚电路关闭指令接收到时发送.在TCP术语中,这意味着FD_CLOSE在连接进入了FIN WAIT或CLOSE WAIT状态时发送.这是远端对发送方进行了shutdown()调用或closesocket()调用的结果.
请注意你的应用程序将只会收到FD_CLOSE消息来指出虚电路的关闭.它不会收到FD_READ消息来表示该状况.
错误代码:
WSANOTINITIALISED 在使用本API前必须进行一次成功的WSAStartup()调用.
WSAENETDOWN WINDOWS SOCKETS实现已检测到网络子系统故障.
WSAEINVAL 指出指定的参数之一是非法的.
WSAEINPROGRESS 一个阻塞的Windows Sockets操作正在进行.
附加的错误代码可能在应用程序窗口接收到消息时被置.这些代码可以用WSAGETSELECTERROR宏从lParam中取出.对应于每个网络事件的可能错误代码为:
事件:FD_CONNECT
WSAEADDRINUSE 给定的地址已被使用.
WSAEADDRNOTAVAIL 指定的地址在本地机器不能使用.
WSAEAFNOSUPPORT 指定族的地址不能和本套接口同时使用.
WSAECONNREFUSED 连接的尝试被拒绝.
WSAEDESTADDRREQ 需要一个目的地址.
WSAEFAULT namelen参数不正确.
WSAEINVAL 套接口已经约束到一个地址.
WSAEISCONN 套接口已经连接.
WSAEMFILE 没有可用的文件描述符.
WSAENETUNREACH 此时网络不能从该主机访问.
WSAENOBUFS 无可用的缓冲区空间.套接口不能连接.
WSAENOTCONN 套接口没有连接.
WSAENOTSOCK 该描述符是文件,不是套接口.
WSAETIMEDOUT 试图连接超时,未建立连接.
事件:FD_CLOSE
WSAENETDOWN WINDOWS SOCKETS实现已检测到网络子系统故障.
WSAECONNRESET 连接由远端重建.
WSAECONNABORTED 由于超时或其它失败放弃连接.
事件:FD_READ
事件:FD_WRITE
事件:FD_OOB
事件:FD_ACCEPT
WSAENETDOWN WINDOWS SOCKETS实现已检测到网络子系统故障.
关于Windows Sockets提供者的说明:
Windows Sockets的提供者应确保消息可以成功地传给应用程序.如果PostMessag()操作失败,Windows Sockets的实现必须重发该消息-只要窗口存在.
Windows Sockets提供者应使用WSAMAKESELECTREPLY宏来构造消息中的lParam参数.
当套接口关闭时,Windows Sockets提供者应清除所有保留下来要发送给应用程序窗口的消息.然而应用程序必须准备好接收,放弃任何在closesocket()之前可能已经发送的消息.
总之:
在WSAAsyncSelect 中注册的消息中,FD_READ只要接收缓冲区有数据过来,系统就会发该消息(如果一次没recv完,则继续发),FD_ACCEPT只要有客户端连接,系统就会发,FD_CLOSE只要连接关闭,系统就会发,FD_WRITE自己根据需要自己发(如果一次send不完,则系统会再发)。