转自:http://blog.csdn.net/zzhboy/article/details/9878941
下面我们就正式开始客户端的搭建 首先我献给大家画一张我的客户端实现的流程图
我PS 画的大家不要见怪啊 不过流程就是这样的
搭建看到我上面的框架图的时候 就知道我的大概设计思路,
boy 在这里强调一点 这个是用异步的结构实现 其中线程类 我是参照java 里面的方法。
好了废话不多 首先先上 BSD SOCKET 这个核心类
- /*
- * define file about portable socket class.
- * description:this sock is suit both windows and linux
- * design:odison
- * e-mail:odison@126.com>
- *
- */
- #ifndef _ODSOCKET_H_
- #define _ODSOCKET_H_
- #ifdef WIN32
- #include <winsock2.h>
- typedef int socklen_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>
- typedef int SOCKET;
- //#pragma region define win32 const variable in linux
- #define INVALID_SOCKET -1
- #define SOCKET_ERROR -1
- //#pragma endregion
- #endif
- class ODSocket {
- public:
- ODSocket(SOCKET sock = INVALID_SOCKET);
- ~ODSocket();
- // Create socket object for snd/recv data
- bool Create(int af, int type, int protocol = 0);
- // Connect socket
- bool Connect(const char* ip, unsigned short port);
- //#region server
- // Bind socket
- bool Bind(unsigned short port);
- // Listen socket
- bool Listen(int backlog = 5);
- // Accept socket
- bool Accept(ODSocket& s, char* fromip = NULL);
- //#endregion
- int Select();
- // Send socket
- int Send(const char* buf, int len, int flags = 0);
- // Recv socket
- int Recv(char* buf, int len, int flags = 0);
- // Close socket
- int Close();
- // Get errno
- int GetError();
- //#pragma region just for win32
- // Init winsock DLL
- static int Init();
- // Clean winsock DLL
- static int Clean();
- //#pragma endregion
- // Domain parse
- static bool DnsParse(const char* domain, char* ip);
- ODSocket& operator = (SOCKET s);
- operator SOCKET ();
- protected:
- SOCKET m_sock;
- fd_set fdR;
- };
- #endif
第二个 Send 方法 这个主要是用来发送数据的
第三个方法 Recv 这个主要是用来接收数据的、
第四个方法 Select 这个主要用来判断当前socket 的状态,这里我只用了 判断是否有数据回来这个方法。
这里说明一下 一旦调用recv 这个方法 他会一直等到读取到数据,所以在我们正常的开发游戏过程中。我们都会把它放到单独的线程中来执行。防止我们的主线程卡死。
好下面介绍一下我们的 连接线程类
- #pragma once
- #include "ODSocket.h"
- #include "pthread.h"
- class SocketThread
- {
- public:
- ~SocketThread(void);
- static SocketThread* GetInstance();
- int start();
- ODSocket getSocket();
- int state;// 0 表示连接成功 1 表示连接失败
- ODSocket csocket;
- void stop();//函数中止当前线程。
- private:
- pthread_t pid;
- static void* start_thread(void *);//静态成员函数,相当于C中的全局函数
- SocketThread(void);
- private:
- static SocketThread* m_pInstance;
- };
这个线程类是用来连接 我们的 服务器的 大家看到我上面的方法就可以才想到 我这个类是一个单利的模式。因为一个游戏中正常情况下只需要一个 套接字即可。所以我这个类采用了单利的模式可以获取一个套接字对象 。
下面贴上实现
- #include "SocketThread.h"
- #include "cocos2d.h"
- #include "ResPonseThread.h"
- USING_NS_CC;
- int SocketThread::start(){
- int errCode = 0;
- do{
- pthread_attr_t tAttr;
- 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);
- return errCode;
- }
- void* SocketThread::start_thread(void *arg) {
- SocketThread* thred=(SocketThread*)arg;
- ODSocket cdSocket;
- cdSocket.Init();
- bool isok=cdSocket.Create(AF_INET,SOCK_STREAM,0);
- bool iscon=cdSocket.Connect("127.0.0.1",8888);
- if(iscon){
- thred->state=0;
- ResPonseThread::GetInstance()->start();// 启动响应参数
- CCLOG("conection");
- }else{
- thred->state=1;
- }
- thred->csocket=cdSocket;
- return NULL;
- }
- ODSocket SocketThread::getSocket(){
- return this->csocket;
- }
- SocketThread* SocketThread::m_pInstance=new SocketThread;
- SocketThread* SocketThread::GetInstance(){
- return m_pInstance;
- }
- void SocketThread::stop(){
- pthread_cancel(pid);
- pthread_detach(pid);
- }
- SocketThread::SocketThread(void)
- {
- }
- SocketThread::~SocketThread(void)
- {
- if(m_pInstance!=NULL){
- delete m_pInstance;
- }
- }
下面贴出
接收线程类
- #pragma once
- // 此类主要 处理服务器推送过来的消息
- #include "pthread.h"
- #include "cocos2d.h"
- #include "BaseResponseMsg.h"
- typedef void (cocos2d::CCObject::*ResPonseThreadEvent)(BaseResponseMsg*);
- #define callFunc_selectormsg(_SELECTOR) (ResPonseThreadEvent)(&_SELECTOR)
- #define M_ADDCALLBACKEVENT(varName)\
- protected: cocos2d::CCObject* m_##varName##listener;ResPonseThreadEvent varName##selector;\
- public: void add##varName##ListenerEvent(ResPonseThreadEvent m_event,cocos2d::CCObject* listener) { m_##varName##listener=listener;varName##selector=m_event; }
- class ResPonseThread
- {
- public:
- ~ResPonseThread(void);
- static ResPonseThread* GetInstance(); // 获取该类的单利
- int start (void * =NULL); //函数是线程启动函数,其输入参数是无类型指针。
- void stop(); //函数中止当前线程。
- void sleep (int tesec); //函数让当前线程休眠给定时间,单位为毫秒秒。
- void detach(); //
- void * wait();
- private:
- ResPonseThread(void);
- pthread_t handle;
- bool started;
- bool detached;
- static void * threadFunc(void *);
- static ResPonseThread* m_pInstance;
- M_ADDCALLBACKEVENT(msg);// 聊天回调函数
- M_ADDCALLBACKEVENT(notcon);//断网回调函数
- };
这个并不算一个完整的类,因为不同的命令对应的回调函数不一样 当然你也可以弄成一个回调函数,不过我喜欢把东西分开来写
实现代码
- #include "ResPonseThread.h"
- #include "cocos2d.h"
- #include "SocketThread.h"
- #include "BaseResponseMsg.h"
- ResPonseThread* ResPonseThread::m_pInstance=new ResPonseThread;
- ResPonseThread* ResPonseThread::GetInstance(){
- return m_pInstance;
- }
- ResPonseThread::ResPonseThread(void)
- {
- this->m_msglistener=NULL;
- started = detached = false;
- }
- ResPonseThread::~ResPonseThread(void)
- {
- stop();
- }
- int ResPonseThread::start(void * param){
- int errCode = 0;
- do{
- pthread_attr_t attributes;
- 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,this);
- started = true;
- }while (0);
- return errCode;
- }
- void* ResPonseThread::threadFunc(void *arg){
- ResPonseThread* thred=(ResPonseThread*)arg;
- ODSocket csocket=SocketThread::GetInstance()->getSocket();
- if(SocketThread::GetInstance()->state==0){
- while(true){
- // 表示服务器端 有消息推送过来
- if(csocket.Select()==-2){
- char recvBuf[8];// 获取请求头的 数据
- int i= csocket.Recv(recvBuf,8,0);
- if (i==8){
- char dc1[2]={recvBuf[1],recvBuf[0]};
- short len = *(short*)&dc1[0];
- char dc2[2]={recvBuf[3],recvBuf[2]};
- short code = *(short*)&dc2[0];
- char dc3[4]={recvBuf[7],recvBuf[6],recvBuf[5],recvBuf[4]};
- int playId=*(int*)&dc3[0];
- CCLOG("%d",playId);
- char* messbody=NULL;
- int myl=0;
- if(len>8){
- myl=len-8;
- messbody=new char[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=new BaseResponseMsg();
- 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);
- }
- }else {
- if(thred->m_notconlistener){
- BaseResponseMsg* basmsg=new BaseResponseMsg();
- basmsg->state=1;// 连接断开
- (thred->m_notconlistener->*(thred->notconselector))(basmsg);
- }
- break;
- }
- }
- }
- }
- return NULL;
- }
- void ResPonseThread::stop(){
- if (started && !detached) {
- pthread_cancel(handle);
- pthread_detach(handle);
- detached = true;
- }
- }
- void * ResPonseThread::wait(){
- void * status = NULL;
- if (started && !detached) {
- pthread_join(handle, &status);
- }
- return status;
- }
- void ResPonseThread::sleep(int secstr){
- timeval timeout = { secstr/1000, secstr%1000};
- select(0, NULL, NULL, NULL, &timeout);
- }
- void ResPonseThread::detach(){
- if (started && !detached) {
- pthread_detach(handle);
- }
- detached = true;
- }
当大家看到接收代码的时候或许很困惑。这里是引文大小端的问。还有前八个字节是我们规定好的,所以只要解析出前八个字节我们就知道服务器给传送过来的什么。 然后调用响应的回调 通知请求方即可。
下面贴出 一个消息体组装类
- #pragma once
- #include <string.h>
- #include "ConvertEndianUtil.h"
- #include "ODSocket.h"
- #include "SocketThread.h"
- typedef struct messageHead{
- short len;
- short code;
- int playerid;
- } messagehead;
- template <typename Rquest> class BaseRequestMsg
- {
- public:
- BaseRequestMsg(void);
- ~BaseRequestMsg(void);
- void setRequestMessage(Rquest message);// 设置请求体
- void setMessageHead(short code,int player=0);// 设置 请求头
- bool sendMessage();// 发送信息
- private:
- Rquest requestmessage;
- messagehead messageHead;
- char* getSendMessage();
- short dateLength;
- std::string requestMessage;
- };
- template <typename Rquest>
- BaseRequestMsg<Rquest>::BaseRequestMsg(void){
- }
- template <typename Rquest>
- BaseRequestMsg<Rquest>::~BaseRequestMsg(void){
- }
- template <typename Rquest>
- void BaseRequestMsg<Rquest>::setRequestMessage(Rquest message){
- std::string data;
- message.SerializeToString(&data);
- this->requestMessage=data;
- }
- template <typename Rquest>
- void BaseRequestMsg<Rquest>::setMessageHead(short code,int player){
- messageHead.code=ConvertEndianUtil::convertEndianShort(code);
- messageHead.playerid=ConvertEndianUtil::convertForInt(player);
- }
- template <typename Rquest>
- char* BaseRequestMsg<Rquest>::getSendMessage(){
- short total=8+requestMessage.length();
- dateLength=total;
- messageHead.len=ConvertEndianUtil::convertEndianShort(total);
- char* requestmessage=new char[total];
- char* requestmessagehead=(char*)&messageHead;
- int i=sizeof(messageHead);
- int len=sizeof(requestMessage.c_str())/sizeof(char);
- memcpy(requestmessage,requestmessagehead,8);
- memcpy(&requestmessage[8],requestMessage.c_str(),requestMessage.length());
- return requestmessage;
- }
- template <typename Rquest>
- bool BaseRequestMsg<Rquest>::sendMessage(){
- ODSocket cSocket=SocketThread::GetInstance()->getSocket();
- char* dd=this->getSendMessage();
- int cout=cSocket.Send(this->getSendMessage(),this->dateLength,0);
- if(cout==this->dateLength){
- return true;
- }else {
- return false;
- }
- }
下面给大家贴上一段调用代码
- BaseRequestMsg<zzboy::protobuf::ChatMsgReq>* baserlong=new BaseRequestMsg<zzboy::protobuf::ChatMsgReq>();
- zzboy::protobuf::ChatMsgReq req;
- 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 是不是全才 服务器和客户端都会 。我感觉如果时间允许我自己可以做一个小型的多人在线网络游戏。 哈哈。
关于上面讲到的可能有些人还是不明白。可以留言给我或者在码农哥的群里给我说,如果我看到了都会给大家讲解。这两天这是忍着病给大家写的 有哪些写的不好请见谅。