cocos2d-x3.2与服务端框架Firefly的网络编程(初级网络通讯)
好久沒(méi)寫東西,最近在研究服務(wù)端框架Firefly和Pomelo,身為菜鳥的我的確花了很大功夫才看懂一些源代碼。原來(lái)打算玩下Pomelo,不過(guò)我不得不說(shuō)這東西真的是給專業(yè)開發(fā)者準(zhǔn)備的,我搞了半天libpomelo也沒(méi)順利鏈接上服務(wù)器,光是鏈接服務(wù)器都那么難搞,更別說(shuō)通訊了,我還能說(shuō)什么呢……(真的是網(wǎng)絡(luò)資料都翻遍了,真不知道其它人是怎么用的),官方示例里并沒(méi)有簡(jiǎn)易代碼,所以不適合像我這樣的超級(jí)菜鳥使用,相比之下,Firefly更容易上手,有很多類型的源代碼,簡(jiǎn)易通俗的和系統(tǒng)完整級(jí)的都有,認(rèn)真研究的話真能學(xué)到不少東西……
因?yàn)楣俜浇o出的網(wǎng)絡(luò)通訊協(xié)議示例里只有python的客戶端源碼,所以對(duì)于小白來(lái)說(shuō),可能不知道如何在cocos2d-x項(xiàng)目中的VC++里實(shí)現(xiàn),這也算是一個(gè)添加的教程吧。還和以前一樣,把研究出的東西記錄下以備后用,希望對(duì)初學(xué)者也能有所幫助……
Firefly是開源游戲服務(wù)器框架,可以直接到九秒社區(qū)下載安裝,這里不說(shuō)安裝過(guò)程了,我使用的是新版的gFirefly,這個(gè)也是可以在gitHub上下載到,安裝會(huì)麻煩些,話說(shuō)好久沒(méi)更新了唉……難道最近都在忙CrossAPP項(xiàng)目?
cocos2d-x3.2需要使用VS2012,其具有C++11新特性,在使用線程上已經(jīng)相當(dāng)方便了,不再需要依賴于第三方的pthread
通常,在cocos2dx里使用的是http類的短鏈接通訊,不過(guò)我在這里要記錄的是使用socket與服務(wù)端進(jìn)行交互,在像linux這樣的平臺(tái)下,一般使用的都是BSD socket,這個(gè)當(dāng)然不是第三方的插件,而是unix / linux系統(tǒng)里自帶的,這也使用得跨平臺(tái)使用也沒(méi)什么問(wèn)題,本例只是在windows上測(cè)試通過(guò)的代碼,未在手機(jī)真機(jī)上測(cè)試過(guò),不過(guò)應(yīng)該差不多。
在Firefly的源代碼里,一般可以看到都包含一個(gè)network的文件夾,里面有網(wǎng)絡(luò)通訊使用的方法和類,算是一個(gè)打了個(gè)包,下面只是把里面最核心的代碼拿出來(lái)修改使用:
socket最核心的三個(gè)方法就是:
connect() 用于鏈接服務(wù)器
send() 用于發(fā)消息到服務(wù)器
recv() 用于接收服務(wù)器返回的消息
本身使用上面的東西沒(méi)什么難的,對(duì)于小白來(lái)說(shuō),真正需要了解的是Firefly的通訊協(xié)議,如果你在客戶端發(fā)送的消息格式與Firefly的消息格式不一樣,那Firefly會(huì)直接飛出一段英文,意思大概是“接收到一個(gè)非法包,沒(méi)法識(shí)別”。所以這里需要了解一下Firefly的通訊協(xié)議。
在發(fā)送給Firefly服務(wù)端的消息中需要包含以下頭部信息(這些在官方的教程里是有的):
class Message:public CCObject { public:char HEAD0;char HEAD1;char HEAD2;char HEAD3;char ProtoVersion;byte serverVersion[4];byte length[4];byte commandId[4];/*** 消息的數(shù)據(jù)*/char* data;Message();int datalength();~Message(); };上面一直到commandId的聲明定義都是消息頭,也就是協(xié)議頭,這個(gè)協(xié)議頭是用來(lái)識(shí)別消息的基礎(chǔ)信息,像協(xié)議版本protoversion,整個(gè)消息包的長(zhǎng)度length,命令號(hào)commandId(可以用來(lái)執(zhí)行指定服務(wù)端功能函數(shù)的識(shí)別號(hào))……data就是我們要傳送的消息主體信息內(nèi)容,上面是在客戶端里定義的一個(gè)基于CCobject的消息對(duì)象。下面再看看服務(wù)端的,下面的代碼取自游戲《烽煙OL》服務(wù)端源碼,只要在copy到新建的Firefly項(xiàng)目中即可使用: from gfirefly.server.globalobject import GlobalObject from gfirefly.netconnect.datapack import DataPackProtocdef callWhenConnLost(conn):dynamicId = conn.transport.sessionnoGlobalObject().remote['gate'].callRemote("NetConnLost_2",dynamicId)print('一個(gè)鏈接已經(jīng)斷開')def CreatVersionResult(netversion):return netversiondef doConnectionMade(conn):print('已成功建立一個(gè)鏈接')dataprotocl = DataPackProtoc(78,37,38,48,9,0) GlobalObject().netfactory.setDataProtocl(dataprotocl)GlobalObject().netfactory.doConnectionLost = callWhenConnLost GlobalObject().netfactory.doConnectionMade = doConnectionMadefrom gfirefly.server.globalobject import remoteserviceHandle from gfirefly.server.globalobject import netserviceHandle@netserviceHandle def echo_1(_conn,data):print(data)return datadef echo_2(showtext):print(showtext);return showtext
其中下面這段就是用來(lái)自定義協(xié)議頭的代碼,分別對(duì)應(yīng)于前面客戶端上的定義的前6個(gè)參數(shù),如果發(fā)送過(guò)來(lái)的包不是包含相同格式及對(duì)應(yīng)信息時(shí),則不會(huì)被服務(wù)端解析 dataprotocl = DataPackProtoc(78,37,38,48,9,0)
上面還定義了一個(gè)名為echo_1的函數(shù),后面這個(gè)_1是Firefly用識(shí)別功能函數(shù)的ID,絕對(duì)不能重復(fù),當(dāng)我們從客戶端發(fā)送消息時(shí),如果指定commandId參數(shù)為1,則服務(wù)端在接收到這個(gè)消息時(shí),會(huì)執(zhí)行echo_1這個(gè)函數(shù),執(zhí)行完后的return用來(lái)把返回給客戶端相應(yīng)的數(shù)據(jù),服務(wù)端的代碼就算是這樣完成了。
再看看消息構(gòu)造函數(shù),這個(gè)也是取自Firefly官方發(fā)布的游戲源代碼:
Message* networkManager::constructMessage(const char* data,int commandId) {Message* msg = new Message();msg->HEAD0=78;msg->HEAD1=37;msg->HEAD2=38;msg->HEAD3=48;msg->ProtoVersion=9;int a=0;msg->serverVersion[3]=(byte)(0xff&a);;msg->serverVersion[2]=(byte)((0xff00&a)>>8);msg->serverVersion[1]=(byte)((0xff0000&a)>>16);msg->serverVersion[0]=(byte)((0xff000000&a)>>24);int b=strlen(data)+4;msg->length[3]=(byte)(0xff&b);;msg->length[2]=(byte)((0xff00&b)>>8);msg->length[1]=(byte)((0xff0000&b)>>16);msg->length[0]=(byte)((0xff000000&b)>>24);int c=commandId;msg->commandId[3]=(byte)(0xff&c);;msg->commandId[2]=(byte)((0xff00&c)>>8);msg->commandId[1]=(byte)((0xff0000&c)>>16);msg->commandId[0]=(byte)((0xff000000&c)>>24);// str.append(msg->HEAD0);printf("%d" ,msg->datalength());msg->data = new char[msg->datalength()];memcpy(msg->data+0,&msg->HEAD0,1);memcpy(msg->data+1,&msg->HEAD1,1);memcpy(msg->data+2,&msg->HEAD2,1);memcpy(msg->data+3,&msg->HEAD3,1);memcpy(msg->data+4,&msg->ProtoVersion,1);memcpy(msg->data+5,&msg->serverVersion,4);memcpy(msg->data+9,&msg->length,4);memcpy(msg->data+13,&msg->commandId,4);memcpy(msg->data+17,data,strlen(data));//memcpy(msg->data+position,bytes+offset,len);//msg->data = data;return msg; }上面的代碼對(duì)消息從頭到尾按次序進(jìn)行了一次拼接封裝,算是打包進(jìn)data中,讓其成為一個(gè)完整的數(shù)據(jù)包,最后返回消息對(duì)象。然后就是鏈接服務(wù)器了,下面是代碼:
bool networkManager::Connect() {mLock.lock();//判斷windows平臺(tái)下初始鏈接初始化是否成功if(Init()==-1){return false;}//判斷套接字是否創(chuàng)建成功if(Create(AF_INET,SOCK_STREAM,0)==false){return false;};//設(shè)置socket為非阻塞模式/*int retVal;unsigned long ul = 1;retVal=ioctlsocket(m_sock, FIONBIO, &ul);if(retVal==SOCKET_ERROR){CCLOG("設(shè)置阻塞參數(shù)錯(cuò)誤");closesocket(m_sock); #ifdef WIN32WSACleanup();#endif}*///使用創(chuàng)建的套接字鏈接服務(wù)器struct sockaddr_in svraddr;svraddr.sin_family = AF_INET;svraddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);svraddr.sin_port = htons(IP_HOST);int ret = connect(m_sock, (struct sockaddr*) &svraddr, sizeof(svraddr));if (ret == SOCKET_ERROR) {/*closesocket(m_sock);#ifdef WIN32WSACleanup();#endif*/CCLOG("link failed");//鏈接成功后開始發(fā)送數(shù)據(jù)到服務(wù)器//sendThread();//recvThread();return false;}//鏈接成功后開始發(fā)送數(shù)據(jù)到服務(wù)器sendThread();CCLOG("link successed");mLock.unlock();return true; }可以看到上面鏈接代碼的尾部已經(jīng)加入執(zhí)行了發(fā)送數(shù)據(jù)的函數(shù),發(fā)送的實(shí)現(xiàn)代碼其實(shí)很簡(jiǎn)單,下面是發(fā)送了一條"getSendMessage successful!"的信息給服務(wù)器,而如果服務(wù)器收到這個(gè)消息后,也會(huì)在log里輸出這樣一條消息的: void networkManager::sendThread(){Message* msg=constructMessage("getSendMessage successful!",1);//發(fā)消息Send(msg->data,msg->datalength(),0); }
發(fā)送消息后,則可以開始監(jiān)聽接收服務(wù)端返回的數(shù)據(jù)了,下面只給出了基本代碼,不包含數(shù)據(jù)解析,收到服務(wù)端返回的消息后可以看到LOG輸出的信息: void networkManager::RecvFunc(){char recvBuf[17];FD_ZERO(&fdRead);FD_SET(m_sock,&fdRead);mLock.lock();struct timeval aTime;aTime.tv_sec = 5;aTime.tv_usec = 0;int ret = select(m_sock,&fdRead,NULL,NULL,&aTime);if (FD_ISSET(m_sock,&fdRead)) {CCLog("socket State=%d",ret);if(ret==1){//先拿到時(shí)協(xié)議頭數(shù)據(jù),根據(jù)里面的信息判斷應(yīng)該調(diào)用哪些回調(diào)函數(shù)進(jìn)行下一步數(shù)據(jù)處理//while(true){int getRevDataLength=recv(m_sock,recvBuf,17,0);if(getRevDataLength==17){CCLOG("recvThread OK,getDataProcess=%d",getRevDataLength); }else{CCLOG("The connect has terminated! revData is not completed!");CCLOG("The ERROR CODE:%d",WSAGetLastError()); //closesocket(m_sock);}}}else{CCLOG("select sock error"); }mLock.unlock(); }//執(zhí)行接收線程 void networkManager::recvThread(){//開啟一條t2線程,入口函數(shù)為RecvFunc()std::thread t2(&networkManager::RecvFunc,this); t2.join(); }
最后執(zhí)行代碼后,可以在服務(wù)端上看到我們發(fā)送的消息,如下圖
至此就算完成了一次與服務(wù)端的通訊會(huì)話。
由于我研究代碼功能實(shí)現(xiàn)時(shí)有隨意亂寫代碼的壞習(xí)慣,所以,源代碼可能會(huì)有些多余和不符合標(biāo)準(zhǔn)的東西,請(qǐng)多包涵!VS2012的客戶端項(xiàng)目源代碼可到下面地址下載:
http://download.csdn.net/detail/cyistudio/8004925
總結(jié)
以上是生活随笔為你收集整理的cocos2d-x3.2与服务端框架Firefly的网络编程(初级网络通讯)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 移动学习 AndroidStudio内存
- 下一篇: 范德堡大学用机器学习预测自杀,准确率在8