COCOS2D-X 网络篇---强联网(采用技术 BSD SOCKET+多线程技术 +protobuf)客户端实战篇

来源:互联网 发布:每日股市分析 知乎 编辑:程序博客网 时间:2024/06/04 19:06

转自:http://blog.csdn.net/zzhboy/article/details/9878941

下面我们就正式开始客户端的搭建 首先我献给大家画一张我的客户端实现的流程图

              我PS 画的大家不要见怪啊 不过流程就是这样的

            

 搭建看到我上面的框架图的时候 就知道我的大概设计思路,

   boy 在这里强调一点 这个是用异步的结构实现  其中线程类 我是参照java 里面的方法。

   好了废话不多 首先先上 BSD SOCKET 这个核心类

   

   

[cpp] view plaincopy
  1. /* 
  2.  * define file about portable socket class. 
  3.  * description:this sock is suit both windows and linux 
  4.  * design:odison 
  5.  * e-mail:odison@126.com> 
  6.  * 
  7.  */  
  8.   
  9. #ifndef _ODSOCKET_H_  
  10. #define _ODSOCKET_H_  
  11.   
  12. #ifdef WIN32  
  13.     #include <winsock2.h>  
  14.     typedef int             socklen_t;  
  15. #else  
  16.     #include <sys/socket.h>  
  17.     #include <netinet/in.h>  
  18.     #include <netdb.h>  
  19.     #include <fcntl.h>  
  20.     #include <unistd.h>  
  21.     #include <sys/stat.h>  
  22.     #include <sys/types.h>  
  23.     #include <arpa/inet.h>  
  24.     typedef int             SOCKET;  
  25.   
  26.     //#pragma region define win32 const variable in linux  
  27.     #define INVALID_SOCKET  -1  
  28.     #define SOCKET_ERROR    -1  
  29.     //#pragma endregion  
  30. #endif  
  31.   
  32.   
  33. class ODSocket {  
  34.   
  35. public:  
  36.     ODSocket(SOCKET sock = INVALID_SOCKET);  
  37.     ~ODSocket();  
  38.   
  39.     // Create socket object for snd/recv data  
  40.     bool Create(int af, int type, int protocol = 0);  
  41.   
  42.     // Connect socket  
  43.     bool Connect(const char* ip, unsigned short port);  
  44.     //#region server  
  45.     // Bind socket  
  46.     bool Bind(unsigned short port);  
  47.   
  48.     // Listen socket  
  49.     bool Listen(int backlog = 5);  
  50.   
  51.     // Accept socket  
  52.     bool Accept(ODSocket& s, char* fromip = NULL);  
  53.     //#endregion  
  54.     int Select();  
  55.     // Send socket  
  56.     int Send(const char* buf, int len, int flags = 0);  
  57.   
  58.     // Recv socket  
  59.     int Recv(char* buf, int len, int flags = 0);  
  60.   
  61.     // Close socket  
  62.     int Close();  
  63.   
  64.     // Get errno  
  65.     int GetError();  
  66.   
  67.     //#pragma region just for win32  
  68.     // Init winsock DLL  
  69.     static int Init();  
  70.     // Clean winsock DLL  
  71.     static int Clean();  
  72.     //#pragma endregion  
  73.   
  74.     // Domain parse  
  75.     static bool DnsParse(const char* domain, char* ip);  
  76.   
  77.     ODSocket& operator = (SOCKET s);  
  78.   
  79.     operator SOCKET ();  
  80.   
  81. protected:  
  82.     SOCKET m_sock;  
  83.     fd_set  fdR;  
  84. };  
  85.   
  86. #endif  
对于这个类 我主要讲解四个方法  一个就是 Connect 这个方法  这个主要是用来连接的

第二个  Send 方法 这个主要是用来发送数据的 

第三个方法 Recv  这个主要是用来接收数据的、

第四个方法 Select  这个主要用来判断当前socket 的状态,这里我只用了 判断是否有数据回来这个方法。 


这里说明一下  一旦调用recv 这个方法 他会一直等到读取到数据,所以在我们正常的开发游戏过程中。我们都会把它放到单独的线程中来执行。防止我们的主线程卡死。


好下面介绍一下我们的 连接线程类

[cpp] view plaincopy
  1. #pragma once  
  2. #include "ODSocket.h"  
  3. #include "pthread.h"  
  4. class SocketThread  
  5. {  
  6. public:   
  7.     ~SocketThread(void);  
  8.     static SocketThread*   GetInstance();  
  9.     int start();    
  10.     ODSocket getSocket();  
  11.     int state;// 0 表示连接成功 1 表示连接失败  
  12.     ODSocket csocket;     
  13.     void stop();//函数中止当前线程。  
  14. private:  
  15.     pthread_t pid;    
  16.     static void* start_thread(void *);//静态成员函数,相当于C中的全局函数     
  17.     SocketThread(void);  
  18. private:  
  19.     static SocketThread* m_pInstance;     
  20. };  

这个线程类是用来连接 我们的 服务器的    大家看到我上面的方法就可以才想到  我这个类是一个单利的模式。因为一个游戏中正常情况下只需要一个 套接字即可。所以我这个类采用了单利的模式可以获取一个套接字对象  。

下面贴上实现

  

[cpp] view plaincopy
  1. #include "SocketThread.h"  
  2. #include "cocos2d.h"  
  3. #include "ResPonseThread.h"  
  4. USING_NS_CC;  
  5. int SocketThread::start(){        
  6.     int errCode = 0;  
  7.     do{  
  8.         pthread_attr_t tAttr;  
  9.         errCode = pthread_attr_init(&tAttr);  
  10.         CC_BREAK_IF(errCode!=0);  
  11.         //但是上面这个函数其他内容则主要为你创建的线程设定为分离式  
  12.         errCode = pthread_attr_setdetachstate(&tAttr, PTHREAD_CREATE_DETACHED);  
  13.         if (errCode!=0) {  
  14.             pthread_attr_destroy(&tAttr);  
  15.             break;  
  16.         }         
  17.         errCode = pthread_create(&pid,&tAttr,start_thread,this);  
  18.     }while (0);  
  19.     return errCode;  
  20. }   
  21.   
  22.   
  23. void* SocketThread::start_thread(void *arg)   {    
  24.     SocketThread* thred=(SocketThread*)arg;  
  25.     ODSocket cdSocket;  
  26.     cdSocket.Init();      
  27.     bool isok=cdSocket.Create(AF_INET,SOCK_STREAM,0);     
  28.     bool iscon=cdSocket.Connect("127.0.0.1",8888);  
  29.     if(iscon){  
  30.         thred->state=0;  
  31.         ResPonseThread::GetInstance()->start();// 启动响应参数  
  32.         CCLOG("conection");  
  33.     }else{  
  34.         thred->state=1;  
  35.     }     
  36.     thred->csocket=cdSocket;  
  37.     return NULL;                                                                                      
  38. }  
  39. ODSocket SocketThread::getSocket(){  
  40.     return this->csocket;  
  41. }  
  42.   
  43. SocketThread* SocketThread::m_pInstance=new SocketThread;   
  44. SocketThread* SocketThread::GetInstance(){    
  45.     return m_pInstance;  
  46. }  
  47.   
  48. void SocketThread::stop(){  
  49.     pthread_cancel(pid);  
  50.     pthread_detach(pid);   
  51. }  
  52.   
  53. SocketThread::SocketThread(void)  
  54. {  
  55.   
  56. }  
  57.   
  58.   
  59. SocketThread::~SocketThread(void)  
  60. {  
  61.     if(m_pInstance!=NULL){  
  62.         delete m_pInstance;  
  63.     }  
  64. }  
对于多线程不是很熟悉的同学们可以在 百度 下相关的资料。只要实现了上面的类,我们就可以连接tong服务器了


下面贴出

接收线程类

