1. 程式人生 > >網路遊戲中訊息包結構

網路遊戲中訊息包結構

一 

NetPacket:

NetPacket(int buffersize,NetPacketPool* pool,bool scal=true,bool policy=false)
{
//建構函式,
if(buffersize<7)
buffersize = 7;//訊息包最小7位元組 1+2+4 type,proc,size
m_buffer = new Char[2048];//預設2K
m_buffer += 7; //指向訊息體
m_bufferSize = buffersize-7;//有效訊息大小,訊息包總大小
m_pool = pool;//這裡傳進池子指標,為了後面回收訊息包使用 
m_scalable = scal;//預設可擴充套件,如果比如寫入大小超過2K了,那麼需要增加這包的大小,重新申請大空間
m_policy = policy;//不管、
reset();//一些成員變數初始化一下


}
void reset()
{
m_rOffset = 0;//讀偏移0 起點
m_wOffset = 0;//寫偏移0 起點
m_rOverFlow = false;
m_wOverFlow = false; //這兩個表示讀寫偏移是否超過了訊息包有效長度
m_proc = 0;//協議號,
m_size = 0;//訊息包總大小
}
readInt readDouble,writeInt writeDouble等都使用模板方法
template<typename T>
T read()
{
if(m_rOverFlow)
return T(0);//訊息包已經讀完了,後面的資料肯定是亂的不能再讀啦
if(m_rOffset + sizeof(T) > m_wOffset)
{
//說明不夠讀了,寫偏移比如10,現在讀偏移是8,你要讀取int,肯定是不可以的,讀了就出錯
m_rOverFlow = true;//丟掉資料也不能讀 正常情況不會出現,出現肯定哪裡程式碼有問題了,比如資料解析不對
return T(0);
}
m_rOffset += sizeof(T);//走到這裡說明這次讀取是沒有問題的,讀偏移後移
return *((T*)(m_buffer+m_rOffset-sizeof(T)));//返回對應型別的有效資料,
//m_buffer是訊息體的起始位置,加上讀的偏移,減去剛才的,就是這次需要讀的資料地址的起點,
//括號裡面指標型別轉化下,否則知道位置,但不知道我該取到哪裡為止,
//比如你是int,那麼我就從這裡取4位元組
//因此轉化成int* 的指標,然後通過指標取,就能取到正確的int資料
}
template<typename T>
T write(T val)
{
if(m_wOverFlow)
return;
if(m_wOffset+sizeof(T)>m_buffersize)
{
//寫偏移再加上這次要寫的大小,超過了訊息包的大小(2k),不行了
if(m_scal)
{
//如果訊息包是彈性包,支援擴容那麼我重新申請大空間
char* buffer = new char[m_buffersize*2+7];//每次空間不夠申請當前大小的2倍,類似vector
memcpy(buffer,m_buffer-7,m_buffersize+7);//m_buffer指向訊息體,-7指向訊息頭部,拷貝整個訊息包的內容到新的記憶體
//buffer現在所指向的位置之後就是原來的訊息,
m_buffer -= 7;
//因此m_buffer-7指標所指的那片空間可以釋放了(訊息頭+訊息體)
delete[] m_buffer;
m_buffer = buffer+7;//釋放完後,把m_buffer重新指向新記憶體空間裡面的訊息體
m_buffersize = m_buffersize*2;//新的訊息體可用大小
}
if(m_wOffset+sizeof(T)>m_buffersize)
{
//重新申請後還是不夠,那麼就退出吧,不要寫了
m_wOverFlow = true;
return;
}
}
//這裡說明可以寫,那就找到些偏移的位置,對應型別的空間,寫入值,
*((T*)(m_buffer+m_wOverFlow)) = val;
//寫偏移後移
m_wOverFlow += sizeof(T);
}




{
頭:1+2+4,
char* m_buffer,訊息體位置,訊息頭往後7位元組
buffersize 訊息包總大小包括頭和尾
m_buffersize 訊息體大小 m_buffersize = buffersize - 7;

}
void destroy()
{
if(m_pool!=null)
m_pool->push(this);//訊息池見下面
else
delete this;
}


當然上面只是一個簡單的網路訊息包的構成,以及讀寫,
遊戲中需要發包和收包當然不能 需要了就new一個,用完之後delete
很耗費時間,而且很多記憶體碎片,需要頻繁的使用記憶體的都可以用池子
簡單的NetPacketPool,如下

實際上池子存的不是訊息包,而是一組訊息包的記憶體地址,就是開闢了一對空間,然後把鑰匙全部放在池子裡
池子裡放的是這些鑰匙,不是空間

//SafeQueue:具體暫時不管,它就是一個自己會處理好互斥鎖的一個容器,
//外部訪問的時候不用考慮多執行緒同步的問題
typedef SafeQueue<NetPacket*> NetPacketList;
NetPacketList m_pool; //池子

建構函式
NetPacketPool(int size,int initcount,int maxcount)
:m_bufferSize(size)//池子裡面的訊息包的大小,預設2K
,m_initCount(initcount)//池子初始大小
,m_maxCount(maxcount)//池子最大存多少個訊息包
{
for(int 9=0;i<m_initCount;i++)
{
NetPacket* netpacket = new NetPacket(m_bufferSize+7,this);//NetPacket的建構函式請看上面
m_pool.push(netpacket);//m_pool才是這個池子
}
}
~NetPacketPool()
{
//析構當然就是用池子裡面的鑰匙,拿了鑰匙把佔的記憶體都釋放掉,空間重回系統的懷抱
NetPacket* pack = null;
while((packet=m_pool.pop())!=NULL)
{
delete pack;
pack = null;
}
}
NetPacket* pop()
{
//有了池子,外部使用需要NetPakcet的時候就不用自己造一個了,池子裡面取就行了
NetPacket* pack = m_pool.pop();
if(pack == null)
{
//空的,沒取到,有可能,可能初始的不夠,或者需要的太多,不夠用了
pack = new NetPacket(m_bufferSize+7,this);//不夠就new 不會空手而歸
}
pack->reset();//重置一下,畢竟池子誰都用過,裡面可能還是之前的資料
return pack;
}
void push(NetPacket* pack)
{
//用完之後,自己不要delete 而是放回池子裡面,其他地方還可以再用
if(pack != NULL && pack->subRefCount() == 0)
{
//pack不為空,而且現在沒人用,引用計數,
//否則回收了指向的空間,其他地方還在用這個指標,程式崩潰
if(m_pool.size()>m_maxCount)
{
//比如池子最大2000,但是很多地方用,當時pop的時候不夠用可能new了很多
//如果我都往裡面放,那麼池子可能非常大,幾千幾萬,那就非常耗費記憶體
delete pack;
pack=null;//所以直接清掉,不放回池子
}
else
{
m_pool.push(pack);//正常範圍內,塞進池子
}
}
}


簡單的池子就是這樣,下面簡單的使用場景

例如GameServiceManager,裡面
成員變數 NetPacketPool m_bufferPool;
m_bufferPool(2048-7,0,2000);
參照上面的NetPacketPool的建構函式,
這個池子裡面訊息包的長度都是2k,池子初始是空的,池子最多放2000個訊息包
當我需要製造一個NetPacket的時候
那麼
NetPacket* packet = m_bufferPool.pop();
if(packet!=null)
{
packet->setType(packType);
packet->setProc(proc);
packet->writeInt(xxx);
packet->writeDouble(xxx);
packet->setSize(packet->getWOffset());
gameServer->sendPacket();//Tcp發訊息
packet->destroy();

}