cocos2d-x3.2与服务端框架Firefly的网络编程(初级网络通讯)
好久沒寫東西,最近在研究服務(wù)端框架Firefly和Pomelo,身為菜鳥的我的確花了很大功夫才看懂一些源代碼。原來打算玩下Pomelo,不過我不得不說這東西真的是給專業(yè)開發(fā)者準(zhǔn)備的,我搞了半天libpomelo也沒順利鏈接上服務(wù)器,光是鏈接服務(wù)器都那么難搞,更別說通訊了,我還能說什么呢……(真的是網(wǎng)絡(luò)資料都翻遍了,真不知道其它人是怎么用的),官方示例里并沒有簡易代碼,所以不適合像我這樣的超級菜鳥使用,相比之下,Firefly更容易上手,有很多類型的源代碼,簡易通俗的和系統(tǒng)完整級的都有,認真研究的話真能學(xué)到不少東西……
因為官方給出的網(wǎng)絡(luò)通訊協(xié)議示例里只有python的客戶端源碼,所以對于小白來說,可能不知道如何在cocos2d-x項目中的VC++里實現(xiàn),這也算是一個添加的教程吧。還和以前一樣,把研究出的東西記錄下以備后用,希望對初學(xué)者也能有所幫助……
Firefly是開源游戲服務(wù)器框架,可以直接到九秒社區(qū)下載安裝,這里不說安裝過程了,我使用的是新版的gFirefly,這個也是可以在gitHub上下載到,安裝會麻煩些,話說好久沒更新了唉……難道最近都在忙CrossAPP項目?
cocos2d-x3.2需要使用VS2012,其具有C++11新特性,在使用線程上已經(jīng)相當(dāng)方便了,不再需要依賴于第三方的pthread
通常,在cocos2dx里使用的是http類的短鏈接通訊,不過我在這里要記錄的是使用socket與服務(wù)端進行交互,在像linux這樣的平臺下,一般使用的都是BSD socket,這個當(dāng)然不是第三方的插件,而是unix / linux系統(tǒng)里自帶的,這也使用得跨平臺使用也沒什么問題,本例只是在windows上測試通過的代碼,未在手機真機上測試過,不過應(yīng)該差不多。
在Firefly的源代碼里,一般可以看到都包含一個network的文件夾,里面有網(wǎng)絡(luò)通訊使用的方法和類,算是一個打了個包,下面只是把里面最核心的代碼拿出來修改使用:
socket最核心的三個方法就是:
connect() 用于鏈接服務(wù)器
send() 用于發(fā)消息到服務(wù)器
recv() 用于接收服務(wù)器返回的消息
本身使用上面的東西沒什么難的,對于小白來說,真正需要了解的是Firefly的通訊協(xié)議,如果你在客戶端發(fā)送的消息格式與Firefly的消息格式不一樣,那Firefly會直接飛出一段英文,意思大概是“接收到一個非法包,沒法識別”。所以這里需要了解一下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é)議頭,這個協(xié)議頭是用來識別消息的基礎(chǔ)信息,像協(xié)議版本protoversion,整個消息包的長度length,命令號commandId(可以用來執(zhí)行指定服務(wù)端功能函數(shù)的識別號)……data就是我們要傳送的消息主體信息內(nèi)容,上面是在客戶端里定義的一個基于CCobject的消息對象。下面再看看服務(wù)端的,下面的代碼取自游戲《烽煙OL》服務(wù)端源碼,只要在copy到新建的Firefly項目中即可使用: 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('一個鏈接已經(jīng)斷開')def CreatVersionResult(netversion):return netversiondef doConnectionMade(conn):print('已成功建立一個鏈接')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
其中下面這段就是用來自定義協(xié)議頭的代碼,分別對應(yīng)于前面客戶端上的定義的前6個參數(shù),如果發(fā)送過來的包不是包含相同格式及對應(yīng)信息時,則不會被服務(wù)端解析 dataprotocl = DataPackProtoc(78,37,38,48,9,0)
上面還定義了一個名為echo_1的函數(shù),后面這個_1是Firefly用識別功能函數(shù)的ID,絕對不能重復(fù),當(dāng)我們從客戶端發(fā)送消息時,如果指定commandId參數(shù)為1,則服務(wù)端在接收到這個消息時,會執(zhí)行echo_1這個函數(shù),執(zhí)行完后的return用來把返回給客戶端相應(yīng)的數(shù)據(jù),服務(wù)端的代碼就算是這樣完成了。
再看看消息構(gòu)造函數(shù),這個也是取自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; }上面的代碼對消息從頭到尾按次序進行了一次拼接封裝,算是打包進data中,讓其成為一個完整的數(shù)據(jù)包,最后返回消息對象。然后就是鏈接服務(wù)器了,下面是代碼:
bool networkManager::Connect() {mLock.lock();//判斷windows平臺下初始鏈接初始化是否成功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ù)錯誤");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ā)送的實現(xiàn)代碼其實很簡單,下面是發(fā)送了一條"getSendMessage successful!"的信息給服務(wù)器,而如果服務(wù)器收到這個消息后,也會在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){//先拿到時協(xié)議頭數(shù)據(jù),根據(jù)里面的信息判斷應(yīng)該調(diào)用哪些回調(diào)函數(shù)進行下一步數(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ù)端的通訊會話。
由于我研究代碼功能實現(xiàn)時有隨意亂寫代碼的壞習(xí)慣,所以,源代碼可能會有些多余和不符合標(biāo)準(zhǔn)的東西,請多包涵!VS2012的客戶端項目源代碼可到下面地址下載:
http://download.csdn.net/detail/cyistudio/8004925
總結(jié)
以上是生活随笔為你收集整理的cocos2d-x3.2与服务端框架Firefly的网络编程(初级网络通讯)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动学习 AndroidStudio内存
- 下一篇: 范德堡大学用机器学习预测自杀,准确率在8