最近几天的闭关修练,终于把OGF的网络部分给补充完了,这样大家就方便开发自己的网络游戏.网络应用方面分为客户端与服务器端,上集我们先介绍客户端的使用,下集再介绍服务器端的的开发.
客户端的消息分发采用原有的队列调配模式,机制上与计时器或动作引擎没有太大的差异,无非创建了一个空窗口与SOCKET进行绑定,然后将SOCKET消息发送队列后再分发.除此之外,提供了简单的数据加/解密的支持,提供了SOCKET代理模式的支持.
好了,接下来就用实例来讲解一下OGF的网络应用:
1.修改GameFrame.cpp的GameInit方法,添加以下两行代码,以开启OGF对网络服务的支持:
//开启网络引擎
m_GameFrameSetting.bUseSocketEngine = true;
//关闭网络数据加密
m_GameFrameSetting.bEncrypted = false;
注:只要开启并连能服务器后,就可以在GameBody中得到网络消息的通知,包括:Connected, Read, Close三种事件消息
2.布置我们的客户端界面.首先要有两个输入框,一个是填IP地址(名为IP),另一个是填连接端口(名为Port);然后添加一个文本按钮,以指示系统连接或断开(名为Connect);最后要加入一个消息框(名为Message),用于显示提示信息.以上的对象为简单起见,均采用GameObjectText对象实现.在布局方面,大家可采用XML的方式去布置(方便很多),也可以像示例中所示,在OnLoading事件中初始化(较为复杂).代码中无非是创建对象,设置大小字体,位置...所以代码不帖出来了,有兴趣就看代码.
3.在MainSection类中添加以下私有变量:
TCHAR szServerIP[16]; //服务器IP
TCHAR szServerPort[6]; //服务器端口
IGameObjectText* pCurrentInput; //当前键盘焦点
4.由于还未做输入框控件,因此程序中用文本对象模拟,这样就要对文本框的键盘事件进行处理.键盘处理分为4种:回车,TAB,退格键和允许录入的字符集.处理方面大概分为以下几个判断:
I.回车(nKey==13),模拟鼠标点击连接按钮的事件,所以基本上将鼠标点击连接按钮处理拷贝过来就可以了.
II.TAB(nKey==9),pCurrentInput是记录当前拥有键盘焦点的对象,通过切换IP对象和PORT对象即可.
III.退格键(nKey==8),将当前键盘焦点对象对应的变量(szServerIP或szServerPort)最后的字符置为0;
IV.字符集,设置该输入框能键入的字符范围,也是将szServerIP或szServerPort最后的字符置为该字符,然后再在后补上0结束符
V.将szServerIP或szServerPort的内容输出至相应的对象
5.连接按钮和IP/PORT输入框的鼠标点击处理.点击IP/PORT输入框只需设置pCurrentInput为当前点击对象即可,而点击连接按钮,我们要判断当前是否连通,若是则作断开网络处理,否则使用szServerIP和szServerPort为参数进行网络连接.连接按钮的代码如下:
if(szObjectName=="Connect"){
IGameObjectText* pGOText = GET_OBJECTPTR_INTERFACE(pGameObject,IGameObjectText);
CString szCaption = pGOText->GetText();
if(szCaption=="Connect"){
WORD wPort = atoi(szServerPort);
m_lpFMHandles->pClientSocket->Connect(szServerIP, wPort);
pCurrentInput = NULL;
}
else{
m_lpFMHandles->pClientSocket->CloseSocket(true);
pCurrentInput = NULL;
}
}
6.重载GameBody的OnEventClientSocketConnect事件,我们要判断连接是否成功,可根据iErrorCode是否为0来判断,成功为0,否则为失败.
连接成功后,返回计算机名,并成生XX connected to Server的字样,以发送给服务器端,实现简单的握手处理.
//回复服务器
TCHAR szHostName[32];
gethostname(szHostName, 32);
TCHAR szConnectMsg[64];
sprintf(szConnectMsg, "%s connected!\0", szHostName);
m_lpFMHandles->pClientSocket->SendData(10049, 2, szConnectMsg, strlen(szConnectMsg));
然后在提示框中输出"Connected to Server!",最后将连接接按钮上的文本改为"Disconnect".
IGameObject *pIGameObject = m_pCurrentSection->GetGameView()->GetObject("default\\default\\Message");
IGameObjectText *pText = GET_OBJECTPTR_INTERFACE(pIGameObject, IGameObjectText);
pText->SetText("Connected to Server!");
pIGameObject = m_pCurrentSection->GetGameView()->GetObject("default\\default\\Connect");
pText = GET_OBJECTPTR_INTERFACE(pIGameObject, IGameObjectText);
pText->SetText("Disconnect");
连接失败,提示框则输出错误代码:
IGameObject *pIGameObject = m_pCurrentSection->GetGameView()->GetObject("default\\default\\Message");
IGameObjectText *pText = GET_OBJECTPTR_INTERFACE(pIGameObject, IGameObjectText);
TCHAR szError[128];
sprintf(szError, "Connect error:%d.", iErrorCode);
pText->SetText(szError);
7.重载GameBody的OnEventClientSocketRead事件中,先判断指令标识是否可识别,然后将收到的内容输出至提示框中,并回复服务器端已收到消息.
switch(Command.wMainCmdID){
case 10049:
if(Command.wSubCmdID==3){
IGameObject *pIGameObject = m_pCurrentSection->GetGameView()->GetObject("default\\default\\Message");
IGameObjectText *pText = GET_OBJECTPTR_INTERFACE(pIGameObject, IGameObjectText);
pText->SetText((LPCTSTR)pBuffer, wDataSize);
//回复服务器
TCHAR szHostName[32];
gethostname(szHostName, 32);
TCHAR szReply[64];
sprintf(szReply, "%s recved text!\0", szHostName);
m_lpFMHandles->pClientSocket->SendData(10049, 4, szReply, strlen(szReply));
}
break;
}
8.重载GameBody的OnEventClientSocketClose事件.当网络断开,即提示网络断开,并设置连接按钮上的文本.
IGameObject *pIGameObject = m_pCurrentSection->GetGameView()->GetObject("default\\default\\Message");
IGameObjectText *pText = GET_OBJECTPTR_INTERFACE(pIGameObject, IGameObjectText);
pText->SetText("Server Closeed!");
pIGameObject = m_pCurrentSection->GetGameView()->GetObject("default\\default\\Connect");
pText = GET_OBJECTPTR_INTERFACE(pIGameObject, IGameObjectText);
pText->SetText("Connect");
9.至此,简单的网络消息应用的实例已完成了.虽然示例代码比之前要多了点,但应该使用上还是算方便的.
Send函数的说明:
原形:virtual bool __cdecl SendData(WORD wMainCmdID, WORD wSubCmdID, void * pData, WORD wDataSize);
wMainCmdID与wSubCmdID实际上是OnEventClientSocketRead中的CMD_Command结构的际,以两层关系指示发送内容的标识.
pData,待发送的数据,可以是任意的结构体和内存区域.wDataSize则指示数据的长度.
运行结果: