分类:cocos2d-x 编译cocos2d-x SOCKETgoogle protobuf 2013-08-10 16:41 9314人阅读 评论(36) 收藏 举报 cocos2d-x 多线程 google protobuf socket 网络
如果按照上面的一讲 你如果把环境搭建好了,下面我们就正式开始客户端的搭建 首先我献给大家画一张我的客户端实现的流程图
我PS 画的大家不要见怪啊 不过流程就是这样的
搭建看到我上面的框架图的时候 就知道我的大概设计思路,
boy 在这里强调一点 这个是用异步的结构实现 其中线程类 我是参照java 里面的方法。
好了废话不多 首先先上 BSD SOCKET 这个核心类
[cpp] view plain copy /* *definefileaboutportablesocketclass. *description:thissockissuitbothwindowsandlinux *design:odison *e-mail:odison@126.com> * */ #ifndef_ODSOCKET_H_ #define_ODSOCKET_H_ #ifdefWIN32 #include<winsock2.h> typedefintsocklen_t; #else #include<sys/socket.h> #include<netinet/in.h> #include<netdb.h> #include<fcntl.h> #include<unistd.h> #include<sys/stat.h> #include<sys/types.h> #include<arpa/inet.h> intSOCKET; //#pragmaregiondefinewin32constvariableinlinux #defineINVALID_SOCKET-1 #defineSOCKET_ERROR-1 //#pragmaendregion #endif classODSocket{ public: ODSocket(SOCKETsock=INVALID_SOCKET); ~ODSocket(); //Createsocketobjectforsnd/recvdata boolCreate(intaf,inttype,87); Font-weight:bold; background-color:inherit">intprotocol=0); //Connectsocket boolConnect(constchar*ip,unsignedshortport); //#regionserver //Bindsocket boolBind(unsignedshortport); //Listensocket boolListen(intbacklog=5); //Acceptsocket boolAccept(ODSocket&s,87); Font-weight:bold; background-color:inherit">char*fromip=NulL); //#endregion intSelect(); //Sendsocket intSend(char*buf,87); Font-weight:bold; background-color:inherit">intlen,87); Font-weight:bold; background-color:inherit">intflags=0); //Recvsocket intRecv(intflags=0); //Closesocket intClose(); //Geterrno intGetError(); //#pragmaregionjustforwin32 //InitwinsockDLL staticintInit(); //CleanwinsockDLL intClean(); //#pragmaendregion //Domainparse boolDnsParse(char*domain,87); Font-weight:bold; background-color:inherit">char*ip); ODSocket&operator=(SOCKETs); operatorSOCKET(); protected: SOCKETm_sock; fd_setfdr; }; #endif 对于这个类 我主要讲解四个方法 一个就是Connect 这个方法 这个主要是用来连接的
第二个 Send 方法 这个主要是用来发送数据的
第三个方法Recv 这个主要是用来接收数据的、
第四个方法Select 这个主要用来判断当前socket 的状态,这里我只用了 判断是否有数据回来这个方法。
这里说明一下 一旦调用recv 这个方法 他会一直等到读取到数据,所以在我们正常的开发游戏过程中。我们都会把它放到单独的线程中来执行。防止我们的主线程卡死。
好下面介绍一下我们的 连接线程类
copy #pragmaonce #include"ODSocket.h" #include"pthread.h" classSocketThread { public: ~SocketThread(voID); staticSocketThread*GetInstance(); intstart(); ODSocketgetSocket(); intstate;//0表示连接成功1表示连接失败 ODSocketcsocket; voIDstop();//函数中止当前线程。 private: pthread_tpID; staticvoID*start_thread(voID*);//静态成员函数,相当于C中的全局函数 SocketThread(staticSocketThread*m_pInstance; };这个线程类是用来连接 我们的 服务器的 大家看到我上面的方法就可以才想到 我这个类是一个单利的模式。因为一个游戏中正常情况下只需要一个 套接字即可。所以我这个类采用了单利的模式可以获取一个套接字对象 。
下面贴上实现
copy #include"SocketThread.h" #include"cocos2d.h" #include"ResPonseThread.h" USING_NS_CC; intSocketThread::start(){ interrCode=0; do{ pthread_attr_ttAttr; errCode=pthread_attr_init(&tAttr); CC_BREAK_IF(errCode!=0); //但是上面这个函数其他内容则主要为你创建的线程设定为分离式 errCode=pthread_attr_setdetachstate(&tAttr,PTHREAD_CREATE_DETACHED); if(errCode!=0){ pthread_attr_destroy(&tAttr); break; } errCode=pthread_create(&pID,&tAttr,start_thread,this); }while(0); returnerrCode; voID*SocketThread::start_thread(voID*arg){ SocketThread*thred=(SocketThread*)arg; ODSocketcdSocket; cdSocket.Init(); boolisok=cdSocket.Create(AF_INET,SOCK_STREAM,0); booliscon=cdSocket.Connect("127.0.0.1",8888); if(iscon){ thred->state=0; ResPonseThread::GetInstance()->start();//启动响应参数 cclOG("conection"); }else{ thred->state=1; } thred->csocket=cdSocket; returnNulL; ODSocketSocketThread::getSocket(){ returnthis->csocket; SocketThread*SocketThread::m_pInstance=newSocketThread; SocketThread*SocketThread::GetInstance(){ returnm_pInstance; voIDSocketThread::stop(){ pthread_cancel(pID); pthread_detach(pID); SocketThread::SocketThread(voID) { SocketThread::~SocketThread(if(m_pInstance!=NulL){ deletem_pInstance; } 对于多线程不是很熟悉的同学们可以在 百度 下相关的资料。只要实现了上面的类,我们就可以连接tong服务器了
下面贴出
接收线程类
copy //此类主要处理服务器推送过来的消息 #include"BaseResponseMsg.h" typedefvoID(cocos2d::CCObject::*ResPonseThreadEvent)(BaseResponseMsg*); #definecallFunc_selectormsg(_SELECTOR)(ResPonseThreadEvent)(&_SELECTOR) #defineM_ADDCALLBACKEVENT(varname)\ protected:cocos2d::CCObject*m_##varname##Listener;ResPonseThreadEventvarname##selector;\ public:voIDadd##varname##ListenerEvent(ResPonseThreadEventm_event,cocos2d::CCObject*Listener){m_##varname##Listener=Listener;varname##selector=m_event;} classResPonseThread ~ResPonseThread(voID); staticResPonseThread*GetInstance();//获取该类的单利 intstart(voID*=NulL);//函数是线程启动函数,其输入参数是无类型指针。 voIDsleep(inttesec);//函数让当前线程休眠给定时间,单位为毫秒秒。 voIDdetach();// voID*wait(); ResPonseThread( pthread_thandle; boolstarted; booldetached; voID*threadFunc(voID*); staticResPonseThread*m_pInstance; M_ADDCALLBACKEVENT(msg);//聊天回调函数 M_ADDCALLBACKEVENT(notcon);//断网回调函数 这个并不算一个完整的类,因为不同的命令对应的回调函数不一样 当然你也可以弄成一个回调函数,不过我喜欢把东西分开来写
实现代码
copy #include"BaseResponseMsg.h" ResPonseThread*ResPonseThread::m_pInstance=newResPonseThread; ResPonseThread*ResPonseThread::GetInstance(){ ResPonseThread::ResPonseThread(this->m_msgListener=NulL; started=detached=false; ResPonseThread::~ResPonseThread(voID) stop(); intResPonseThread::start(voID*param){ interrCode=0; do{ pthread_attr_tattributes; errCode=pthread_attr_init(&attributes); CC_BREAK_IF(errCode!=0); //但是上面这个函数其他内容则主要为你创建的线程设定为分离式 errCode=pthread_attr_setdetachstate(&attributes,PTHREAD_CREATE_DETACHED); if(errCode!=0){ pthread_attr_destroy(&attributes); break; errCode=pthread_create(&handle,&attributes,threadFunc,153); Font-weight:bold; background-color:inherit">this); started=true; voID*ResPonseThread::threadFunc(voID*arg){ ResPonseThread*thred=(ResPonseThread*)arg; ODSocketcsocket=SocketThread::GetInstance()->getSocket(); if(SocketThread::GetInstance()->state==0){ while(true){ //表示服务器端有消息推送过来 if(csocket.Select()==-2){ charrecvBuf[8];//获取请求头的数据 inti=csocket.Recv(recvBuf,8,0); if(i==8){ chardc1[2]={recvBuf[1],recvBuf[0]}; shortlen=*(short*)&dc1[0]; chardc2[2]={recvBuf[3],recvBuf[2]}; shortcode=*(short*)&dc2[0]; chardc3[4]={recvBuf[7],recvBuf[6],recvBuf[5],recvBuf[4]}; intplayID=*(int*)&dc3[0]; cclOG("%d",playID); char*messbody=NulL; intmyl=0; if(len>8){ myl=len-8; messbody=newchar[myl]; csocket.Recv(messbody,myl,0); ////1001=com.lx.command.player.LoginCmd //1002=com.lx.command.player.RegisterCmd //1003=com.lx.command.player.HeartBeatCmd //登陆 BaseResponseMsg*basmsg=newBaseResponseMsg(); basmsg->code=code; basmsg->len=len; basmsg->playerID=playID; //表示服务器推动过来的消息 if(code==1000){ if(thred->m_msgListener){ basmsg->setStringToMsg(messbody,myl); (thred->m_msgListener->*(thred->msgselector))(basmsg); else{ cclOG("%d",code); if(thred->m_notconListener){ basmsg->state=1;//连接断开 (thred->m_notconListener->*(thred->notconselector))(basmsg); voIDResPonseThread::stop(){ if(started&&!detached){ pthread_cancel(handle); pthread_detach(handle); detached=voID*ResPonseThread::wait(){ voID*status=NulL; if(started&&!detached){ pthread_join(handle,&status); returnstatus; voIDResPonseThread::sleep(intsecstr){ timevaltimeout={secstr/1000,secstr%1000}; select(0,NulL,&timeout); voIDResPonseThread::detach(){ pthread_detach(handle); }
当大家看到接收代码的时候或许很困惑。这里是引文大小端的问。还有前八个字节是我们规定好的,所以只要解析出前八个字节我们就知道服务器给传送过来的什么。 然后调用响应的回调 通知请求方即可。
下面贴出 一个消息体组装类
copy #include<string.h> #include"ConvertEndianUtil.h" structmessagehead{ shortlen; shortcode; intplayerID; }messagehead; template<typenameRquest>classBaseRequestMsg BaseRequestMsg( ~BaseRequestMsg(voIDsetRequestMessage(Rquestmessage);//设置请求体 voIDsetMessagehead(shortcode,87); Font-weight:bold; background-color:inherit">intplayer=0);//设置请求头 boolsendMessage();//发送信息 private: Rquestrequestmessage; messageheadmessagehead; char*getSendMessage(); shortdateLength; std::stringrequestMessage; }; typenameRquest> BaseRequestMsg<Rquest>::BaseRequestMsg(voID){ typenameRquest> BaseRequestMsg<Rquest>::~BaseRequestMsg(voID){ voIDBaseRequestMsg<Rquest>::setRequestMessage(Rquestmessage){ std::stringdata; message.SerializetoString(&data); this->requestMessage=data; voIDBaseRequestMsg<Rquest>::setMessagehead(intplayer){ messagehead.code=ConvertEndianUtil::convertEndianShort(code); messagehead.playerID=ConvertEndianUtil::convertForInt(player); char*BaseRequestMsg<Rquest>::getSendMessage(){ shorttotal=8+requestMessage.length(); dateLength=total; messagehead.len=ConvertEndianUtil::convertEndianShort(total); char*requestmessage=char[total]; char*requestmessagehead=(char*)&messagehead; inti=sizeof(messagehead); intlen=sizeof(requestMessage.c_str())/sizeof(char); memcpy(requestmessage,requestmessagehead,8); memcpy(&requestmessage[8],requestMessage.c_str(),requestMessage.length()); returnrequestmessage; boolBaseRequestMsg<Rquest>::sendMessage(){ ODSocketcSocket=SocketThread::GetInstance()->getSocket(); char*dd=this->getSendMessage(); intcout=cSocket.Send(this->getSendMessage(),this->dateLength,153); Font-weight:bold; background-color:inherit">if(cout==this->dateLength){ false; } 这里采用了C++ 类模板 这样可以让我们的程序更通用一点。 这个主要是针对protobuf 协议体的组装。
下面给大家贴上一段调用代码
copy BaseRequestMsg<zzboy::protobuf::ChatMsgReq>*baserlong=newBaseRequestMsg<zzboy::protobuf::ChatMsgReq>(); zzboy::protobuf::ChatMsgReqreq; req.set_msgtype(1); req.set_message("ddddd"); baserlong->setMessagehead((short)1000,(int)1); baserlong->setRequestMessage(req); baserlong->sendMessage(); 这就是一个发送消息的方法,哈哈看起来很简单吧。
这里我做的demo 是 聊天系统。在很多游戏里面都有聊天。这里就是一个简单的实现。不过整体的思路应该是这样
红色区域内 1 表示我自己说的话 4545 是别人说的话。其实只要服务器通知我 有人给我发送消息。都会在这里展示出来。
在这里我遇见一个BUG 就是 cclabelTTF* lable = cclabelTTF::create(tem,"Arial",24); 这个标签的创建 在线程的回调函数中我始终穿件不成功。导致我用了另外的一个办法解决。这里如果谁知道这个BUG 为什么 请私信给我活着留言给我。咱们共同进步
哈哈 写到这里网络连接着一块就完了,其实感觉也没什么,最重要的就是你解析过数据之后要干什么。大家发现BOY 是不是全才 服务器和客户端都会 。我感觉如果时间允许我自己可以做一个小型的多人在线网络游戏。 哈哈。
关于上面讲到的可能有些人还是不明白。可以留言给我或者在码农哥的群里给我说,如果我看到了都会给大家讲解。这两天这是忍着病给大家写的 有哪些写的不好请见谅。
客户端源码下载
总结以上是内存溢出为你收集整理的跟着BOY 学习COCOS2D-X 网络篇---强联网(采用技术 BSD SOCKET+多线程技术 +protobuf)客户端实战篇全部内容,希望文章能够帮你解决跟着BOY 学习COCOS2D-X 网络篇---强联网(采用技术 BSD SOCKET+多线程技术 +protobuf)客户端实战篇所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)