如果按照上面的一講 你如果把環境搭建好了,下面我們就正式開始客戶端的搭建 首先我獻給大家畫一張我的客戶端實現的流程圖
我PS 畫的大家不要見怪啊 不過流程就是這樣的
搭建看到我上面的框架圖的時候 就知道我的大概設計思路,
boy 在這裏強調一點 這個是用異步的結構實現 其中線程類 我是參照java 裏面的方法。
好了廢話不多 首先先上 BSD SOCKET 這個核心類
/*
* define file about portable socket class.
* description:this sock is suit both windows and linux
* design:odison
* e-mail:[email protected]>
*
*/
#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 這個方法 他會一直等到讀取到數據,所以在我們正常的開發遊戲過程中。我們都會把它放到單獨的線程中來執行。防止我們的主線程卡死。
好下面介紹一下我們的 連接線程類
#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;
}
}
對於多線程不是很熟悉的同學們可以在 百度 下相關的資料。只要實現了上面的類,我們就可以連接tong服務器了
下面貼出
接收線程類
#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;
}
}
這裏採用了C++ 類模板 這樣可以讓我們的程序更通用一點。 這個主要是針對protobuf 協議體的組裝。
下面給大家貼上一段調用代碼
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 是不是全才 服務器和客戶端都會 。我感覺如果時間允許我自己可以做一個小型的多人在線網絡遊戲。 哈哈。
關於上面講到的可能有些人還是不明白。可以留言給我或者在碼農哥的羣裏給我說,如果我看到了都會給大家講解。這兩天這是忍着病給大家寫的 有哪些寫的不好請見諒。