[cpp] view plaincopy
  1. #pragma once  
  2. // 此类主要 处理服务器推送过来的消息  
  3. #include "pthread.h"  
  4. #include "cocos2d.h"  
  5. #include "BaseResponseMsg.h"  
  6. typedef void (cocos2d::CCObject::*ResPonseThreadEvent)(BaseResponseMsg*);  
  7. #define callFunc_selectormsg(_SELECTOR) (ResPonseThreadEvent)(&_SELECTOR)  
  8.   
  9. #define M_ADDCALLBACKEVENT(varName)\  
  10. protected: cocos2d::CCObject* m_##varName##listener;ResPonseThreadEvent varName##selector;\  
  11. publicvoid add##varName##ListenerEvent(ResPonseThreadEvent m_event,cocos2d::CCObject* listener)  { m_##varName##listener=listener;varName##selector=m_event; }  
  12.   
  13. class ResPonseThread  
  14. {  
  15. public:   
  16.     ~ResPonseThread(void);  
  17.     static ResPonseThread*   GetInstance(); // 获取该类的单利  
  18.     int start (void * =NULL); //函数是线程启动函数,其输入参数是无类型指针。  
  19.     void stop();     //函数中止当前线程。  
  20.     void sleep (int tesec); //函数让当前线程休眠给定时间,单位为毫秒秒。  
  21.     void detach();   //  
  22.     void * wait();  
  23.           
  24. private:  
  25.     ResPonseThread(void);  
  26.     pthread_t handle;   
  27.     bool started;  
  28.     bool detached;  
  29.     static void * threadFunc(void *);  
  30.     static ResPonseThread* m_pInstance;   
  31.     M_ADDCALLBACKEVENT(msg);// 聊天回调函数  
  32.     M_ADDCALLBACKEVENT(notcon);//断网回调函数  
  33.       
  34. };  

这个并不算一个完整的类,因为不同的命令对应的回调函数不一样 当然你也可以弄成一个回调函数,不过我喜欢把东西分开来写

实现代码

[cpp] view plaincopy
  1. #include "ResPonseThread.h"  
  2. #include "cocos2d.h"  
  3. #include "SocketThread.h"  
  4. #include "BaseResponseMsg.h"  
  5. ResPonseThread* ResPonseThread::m_pInstance=new ResPonseThread;   
  6. ResPonseThread* ResPonseThread::GetInstance(){    
  7.     return m_pInstance;  
  8. }  
  9. ResPonseThread::ResPonseThread(void)  
  10. {  
  11.   
  12.     this->m_msglistener=NULL;  
  13.   
  14.     started = detached = false;  
  15. }  
  16.   
  17.   
  18. ResPonseThread::~ResPonseThread(void)  
  19. {  
  20.     stop();  
  21. }  
  22. int ResPonseThread::start(void * param){          
  23.     int errCode = 0;  
  24.     do{  
  25.         pthread_attr_t attributes;  
  26.         errCode = pthread_attr_init(&attributes);  
  27.         CC_BREAK_IF(errCode!=0);  
  28.         //但是上面这个函数其他内容则主要为你创建的线程设定为分离式  
  29.         errCode = pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);  
  30.         if (errCode!=0) {  
  31.             pthread_attr_destroy(&attributes);  
  32.             break;  
  33.         }         
  34.         errCode = pthread_create(&handle, &attributes,threadFunc,this);  
  35.         started = true;   
  36.     }while (0);  
  37.     return errCode;  
  38. }   
  39.   
  40. void* ResPonseThread::threadFunc(void *arg){  
  41.     ResPonseThread* thred=(ResPonseThread*)arg;   
  42.     ODSocket csocket=SocketThread::GetInstance()->getSocket();  
  43.     if(SocketThread::GetInstance()->state==0){  
  44.         while(true){  
  45.             // 表示服务器端 有消息推送过来  
  46.             if(csocket.Select()==-2){                 
  47.                 char recvBuf[8];// 获取请求头的 数据  
  48.                 int i=  csocket.Recv(recvBuf,8,0);                
  49.                 if (i==8){                
  50.                     char dc1[2]={recvBuf[1],recvBuf[0]};  
  51.                     short len = *(short*)&dc1[0];  
  52.                     char dc2[2]={recvBuf[3],recvBuf[2]};  
  53.                     short code = *(short*)&dc2[0];  
  54.                     char dc3[4]={recvBuf[7],recvBuf[6],recvBuf[5],recvBuf[4]};  
  55.                     int playId=*(int*)&dc3[0];  
  56.                     CCLOG("%d",playId);  
  57.                     char* messbody=NULL;  
  58.                     int myl=0;  
  59.                     if(len>8){                             
  60.                         myl=len-8;  
  61.                         messbody=new char[myl];                       
  62.                         csocket.Recv(messbody,myl,0);                       
  63.                     }  
  64.                     //  //1001 = com.lx.command.player.LoginCmd  
  65.                     //1002 = com.lx.command.player.RegisterCmd  
  66.                     //1003 = com.lx.command.player.HeartBeatCmd  
  67.                     // 登陆  
  68.   
  69.                     BaseResponseMsg* basmsg=new BaseResponseMsg();  
  70.                     basmsg->code=code;  
  71.                     basmsg->len=len;  
  72.                     basmsg->playerId=playId;  
  73.                     // 表示服务器推动过来的消息  
  74.                     if(code==1000){  
  75.                         if(thred->m_msglistener){  
  76.                         basmsg->setStringToMsg(messbody,myl);  
  77.                         (thred->m_msglistener->*(thred->msgselector))(basmsg);  
  78.                         }  
  79.                     }  
  80.                     else {  
  81.                         CCLOG("%d",code);  
  82.                     }  
  83.                 }else {  
  84.                     if(thred->m_notconlistener){  
  85.                      BaseResponseMsg* basmsg=new BaseResponseMsg();  
  86.                      basmsg->state=1;// 连接断开  
  87.                     (thred->m_notconlistener->*(thred->notconselector))(basmsg);  
  88.                     }  
  89.                     break;  
  90.                 }  
  91.   
  92.             }  
  93.         }  
  94.     }  
  95.     return NULL;  
  96. }  
  97.   
  98.   
  99. void ResPonseThread::stop(){  
  100.     if (started && !detached) {   
  101.         pthread_cancel(handle);  
  102.         pthread_detach(handle);   
  103.         detached = true;   
  104.     }  
  105. }  
  106.   
  107. void * ResPonseThread::wait(){  
  108.     void * status = NULL;  
  109.     if (started && !detached) {   
  110.         pthread_join(handle, &status);   
  111.     }  
  112.     return status;  
  113. }  
  114. void ResPonseThread::sleep(int secstr){  
  115.     timeval timeout = { secstr/1000, secstr%1000};   
  116.     select(0, NULL, NULL, NULL, &timeout);  
  117. }  
  118.   
  119. void ResPonseThread::detach(){  
  120.     if (started && !detached) {  
  121.         pthread_detach(handle);  
  122.     }   
  123.     detached = true;  
  124. }  

 当大家看到接收代码的时候或许很困惑。这里是引文大小端的问。还有前八个字节是我们规定好的,所以只要解析出前八个字节我们就知道服务器给传送过来的什么。 然后调用响应的回调 通知请求方即可。

   下面贴出 一个消息体组装类

    

[cpp] view plaincopy
  1. #pragma once  
  2. #include <string.h>  
  3. #include "ConvertEndianUtil.h"  
  4. #include "ODSocket.h"  
  5. #include "SocketThread.h"  
  6. typedef struct messageHead{  
  7.     short len;  
  8.     short code;  
  9.     int   playerid;  
  10. } messagehead;  
  11. template  <typename  Rquest> class BaseRequestMsg  
  12. {  
  13. public:  
  14.     BaseRequestMsg(void);  
  15.     ~BaseRequestMsg(void);  
  16.     void setRequestMessage(Rquest message);// 设置请求体  
  17.     void setMessageHead(short code,int player=0);// 设置 请求头  
  18.     bool sendMessage();// 发送信息  
  19. private:  
  20.     Rquest requestmessage;  
  21.     messagehead messageHead;  
  22.     char* getSendMessage();   
  23.     short dateLength;  
  24.     std::string requestMessage;   
  25. };  
  26.   
  27. template  <typename  Rquest>  
  28. BaseRequestMsg<Rquest>::BaseRequestMsg(void){  
  29. }  
  30. template  <typename  Rquest>  
  31. BaseRequestMsg<Rquest>::~BaseRequestMsg(void){  
  32. }  
  33.   
  34. template  <typename  Rquest>  
  35. void BaseRequestMsg<Rquest>::setRequestMessage(Rquest message){  
  36.     std::string data;   
  37.     message.SerializeToString(&data);  
  38.     this->requestMessage=data;  
  39. }  
  40.   
  41. template  <typename  Rquest>  
  42. void BaseRequestMsg<Rquest>::setMessageHead(short code,int player){  
  43.     messageHead.code=ConvertEndianUtil::convertEndianShort(code);  
  44.     messageHead.playerid=ConvertEndianUtil::convertForInt(player);  
  45. }  
  46.   
  47. template  <typename  Rquest>  
  48. char* BaseRequestMsg<Rquest>::getSendMessage(){  
  49.     short total=8+requestMessage.length();  
  50.     dateLength=total;  
  51.     messageHead.len=ConvertEndianUtil::convertEndianShort(total);  
  52.     char* requestmessage=new char[total];  
  53.     char* requestmessagehead=(char*)&messageHead;     
  54.     int i=sizeof(messageHead);  
  55.     int len=sizeof(requestMessage.c_str())/sizeof(char);  
  56.     memcpy(requestmessage,requestmessagehead,8);  
  57.     memcpy(&requestmessage[8],requestMessage.c_str(),requestMessage.length());  
  58.     return requestmessage;  
  59. }  
  60.   
  61. template  <typename  Rquest>  
  62. bool BaseRequestMsg<Rquest>::sendMessage(){     
  63.     ODSocket cSocket=SocketThread::GetInstance()->getSocket();  
  64.     char* dd=this->getSendMessage();  
  65.     int cout=cSocket.Send(this->getSendMessage(),this->dateLength,0);  
  66.     if(cout==this->dateLength){  
  67.         return true;  
  68.     }else {  
  69.         return false;  
  70.     }  
  71. }  
 这里采用了C++ 类模板  这样可以让我们的程序更通用一点。 这个主要是针对protobuf 协议体的组装。

 下面给大家贴上一段调用代码

[cpp] view plaincopy
  1. BaseRequestMsg<zzboy::protobuf::ChatMsgReq>* baserlong=new BaseRequestMsg<zzboy::protobuf::ChatMsgReq>();  
  2. zzboy::protobuf::ChatMsgReq req;  
  3. req.set_msgtype(1);  
  4. req.set_message("ddddd");  
  5. baserlong->setMessageHead((short)1000,(int)1);  
  6. baserlong->setRequestMessage(req);  
  7. baserlong->sendMessage();  
这就是一个发送消息的方法,哈哈看起来很简单吧。

   这里我做的demo 是 聊天系统。在很多游戏里面都有聊天。这里就是一个简单的实现。不过整体的思路应该是这样

   

   

红色区域内 1 表示我自己说的话  4545 是别人说的话。其实只要服务器通知我 有人给我发送消息。都会在这里展示出来。

在这里我遇见一个BUG  就是 CCLabelTTF* lable = CCLabelTTF::create(tem, "Arial", 24); 这个标签的创建 在线程的回调函数中我始终穿件不成功。导致我用了另外的一个办法解决。这里如果谁知道这个BUG  为什么 请私信给我活着留言给我。咱们共同进步


                  哈哈 写到这里网络连接着一块就完了,其实感觉也没什么,最重要的就是你解析过数据之后要干什么。大家发现BOY 是不是全才 服务器和客户端都会 。我感觉如果时间允许我自己可以做一个小型的多人在线网络游戏。 哈哈。 

                   关于上面讲到的可能有些人还是不明白。可以留言给我或者在码农哥的群里给我说,如果我看到了都会给大家讲解。这两天这是忍着病给大家写的 有哪些写的不好请见谅。


0 0
原创粉丝点击