`
gdvzq90e
  • 浏览: 13471 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

《ASCE1885的网络编程》---Winsock APIの套接口I/O处理函数

阅读更多

《ASCE1885的网络编程》---Winsock APIの套接口I/O处理函数
2010年08月03日
  在Windows环境下,套接口的通信方式分为两种:阻塞方式和非阻塞方式。阻塞方式下工作的套接口在进行I/O操作时,函数要等待到相关操作完成以后才能返回;非阻塞方式下工作的套接口在进行I/O操作时,无论操作是否成功,调用都会立即返回。
  阻塞方式和非阻塞方式各有优缺点,阻塞方式的套接口编程简单,易于实现。因此,一个套接口默认操作模式被设置为阻塞方式。如果要使套接口工作在非阻塞方式下,就要使用ioctlsocket()函数进行设置。阻塞方式的套接口在下面几种情况下显得难于管理:
  1)当有多个已建立连接的套接口需要进行管理的;
  2)当发送的数据量不均匀或接收的数据量不均匀时;
  3)当发送或接收的数据时间不确定时。
  在进行程序设计时,应该尽量使用非阻塞方式的操作,但非阻塞方式的套接口较为复杂,并且由于操作常常失败,因此在程序中就要考虑操作失败时应该如何处理了。、
  1)设置套接口的工作方式---ioctlsocket()和WSAIoctl():
  int ioctlsocket(
  __in     SOCKET s, //套接字描述字
  __in     long cmd, //预定义好的标志,表示对套接口s的操作控制命令
  __inout  u_long *argp //指向cmd命令所待参数的指针
  );
  在Winsock2中引入一个新的功能更强大的函数是WSAIoctl():
  int WSAIoctl(
  __in   SOCKET s, //套接字描述字
  __in   DWORD dwIoControlCode, //指示将要进行的操作的控制代码
  __in   LPVOID lpvInBuffer, //指向函数的输入参数(用于描述函数输入参数缓冲区的地址)
  __in   DWORD cbInBuffer, //用于描述输入缓冲区的大小
  __out  LPVOID lpvOutBuffer, //指向函数的返回参数(用于描述函数返回数据缓冲区的地址)
  __in   DWORD cbOutBuffer, //用于描述返回参数缓冲区的大小
  __out  LPDWORD lpcbBytesReturned, //指向函数实际返回的字节数的地址
  __in   LPWSAOVERLAPPED lpOverlapped, //WSAOVERLAPPED结构的地址
  __in   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //一个指向操作结束后
  //调用的例程指针,该参数和lpOverlapped使用在重叠I/O中
  );
  Ioctlsocket()和WSAIoctl()函数支持的标准I/O命令有FIONBIO、FIONREAD和SIOCATMARK:
  1)FIONBIO:该命令用于在套接口s上允许或禁止非阻塞模式。一个套接口在创建好以后,默认的通信模式是阻塞模式,如果要在程序中对一个创建好的套接口通行模式进行设置,那么ioctlsocket()函数的第二个参数使用命令FIONBIO,第三个参数argp设为0时,表示禁止非阻塞模式;设为非0值,表示允许非阻塞模式。
  要注意:如果使用WSAAsyncSelect()函数或WSAEventSelect()函数,则套接口将会自动设置为非阻塞模式。如果已对一个套接口进行了WSAAsyncSelect()操作,则任何用ioctlsocket()调用来套接口重新设置成阻塞模式的操作都将失败,并返回WSAEINVAL错误。为了把套接口重新设置成阻塞模式,应用程序必须把IEvent参数置为0,调用WSAAsyncSelect()函数,以禁止WSAAsyncSelect()调用;或者令INetworkEvents参数等于0,调用WSAEventSelect()函数,从而禁止WSAEventSelect()。
  2)FIONREAD:该命令用于确定可从套接口s上自动读入的数据量。参数argp指向一个无符号长整型量,其中存有打算读入的字节数。如果使用WSAIoctl()函数,那么无符号整数是通过lpvOutBuffer返回的。如果套接口s面向数据流(SOCK_STREAM类型)的套接口,则FIONREAD返回一次recv()调用中所接收的数据总量,这通常与套接口中排队的数据总量相同;如果套接口s是面向数据报(SOCK_DGRAM类型)的套接口,则FIONREAD返回套接口上排队的第一个数据报的大小。
  3)SIOCATMARK:该命令用于确定是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINLINE)。如无带外数据等待读入,则该操作返回TRUE,否则返回FALSE,下一个recv()或revcfrom()操作将检索"标记"前的一些或所有数据。应用程序可用SIOCATMARK操作来确定是否有数据剩余。如果在"紧急"带外数据前有常规数据,则按序接收这些数据(注意:recv()和recvfrom()操作不会在一次调用中混淆常规数据和带外数据)。
  对于ioctlsocket()函数来说,argp指向一个BOOL值,在其中存入返回值;而对于WSAIoctl()函数来说,会在lpvOutBuffer中返回指向布尔变量的指针。
  Ioctlsocket函数使用实例如下:
  //-------------------------
  // Initialize Winsock
  WSAData wsaData;
  int iResult = WSAStartup(NAKEWORD(2,2), &wsaData);
  if(iResult != NO_ERROR)
  printf("Error at WSAStartup\n");
  //-------------------------
  // Create a SOCKET object.
  SOCKET m_socket;
  m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(m_socket == INVALID_SOCKET)
  {
  printf("Error at socket():%d\n", WSAGetLastError());
  WSACleanup();
  return;            
  } 
  //-------------------------
  // Set the socket I/O mode: In this case FIONBIO
  // enables or disables the blocking mode for the 
  // socket based on the numerical value of iMode.
  // If iMode = 0, blocking is enabled;  // If iMode != 0, non-blocking mode is enabled. u_long iMode = 0; ioctlsocket(m_socket, FIONBIO, &iMode); 2)套接口I/O状态查询---select()
  使用select()函数的好处是在进行I/O操作之前,可以首先判断能否向一个套接口写入数据,或者套接口上是否存在可读的数据。这样就可以防止应用程序在套接口处于阻塞模式时,对它进行的I/O操作被迫进入等待状态;同时也可以防止在套接口处于非阻塞模式时,产生WSAEWOULDBLOCK错误。
  select函数原型如下:
  int select(
  __in     int nfds, //本参数被忽略,仅起到与Berkeley API套接口兼容的作用
  __inout  fd_set *readfds, //检查可读性
  __inout  fd_set *writefds, //检查可写性
  __inout  fd_set *exceptfds, //检查例外数据
  __in     conststruct timeval *timeout //本次select()调用最长等待时间
  );
  fd_set是一个结构类型说明符,代表这一系列特定套接口的集合:
  typedefstruct fd_set {
  u_int  fd_count; //套接口的数目
  SOCKET fd_array[FD_SETSIZE]; //表示数组中存放的套接口号,FD_SETSIZE是常量,定义为64
  } fd_set;
  timeval是一个结构类型,定义如下:
  typedefstruct timeval {
  long tv_sec; //等待的秒数
  long tv_usec; //等待的毫秒数
  } timeval;
  tv_sec和tv_usec字段都表示等待时间,只是单位不同。它们的设置可分为如下三种情况:
  1)如果在调用select()函数时将等待时间tv_sec和tv_usec都设置为0,则select()调用在检查完套接口描述符后立即返回,这可用于探询所选套接口的状态。如果处于这种状态,则select()调用可认为是非阻塞的。
  2)如果在调用select()函数时将timeout指向NULL,则进行阻塞等待,即被监视的描述符中只有当其中的任何一个准备好读写操作时,select()调用才返回。
  3)如果等待时间tv_sec和tv_usec不全为0,则当等待时间没有超时时,select()函数在被检查的描述符中有任何一个套接口准备好读写时返回。
  select()函数可用于检查一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select返回满足条件的套接口数目。
  为了方便对readfds、writefds和exceptfds集合进行操作,Winsock实现中已经定义好了如下4个宏,以简化程序的设计:
  1)FD_SET(s, *set):向set集合添加套接口描述符s;
  2)FD_CLR(s, *set):从set集合中删除套接口描述符s;
  3)FD_ISSET(s, *set):检查s是否为set集合的一员,如果是则返回TRUE;
  4)FD_ZERO(*set):将set集合初始化为空集;
  这样使用select函数对一个或多个套接口进行检查的过程如下:
  1)使用FD_ZERO宏,初始化要检查的每一个集合;
  2)使用FS_SET宏,将要检查的套接口加入到一个集合中;
  3)设置等待时间,即对timeval中的tv_sec和tv_usec字段进行设置;
  4)调用select函数;
  5)当select函数正确返回时,使用FD_ISSET检查一个选定的套接口是否在指定的集合中。
  具体在程序中使用select函数时,还要注意以下几个问题:
  1)readfds参数中包括的套接口标识符,,。
  2)writefds参数中包括的套接口标识符有。如果一个套接口正在connect()连接(非阻塞),则可写性意味这连接顺利建立。
  3)exceptfds参数中包括的套接口标识符有。假如已完成对一个非阻塞连接调用的处理,连接尝试就会失败。注意:如果设置了SO_OOBINLINE选项为FALSE,则只能用这种方法来检查带外数据的存在与否。   对于SO_STREAM类型的套接口,远端造成的连接中止和KEEPALIVE错误都将被作为例外出错。   如果套接口正在进行连接connect()(非阻塞方式),则连接试图的失败将会表现在exceptfds中。
  4)如果不想对readfds、writefds和exceptfds进行监视,则可将其值为NULL,但三组参数不能同时全为NULL。
  select()函数使用实例,从网络上接收数据:
  #include
  #include
  #include
  #pragmacomment(lib, "ws2_32.lib")
  #define PORT 5150
  #define MSGSIZE 1024
  int g_iTotalConn = 0;
  SOCKET g_socketArr[FD_SETSIZE];
  DWORD WINAPI WorkerThread(LPVOID lpParam); int main() {     WSADATA wsaData;     SOCKET listenSock, clientSock;     sockaddr_in localAddr, clientAddr;     int iaddrsize = sizeof(sockaddr_in);     DWORD dwThreadId;     //Initialize Windows socket library     WSAStartup(MAKEWORD(2,2), &wsaData);     //Create listening socket     listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);     localAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);     localAddr.sin_family = AF_INET;     localAddr.sin_port = htons(PORT);     bind(listenSock, (struct sockaddr*)&localAddr, sizeof(sockaddr_in));     //Create Worker Thread     CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);     while(TRUE)     {         clientSock = accept(listenSock, (struct sockaddr*)&clientAddr, &iaddrsize);         printf("Accepted client:%s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));         //Add socket to g_socketArr         g_socketArr[g_iTotalConn++] = clientSock;     }     system("pause");     return 0;     } //Thread function DWORD WINAPI WorkerThread(LPVOID lpParam) {          int i;     fd_set fdread;     int ret;     struct timeval tv = {1, 0};     char szMessage[MSGSIZE];     while(TRUE)     {         FD_ZERO(&fdread);         for(i=0; isocket closed                     printf("Client socket %d closed\n", g_socketArr[i]);                     closesocket(g_socketArr[i]);                            if(i 发送一个消息(事件通知):
  int WSAAsyncSelect(
  __in  SOCKET s, //标识一个需要事件通知的套接口描述符
  __in  HWND hWnd, //标识一个在网络事件发生时要想收到消息的窗口或对话框的句柄
  __in  unsignedint wMsg, //在网络事件发生时要接收的消息,该消息会投递到由hWnd句柄指定
  //的窗口或对话框
  __in  long lEvent //位屏蔽码,用于指明应用程序感兴趣的网络事件集合
  );
  WSAAsyncSelect函数常用到的网络事件:
  
  若应用程序感兴趣的网络事件声明成功,则返回0;如果声明失败,则返回SOCKET_ERROR错误信息。可进一步调用WASGetLastError()函数返回如下的特定错误代码:
  WSANOTINITIALISED  //在使用本API之前必须进行一次成功的WSAStartup()调用
  WSAENETDOWN//Windows Sockets实现已检测到网络子系统故障
  WSAENIVAL   //指定的参数之一是非法的
  WSAEINPROGRESS       //一个阻塞的Windows Sockets操作正在进行
  附加的错误代码可能在应用程序窗口接收到消息时被设置,这些代码可以用WSAGETSELECTERROR宏从lParam中取出,对应于每个网络事件的可能错误代码说明如下:
  1)网络事件FD_CONNECT可能的错误代码:
  WSAEADDRINUSE         //给定的地址已被使用
  WSAEADDRNOTAVAIL        //指定的地址在本地机器不能使用
  WSAEAFNOSUPPORT//指定族的地址不能和本套接口同时使用
  WSAECONNREFUSED//连接的尝试被拒绝
  WSAEDESTADDRREQ  //需要一个目的地址
  WSAEFAULT  //namelen参数不正确
  WSAEINVAL   //套接口已经约束到一个地址
  WSAEISCONN        //套接口已经连接
  WSAEMFILE   //没有可用的文件描述符
  WSAENETUNREACH    //此时网络不能从该主机访问
  WSAENOBUFS       //无可用的缓冲区空间,套接口不能连接
  WSAENOTCONN//套接口没有连接
  WSAENOTSOCK   //该描述符是文件,不是套接口
  WSAETIMEOUT    //试图连接超时,未建立连接
  2)网络事件FD_CLOSE可能的错误代码是:
  WSAENETDOWN//Windows Sockets实现已检测到网络子系统故障
  WSAECONNRESET        //连接由远端重建
  WSAECONNABORTED         //由于超时或其他失败放弃连接
  3)网络事件FD_READ、FD_WRITE、FD_OOB和FD_ACCEPT可能的错误代码为:
  WSAENETDOWN//Windows Sockets实现已检测到网络子系统故障
  WSAAsyncSelect函数用来请求Windows Sockets DLL为窗口句柄发送一条由IEvent参数指明的网络事件。要发送的消息由wMsg参数表明,在使用时要注意以下问题:
  1)若应用程序对一个套接口s调用了WSAAsyncSelect()函数,那么套接口s的模式会自动从阻塞模式变成非阻塞模式。这样一来,假如在程序中调用了像WSARecv()这样的I/O函数,当没有数据可用时,必然会造成调用失败,并返回WSAEWOULDBLOCK错误信息;
  2)如果应用程序同时对多个网络事件感兴趣,那么只需对各种类型的网络事件执行按位或的运算即可。例如,当一个网络应用程序对套接口s上的连接、发送以及套接口关闭这三个网络事件感星期时,可以用如下的格式调用WSAAsyncSelect函数:
  rc = WSAAsyncSelect(s, hWnd, wMsg, FD_CONNECT | FD_WRITE | FD_CLOSE);
  3)特别要注意的是,进行一次WSAAsyncSelect()调用,将使为同一个套接口启动的所有以前的WSAAsyncSelect()调用作废。例如,要接收读写通知,应用程序必须同时用FD_READ和FD_WRITE调用WSAAsyncSelect():
  rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);
  而不能使用如下的调用方式,因为第二次调用将使第一次调用的作用失效:
  rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ);
  rc = WSAAsyncSelect(s, hWnd, wMsg, FD_WRITE);
  4)如果要取消所有的通知,也就是指出Windows Sockets的实现不再在套接口上发送任何和网络事件相关的消息,则把IEvent字段置为0,然后调用WSAAsyncSelect()即可:
  rc = WSAAsyncSelect(s, hWnd, 0 , 0);
  尽管在本例中,WSAAsyncSelect()调用立即使传给该套接口的事件消息无效,但仍可能有消息等待在应用程序的消息队列中,应用程序因此也必须仍准备好接收网络消息(即使消息作废)。用closesocket()关闭一个套接口也同样使WSAAsyncSelect()发送的消息作废,但在调用closesocket()之前,队列中的消息仍然起作用。
  5)当某一套接口s上发生了一个已命名的网络事件时,应用程序窗口hWnd会接收到消息wMsg。应用程序窗口例程的wParam参数标识了网络事件发生的套接口,lParam参数的低位字指明了发生的网络事件,高位字则含有一个错误代码。错误代码和事件可以通过WSAGETSELECTERROR和WSAGETSELECTEVENT宏从lParam中取出,宏定义如下:
  #include
  #define WSAGETSELECTEVENT(lParam)       LOWORD(lParam)
  #define WSAGETSELECTERROR(lParam)       HIWORD(lParam)
  若应用程序发现套接口上没有产生任何错误,接着便会检查lParam的低位字,以弄清到底是哪个网络事件类型造成了这条Windows消息的触发。 4)取消正在执行的阻塞调用---WSACancelBlockingCall()
  如果应用程序中想取消正在执行的阻塞调用,就要使用WSACancelBlockingCall()函数。要注意的是,在Winsock2的实现规范中已经不包括该函数了:
  int WSACancelBlockingCall(void);
  5)判断是否有阻塞调用---WSAIsBlocking()
  该函数用于判断是否有阻塞调用正在进行:
  BOOL WSAIsBlocking(void);
  如果存在一个尚未完成的阻塞函数在等待完成,则函数返回TRUE,否则返回FALSE。
  在Winsock2的实现规范中已经不包括该函数了,在Winsock1.1中使用该函数时要注意它禁止对每一个线程多于一个未完成的调用。
  6)取消一个未完成的异步操作---WSACancelAsyncRequest()
  int WSACancelAsyncRequest(
  __in  HANDLE hAsyncTaskHandle //指明将要被取消的异步操作
  );
  如果该操作成功地取消了异步操作,则函数返回0,否则返回SOCKET_ERROR错误信息。可通过WSAGetLastError()调用获得对错误的进一步描述,错误代码如下:
  WSANOTINITIALISED  //在使用本API前必须进行一次成功的WSAStartup()调用
  WSAENETDOWN//Windows Sockets实现已检测到网络子系统故障
  WSAEINVAL   //指示异步操作句柄非法
  WSAEINPROGRESS       //一个阻塞的Windows Sockets操作正在进行
  WSAEALREADY      //被取消的异步操作已经完成
  WSACancelAsyncRequest()函数用于取消一次异步操作,该异步操作应该是以一个WSAAsyncGetXByY()函数的形式(例如WSAAsyncGetHostByName())启动的。hAsyncTaskHandle参数标识了要取消的操作,它应由初始函数作为异步任务句柄返回。
  试图取消一个已存在的异步操作WSAAsyncGetXByY()可能失败(错误代码是WSAEALREADY),这有两种原因:一是原来的操作已经完成,并且应用程序已经处理了结果消息;二是原来的错误已经完成,但结果消息仍在应用程序窗口队列中等待。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics