现在的位置: 首页 > 综合 > 正文

socket同步与异步

2014年02月09日 ⁄ 综合 ⁄ 共 3467字 ⁄ 字号 评论关闭

转自:网易wxy0619的博客  http://wxy0619.blog.163.com/blog/static/4471987320078611412838/

 

socket同步与异步  

 

前面说了socket建立与传输的函数,但是光这些函数,还不能达到网络通信程序的全部要求。因为accept, send和recv函数默认是同步的,也就是阻塞的。send还好说,发不出去就等着,有情可原。但是,accept和recv阻塞,通常会有麻烦,一个服务器程序,在试图接受连接(调用accept)之后, 由于没有客户端试图连接,所以服务器将进入阻塞状态,不能接受其他任何消息,包括用户输入,和窗口消息(如果有窗口的话),这是不可取的。操作人员也许会认为程序已经死了,而强行结束程序。recv也一样会遇到这个问题。

   这就需要异步,即在socket没有消息的时候,程序要能够处理其他事情,让程序保持活着状态。方法有很多,从基础的开始说:

   一,可以通过 ioctlsocket 函数将socket设定为非阻塞状态,这样当socket执行recv操作时,如果没有数据到达,函数会立即返回,而不会进入阻塞状态。这种方法的缺点是,你需要每隔一短时间去查询一下socket的状态以便接收数据,浪费了资源。所以我没有用过这种方式,函数原形为
int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp);
阻塞状态改变时第二个参数为FIONBIO,argp指向一个无符号长整型。如允许非阻塞模式则非零,如禁止非阻塞模式则为零。

    二,select方法,从多个描述符(句柄)中选取有消息到达的,进行处理。这种方法的一般步骤是,先声明一个描述符的集合,将需要处理的描述符加入到集合中 ——〉调用select方法选取有消息的描述符 ——〉判断哪些描述符有消息到达,处理消息 ——〉重新设置集合 ——〉循环。
fd_set rfds; //描述符集合
FD_ZERO(&rfds); //清空集合
FD_SET(socket, &rfds); //将socket加入到集合中, 0是标准输入句柄,加入后可以在等待socket时兼顾用户输入
FD_ISSET(0, &rfds)  //判断句柄是否有消息到达
    这种方法的缺点....select本身是一个阻塞的方法,虽然它可以设定最长等待时间。函数原形为
  int select (int FD_SETSIZE, fd_set in, fd_set out, fd_set except, const struct timeval FAR * timeout);

    三,多线程。既然怕阻塞影响了其他消息响应,那就开另外一个线程用来处理socket,阻塞也只是阻塞一个线程,不影响主线程和其他线程的响应消息。推荐,不过缺点是需要线程的资源开销,而且一般系统线程的数目是有限制的,因此通常要结合select函数使用。创建线程的方法这里就不写了,有机会再写。

    以上三种方法在linux系统,windows系统通用。

    四,windows提供的基于事件的select异步模型WSAEventSelect,它将socket与event绑定,当有消息到达socket时,触发event。然后用WSAWaitForMultipleEvents, WSAEnumNetworkEvents筛选并处理消息。
    说函数用法之前先说下windows下使用socket需要初始化
int PASCAL FAR WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData );
应用举例
                  WORD wVersionRequested;
                  WSADATA wsaData;
                  int err;
                  wVersionRequested = MAKEWORD( 1, 1 );
                  err = WSAStartup( wVersionRequested, &wsaData );

退之前需要调用WSACleanup()释放资源。

    socket函数包含在头文件WinSock2.h中,并且编译时链接Ws2_32.lib库文件。

    好了,说正题
     int WSAAPI WSAEventSelect ( SOCKET s, WSAEVENT hEventObject, long lNetworkEvents );
hEventObject:一个句柄,用于标识与所提供的FD_XXX网络事件集合相关的一个事件对象。
lNetworkEvents:一个屏蔽位,用于指定感兴趣的FD_XXX网络事件组合, 比如FD_READ | FD_CLOSE。
返回值:如果应用程序指定的网络事件及其相应的事件对象成功设置,则返回0。否则的话,将返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()来获取相应的错误代码。

   DWORD WSAWaitForMultipleEvents( DWORD cEvents, const WSAEVENT* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable );
cEvents:等候事件的总数量;
lphEvents:事件数组的指针;
fWaitAll:如果设置为 TRUE,则事件数组中所有事件被传信的时候函数才会返回, FALSE则任何一个事件被传信函数都要返回;
dwTimeout : 超时时间,如果超时,函数会返回 WSA_WAIT_TIMEOUT。如果设置为0,函数会立即返回。 如果设置为 WSA_INFINITE只有在某一个事件被传信后才会返回。
fAlertable  :一般为FALSE。
返回值:WSA_WAIT_TIMEOUT :最常见的返回值,我们需要做的就是继续Wait; WSA_WAIT_FAILED :出现了错误,请检查cEvents和lphEvents两个参数是否有效。如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置。

  int WSAAPI WSAEnumNetworkEvents ( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents,  LPINT lpiCount);
hEventObject:用于标识需要复位的相应事件对象。
lpNetworkEvents:一个WSANETWORKEVENTS结构的数组,每一个元素记录了一个网络事件和相应的错误代码。
lpiCount:数组中的元素数目。可省。
返回值:如果操作成功则返回0。否则的话,将返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()来获取相应的错误代码。
调用该函数后,用NetworkEvents.lNetworkEvents & FD_XXX判断消息类型,进行处理。
别忘了用BOOL WSACloseEvent(WSAEVENT hEvent);关闭事件。

    五,windows下窗口消息WSAAsyncSelect 模型。原理同前一个方法差不多,只不过,消息会自动加入到window窗口消息循环中,因此需要一个窗口句柄。
int PASCAL FAR WSAAsyncSelect ( SOCKET s, HWND hWnd, unsigned int wMsg,  long lEvent );
hWnd:标识一个在网络事件发生时需要接收消息的窗口句柄。
wMsg:在网络事件发生时要接收的消息。
lEvent:位屏蔽码,用于指明应用程序感兴趣的网络事件集合。

窗口要处理消息,需要在窗口的消息映射(MESSAGE_MAP)中加入
ON_MESSAGE(NETWORK_EVENT, OnNetEvent)

   好了,函数介绍完了,虽然说得不是很清楚。当然,这只是直接通过socket连接函数进行网络通信的方法。如果用IDE提供的通信类操作,会方便得多。比如java的Socket,ServerSocket类,VC++的Socket类,QT的QSocket类,不但连接通信的函数简单明了,而且异步操作也相对简单得多,一般只要重载一些回调函数就可以了。

抱歉!评论已关闭.