您的位置:首页 > 理论基础 > 计算机网络

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

2014-04-29 19:01 597 查看
转自:http://blog.csdn.net/zzhboy/article/details/9878941

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

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



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

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

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

[cpp] view
plaincopy

/*

* 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

对于这个类 我主要讲解四个方法 一个就是 Connect 这个方法 这个主要是用来连接的

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

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

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

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

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

[cpp] view
plaincopy

#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;

};

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

下面贴上实现

[cpp] view
plaincopy

#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;

}

}

对于多线程不是很熟悉的同学们可以在 百度 下相关的资料。只要实现了上面的类,我们就可以连接tong服务器了

下面贴出

接收线程类

[cpp] view
plaincopy

#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);//断网回调函数

};

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

实现代码

[cpp] view
plaincopy

#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;

}

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

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

[cpp] view
plaincopy

#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;

}

}

这里采用了C++ 类模板 这样可以让我们的程序更通用一点。 这个主要是针对protobuf 协议体的组装。

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

[cpp] view
plaincopy

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 是不是全才 服务器和客户端都会 。我感觉如果时间允许我自己可以做一个小型的多人在线网络游戏。 哈哈。

关于上面讲到的可能有些人还是不明白。可以留言给我或者在码农哥的群里给我说,如果我看到了都会给大家讲解。这两天这是忍着病给大家写的 有哪些写的不好请见谅。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: