现在,终于有点理解循序渐进的意思了。已经说过,我在开始学习VC的时候就对SOCKET编程进行了学习。当时也就是把函数给认识了那么几个,那么多参数,那么多结构的。也记不清楚。合上书后,就知道有这么几个模型,让我写出具体的编程步骤都写不来。但是,现在来看,就不是那么难了。就其原因,我没有按规矩办事吧。现在想来很简单。只要我们对WINDOWS的消息机制熟练的掌握了。对于昨天所说的WSAAsyncSelect模型的学习也就不是什么难事了。而对于今天学习的呢,只要你熟练地掌握了事件机制,对于学习WSAEventSelect模型也简直是简单不过的了。为什么呢,请看下面。
1、创建套接字,绑定套接字这些就不说了,不会的去复习。不要懵懵懂懂地看,那样害处很大的,让你最后都不知道你自己是不是真的学会了。感觉是会了,让你写一个代码出来时,你却感到无从下手。还是去复习复习吧。
2、创建事件对象:
WSAEVENT hWSAevent = WSACreateEvent();//创建事件
WSAEventSelect(sListenSock,hWSAevent,FD_ACCEPT);//关联网络事件到事件句柄
其实调用该函数就是要告诉系统,如果有FD_ACCEPT事件发生的时候,就要让hWSAevent对象有信号。因为我们知道,一个事件对象有两种状态:有信号和无信号,而创建的时候默认的是无信号的,也是在无信号的情况下,我们的等待函数才会被阻塞(也就是为什么会采用该机制的原因了)。而当发生事件FD_ACCEPT(或其它如FD_READ、FD_WRITE、FD_CLOSE等)时让事件对象有信号,从而让等待函数被唤醒(当然除了等待事件有信号让等待函数返回外还有可能是设置超时时间已到),继续执行后面的操作。也就可以在后面判断具体的是什么事件。该怎样处理它了。
一般用的等待函数是:
DWORD WSAWaitForMultipleEvents(
DWORD cEvent,//有几个事件
const WSAEVENT FAR * lphEvents,//该事件对象句柄数组地址
BOOL fWaitAll,//是所有事件都有信号才返回,还是只要有一个有信号即可返回
DWORD dwTimeout,//如果都没有信号的情况下,要等待多少时间才返回
BOOL fAlertable//主要用于重叠模型中,在这设为FALSE即可
);返回值是有信号事件句柄的索引。该索引是系统指出的,不是我们定义的事件对象句柄数组中事件对象句柄所在的索引,但是该索引减去WSA_WAIT_EVENT_0后得到的数值就是事件句柄在其所在数组中的索引了,从而就找出了是那个套接字上产生了事件。在后面对事件进行枚举时有用。
3、判断事件类型
WSANETWORKEVENTS记录了网络事件类型和可能的出错代码,该结构如下:
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;//网络事件类型
int iErrorCode[FD_MAX_EVENTS];//可能的出错代码
}WSANETWORKEVENTS,FAR * LPWSANETWORKEVENTS;
要判断事件类型就是用上面的结构作为参数,传递给函数
int WSAEnumNetworkEvents(
SOCKET s,//要检测的套接字,可以从上面等待函数的返回值计算出是那个套接字或是那个事件句柄
WSAEVENT hEventObject,//要检测的事件对象句柄
LPWSANETWORKEVENTS lpNetworkEvents//填充该结构
);
lpNetworkEvents记录了该套接字发生了什么样的事件,才让该事件有信号返回了。如果要判断是不是FD_ACCEPT事件,就如下判断:
if(FD_ACCEPT & lpNetworkEvents.lNetworkEvents)
{
if (0 != lpNetworkEvents.iErrorCode[FD_ACCEPT_BIT]) //判断是否出错,判断各种事件出错方式看MSDN
//如判断FD_READ时使用lpNetworkEvents.iErrorCode[FD_READ_BIT]来判断,FD_WRITE同理了
{
//出错处理
continue;
}
else
{
SOCKET tSockIn = accept(...);//接受新连接的到来
ev.event = WSACreateEvent();//创建新事件对象,准备给新连接添加事件
WSAEventSelect(tSockIn, ev.event, FD_READ | FD_CLOSE);//在此处给新来的连接关联上事件
//在给新连接添加上事件后,将它们加入到事件数组中,就可以用同样的一段代码来对他们进行处理了。
}
}
在这里有一件是不知道在WSAAsyncSelect的时候说没有,忘了。在这在说一遍了。那就是,为什么开始的WSAEventSelect的第三个参数是FD_ACCEPT,而后面的WSAEventSelect的第三个参数是FD_READ | FD_WRITE,有的再加一个FD_CLOSE什么的。原因很简单。
因为开始的WSAEventSelect处的那个是主套接字嘛。他负责监听说有的想连接到服务器的客户端。所以使用FD_ACCEPT了。而后面的WSAEventSelect时,是客户端已经连接了,现在要处理的是一些传输数据之类的事了,所以也就用FD_READ|FD_WRITE了,当然以可以用FD_ACCEPT啊,但是在这上面是不会有该事件的,添加也不白添加嘛。FD_CLOSE可以添加进去,目的是在检测到客户端退出时好进行处理。
整个过程就是这样了。思路是完了,但是主要的还是能够写出代码来。
下面是转载过来的一个完整的代码,帮助理解:
原文地址:http://blog.csdn.net/jimmy_w/archive/2009/04/10/4062973.aspx
怎么感觉标题很学术的样子。。。上次那个重叠IO的事件模型搞完之后,就觉得TCP是很不错的协议,可以将重点转移到对客户端的管理之上,而不必为了数据报的丢失和客户端keepalive的问题而绞尽脑汁(之前我做了个简单的聊天软件,面对的就是这种问题)。
WSAEventSelect是基于事件通知的,我觉得没有比这个模型更加简单实用了,编译环境:vc++ 6.0,代码如下:
view plaincopy to clipboardprint?
#include <tchar.h>
#include <iostream>
#include <algorithm>
#include <winsock2.h>
#include <crtdbg.h>
#include <cstring>
#include <iomanip>
#include <list>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define LOC_PORT 5678
#define LOC_ADDR "127.0.0.1"
class CSocketObject
{
public:
CSocketObject(){WSAStartup(MAKEWORD(2,2), &m_wsaData);}
virtual ~CSocketObject(){WSACleanup();}
public:
virtual void Run() = 0;
protected:
WSADATA m_wsaData;
};
class CTcpServer : public CSocketObject
{
public:
virtual void Run();
private:
typedef struct
{
WSAEVENT event;
SOCKET sock;
}EVENT_T;
list<EVENT_T> m_listClient;
static WSAEVENT& DoGetEvent(EVENT_T& ev){return ev.event;}
};
void CTcpServer::Run()
{
SOCKET tSock = socket(AF_INET, SOCK_STREAM, 0);
char cBuf[1024];
SOCKADDR_IN tAddr, tComeinAddr;
tAddr.sin_family = AF_INET;
tAddr.sin_port = htons(LOC_PORT);
tAddr.sin_addr.s_addr = inet_addr(LOC_ADDR);
_ASSERT(bind(tSock,(SOCKADDR*)&tAddr, sizeof(tAddr)) == 0);
WSAEVENT hWSAEvent = WSACreateEvent();
WSAEventSelect(tSock, hWSAEvent, FD_ACCEPT);
_ASSERT(listen(tSock, 5) == 0);
EVENT_T ev = {hWSAEvent, tSock};
m_listClient.push_back(ev);
WSAEVENT events[64];
for (;;)
{
transform(m_listClient.begin(), m_listClient.end(), events, DoGetEvent);
int dwIndex = WSAWaitForMultipleEvents(m_listClient.size(), events, false, WSA_INFINITE, false);
list<EVENT_T>::iterator iter = m_listClient.begin();
int iPos = 0;
while (iPos++ != dwIndex - WSA_WAIT_EVENT_0)++iter;
WSANETWORKEVENTS tNetEvents;
WSAEnumNetworkEvents(iter->sock, iter->event, &tNetEvents);
if (FD_ACCEPT & tNetEvents.lNetworkEvents)
{
if (0 != tNetEvents.iErrorCode[FD_ACCEPT_BIT])
{
std::cout<<"accept error occured/n";
continue;
}
else
{
int dwAddrLen = sizeof(tComeinAddr);
SOCKET tSockIn = accept(tSock, (sockaddr*)&tComeinAddr, &dwAddrLen);
ev.event = WSACreateEvent();
WSAEventSelect(tSockIn, ev.event, FD_READ | FD_CLOSE);
ev.sock = tSockIn;
m_listClient.push_back(ev);
cout << "new come in..." << inet_ntoa(tComeinAddr.sin_addr) << "/n";
send(tSockIn, "You are welcome", strlen("You are welcome"), 0);
}
}
else if (FD_READ & tNetEvents.lNetworkEvents)
{
if (0 != tNetEvents.iErrorCode[FD_READ_BIT])
{
std::cout<<"read error occured/n";
continue;
}
else
{
int dwRet = recv(iter->sock, cBuf, 1024, 0);
if (SOCKET_ERROR == dwRet)
{
}
else
{
cout << "client...data: ";
for (int n = 0; n != dwRet; ++n)
{
cout << "0x" << hex << (int)cBuf[n] << " ";
}
cout << endl;
}
}
}
else if (FD_CLOSE & tNetEvents.lNetworkEvents)
{
int dwNameLen = sizeof(tComeinAddr);
getpeername(iter->sock, (sockaddr*)&tComeinAddr, &dwNameLen);
closesocket(iter->sock);
WSACloseEvent(iter->event);
m_listClient.erase(iter);
cout << "client..." << inet_ntoa(tComeinAddr.sin_addr) << " quit/n";
}
}
}
int _tmain(int argc, TCHAR *argv[])
{
CSocketObject *poSockServer = new CTcpServer;
poSockServer->Run();
delete poSockServer;
return 0;
}