本人不是专业的服务器程序员,所写的文章难免错漏,仅供参考,欢迎指正。
        游戏中用到的网络编程多数是TCP连接,比如RPG类游戏都需要建立一个可靠的长连接来不停收发消息。有的游戏比如ACT类需要低延迟的游戏可能用的是P2P连接。还有一部分局域网对战类游戏会用到UDP广播来发送和同步局域网内的部分游戏主机信息。

1.封包处理

不管那种类型的网络连接首先要处理的都是消息的封包。
        TCP消息的传输是可靠并且有序的,但是传输的过程中可能出现合并包和断包,因此消息的封包格式中必须包含一个字段用来保存包的长度。

一般windows上服务器可能是基于iocp,消息包设定了最大长度,大包是需要手动拆分的,比如发送商城物品列表时一次只发送一部分,并添加一个发送状态字段。

参见附录源码
PacketBase 是消息包的基类:
PacketBase::WriteHeader(); 发送时自动写入头部,消息包的前三个int字段分别是checksum&ID、size、debugID。 
PacketBase::WriteValue();  一系列函数用于向待发送缓冲写入数据
PacketBase::Read......();  一系列函数用于读取包中的数据

PacketQueue 是带锁的消息包队列

发送消息的包分配在栈上,不会产生内存碎片无需内存池,接收消息的包需要使用内存池,这一点还未确定也没实现,留个坑不填了。

TcpStream 用于保存断包数据,等待接收到后续数据再拼接成完整包。关于断包处理类TcpStream一直有个疑惑,是一个接收线程对应一个TcpStream 还是一个socket对应一个TcpStream, 咨询了很多服务器开发人员,网上到处查资料也没有一个肯定的答案。纳闷有些小游戏服务器直接没有处理断包的代码也能玩。 个人倾向于一个socket对应一个TcpStream,但是并不用为每个连接的socket分配一个TcpStream,这样太浪费空间(因为断包可能出现在任意位置所以TcpStream要设置为最大封包长度)。 我们可以建立一个TcpStream的池子,某个socket需要的时候到池子中申请。暂时测试下来池子中TcpStream个数不会很大。

2.完成端口IOCP

做了一个带界面的服务器,很多服务器的程序都是不带界面的,甚至连黑框控制台都没有,可能是出于防止误操作考虑吧,效率上应该没啥影响。大约有几个要注意的地方

windows api向界面控制台打印输出时要注意必须在主线程打印,否则是无效的,所以添加了子线程打印函数LogSub。
        玩家在登录完成之前发送的某些游戏内消息是直接丢弃的。
        如果客户端卡死到达一段时间,则服务器不再给其发消息,否则会存在很多write overlapped,会占用很多发送缓冲,必要时要主动关闭该客户端,此时占用的write overlapped会被释放。
        客户端关闭或断开连接时,服务器并不是总能收到通知,大约是四次挥手过程没有完成。有时客户端断开连接服务器会反复收到长度为0的消息,这时也需要主动关闭该客户端连接。可能心跳包检测是必须的。

测试服务器:凑合着先用,就一个进程,没做分布式 ,界面是win32api做的很简易。

封包代码:

//========================================================
//  @Date:     2016.05
//  @File:     Include/Net/PacketList.h
//  @Brief:     PacketList
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once#ifndef  __PacketList__H__
#define  __PacketList__H__typedef void* PSocket;
#define Ptr2Socket(socket)  (*((SOCKET*)socket))
#define Socket2Ptr(socket)  (&socket)
void*   CreateSocket();
void    FreeSocket(void*& sokcet);#include "General/Singleton.h"
#include "General/Thread.h"
//#include "General/ObjectPool.h"//!减小缓冲使占用内存减少 1000player*2packet/per player = 2M级别
#define MaxPacketSize   1024*10
#define MaxValueLength  512typedef short int Short;enum PacketGrabRes
{PR_ErrorStream,   //PR_ErrorChecksum, //PR_Complete,      //!包接收完成PR_Incomplete,    //!只收到一半的包流PR_EmptyStream,   //!空流
};class OverlappedCustumRecv;//!tcp是顺序接收数据,但可能出现合并包、分包的情况, 发送端发送A,B,C,D四个包,协议栈可能会发送A,BC,D,也就是把BC合成一个包发出去。
//!TCP协议栈在收到一个包的时候会同时计算下一个包的sequenceID,nextSequenceID = sequenceID + sizeof(currentPackage); 可以检测到包丢失
class PacketBase
{friend class PacketQueue;friend class NetClient;friend class NetRobotsClient;friend class NetRobot;friend class NetServerIocp;friend class NetPointUdp;friend class PacketTemplateLib;friend class InnerClient;friend class InnerServer;
public:PacketBase(int packetID);virtual ~PacketBase();virtual  PacketBase* Alloc();virtual void operator=(PacketBase* other){}int      GetPacketID();void*   GetParm();int     GetBufferSize();char*   GetBuffer();virtual bool PreProcess();virtual bool Process();virtual void ToBuffer();virtual void FromBuffer(void* syncGameInfo=NULL);void SeekPos(int pos);//!字段操作void ReadHeader();void ReadArray(void*value,int size);template<class T>void ReadValue(T& value);void ReadString(char* value,int size);template <int size> void ReadString(char(&value)[size]);void WriteHeader();void WriteArray(const void* value,int size);template<class T>void WriteValue(const T& value);void WriteString(const char* value);//!准备发送:加校验 加密等void PrepareSend();//!直接复制转发void CopyPacket(PacketBase* packet);//!添加错误码m_res+复制转发void CopyPacketRes(PacketBase* packet);public://!buf前两个int分别是checksum&ID 和size,接收到包中会解除校验和static PacketGrabRes GenPacketFromBuf(PacketBase*& packet, const char* buf, int &readSsize, int streamSize, PSocket fromSocket);static void PushInTemplate(PacketBase* packet);double GetAccumTime();protected://!大包需要手动拆分char  m_buffer[MaxPacketSize];int   m_curPos;//为了跨平台,不包含平台相关文件。//如果使用外部socket指针,需要外部保证socket有效性。//断开连接时,socket被先一步delete不好保证有效性(packet的处理要放在后面的主线程)。所以此处指向自己new出来的socket,自己负责delete。PSocket m_fromSocket;void*   m_parm;   //比如owner robot指针//重叠字中可以保存 player指针,省去每个消息的map查找,提高效率OverlappedCustumRecv* m_overlapedRecv;public:int   m_packetID;int   m_streamSize;//int   m_res;    //错误码static int HeadSize; //消息的前三个int分别是checksum&ID、size、debugID #define DebugPacketID
//#ifdef DebugPacketIDunsigned int m_debugID;  static unsigned int DebugSendID; static unsigned int DebugRecvID; //#endifstatic char PacketLogFlag[1024*10];//static PacketBase* PacketTemplate[1024*10];
};template<class T>
void PacketBase::WriteValue(const T& value)
{int lenval=sizeof(T);if(lenval>MaxValueLength)lenval=MaxValueLength;memcpy(m_buffer+m_curPos,&value,lenval);m_curPos+=lenval;m_streamSize=m_curPos;
}template<class T>
void PacketBase::ReadValue(T& value)
{int lenval=sizeof(T);if(lenval>MaxValueLength)lenval=MaxValueLength;memcpy(&value,m_buffer+m_curPos,lenval);m_curPos+=lenval;
}template <int size>
void PacketBase::ReadString(char(&value)[size])
{int lenstr=0;ReadValue(lenstr);if(lenstr>MaxValueLength)lenstr=MaxValueLength;if(lenstr>=size){memcpy(value, m_buffer+m_curPos,size-1);value[size-1]='\0';}else{memcpy(value, m_buffer+m_curPos,lenstr);value[lenstr]='\0';}m_curPos+=lenstr;
} template<class T>
class PacketTemplateRegister
{
public:PacketTemplateRegister(){//!确保全局变量构造顺序PacketBase::PushInTemplate(new T);}void Space(){}
};//
template<class T>
class PacketVisual:public PacketBase
{
public:PacketVisual(int packetID):PacketBase(packetID){}virtual PacketBase* Alloc(){return new T;}virtual void operator=(PacketBase* other){T* this_  = dynamic_cast<T*>(this);T* other_ = dynamic_cast<T*>(other);if (this_ && other_){PSocket oldSocket = m_fromSocket;//char*   oldBuffer = m_buffer;(*this_) = (*other_);//深拷贝 恢复指针指向Ptr2Socket(oldSocket) = Ptr2Socket(m_fromSocket);m_fromSocket = oldSocket;//memcpy(oldBuffer,m_buffer,m_bufferMax);//m_buffer = oldBuffer;}}//有的消息可能根据code写入不同的数据,或者list类消息数据不在结构体内,或者带有变长string、指针等,需要重写下面两个函数。virtual void ToBuffer(){WriteHeader();//copy packet T中的数据段,sizeof(T)-4-baseMemberWriteArray(this+sizeof(PacketBase),sizeof(T) - sizeof(PacketBase));}virtual void FromBuffer(void* syncGameInfo=NULL){ReadHeader();ReadArray(this+sizeof(PacketBase),sizeof(T) - sizeof(PacketBase));}protected://!利用虚函数必须被实现,使得静态成员被链接进来,//!否则静态成员constructor不被链接,PacketTemplateRegister构造函数的断点都无法加virtual void Space(){constructor.Space();}static PacketTemplateRegister<T> constructor;//todo //send packet是栈数据 不会产生内存碎片 无需池子   //recv packet修改为  PacketT p; p.Parse(packetBase& b); 这样p为临时栈对象  b可以统一放在一个池子里 只需一把锁  (?p未知类型 无法定义临时变量?)//!对象池, 一千消息就需要一千个池子,一千把锁??//ObjectPool<T>//PacketBase//客户端发包限制
#ifdef CLIENT_APP
public:static bool IsReady();static bool   m_waitingPacket;//某些消息在没收到反馈前不能多发
protected:static double m_sendPeriod;   //时间即时消息,一帧只能一次移动,捡取,技能等? 其它消息0.1秒间隔。? 列表1秒static double m_nextSendTime;
#endif};template<class T>
PacketTemplateRegister<T> PacketVisual<T>::constructor;
#ifdef CLIENT_APP
template<class T> double PacketVisual<T>::m_waitingPacket = false;
template<class T> double PacketVisual<T>::m_sendPeriod = 0.033;
template<class T> double PacketVisual<T>::m_nextSendTime = 0;
template<class T> bool   PacketVisual<T>::IsReady()
{if (IsZero(m_sendPeriod)) return true;if (m_sendPeriod < 0) return false;double curTime = GetAccumTime();if (curTime >= m_nextSendTime){m_nextSendTime = curTime + m_sendPeriod;return true;}
}
#endifclass PacketQueue
{friend class NetServerIocp;
public:PacketQueue();~PacketQueue();int         GetSize();void      DestroyList();PacketBase* Pop();bool        Push(PacketBase *packet );CriticalSection* GetCriticalSection();static const char* classname(){return "PacketQueue";}private:CriticalSection  m_criticalSection;void*               m_listPackets;};#if (defined SERVER_APP) || (defined INNERSERVER_APP)
#define ServerProcess virtual bool Process();
#else
#define ServerProcess
#endif#ifdef CLIENT_APP
#define ClientProcess virtual bool Process();
#else
#define ClientProcess
#endif//应用层实现
const char* ProtocolToString(int enumeration);class TcpStream
{
public:TcpStream();~TcpStream();int  RecvStream( PSocket socket, char* buffer, int bufferSize, OverlappedCustumRecv* overlappedRecv);PacketGrabRes GrabPacket(PacketBase*& packet);//!(使用缓冲的时候也可能收到断包)//!前面有MaxPacketSize-1大小的断包时加断点可能导致,又收到MaxPacketSize大小的流,所以缓冲大小MaxPacketSize*2。char     m_buffer[MaxPacketSize*2];int      m_incompleteStreamSize;int      streamSize;char*    curPos;PSocket  m_socket;OverlappedCustumRecv* m_overlappedRecv;static int ErrorPacketNum;
private:};#endif //========================================================
//  @Date:     2016.05
//  @File:     SourceLib/Net/PacketList.cpp
//  @Brief:     PacketList
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================
#include "General/Pch.h"#ifdef WIN32APP
#include <winsock2.h>
#include <windows.h>
#include <windef.h>
typedef SOCKET HSocket;
#else
typedef unsigned int HSocket;
#endif #include "General/AES.h"
#include "Net/PacketList.h"
#include <list>
#include <map>
#include "General/Timer.h"
#include "General/Pce.h"void* CreateSocket()
{HSocket* socket = new HSocket;//Ptr2Socket(socket) = 7189;return socket;
}void  FreeSocket(void*& socket_)
{HSocket*socket = (HSocket*)socket_;SafeDelete(socket);socket_ = NULL;
}//<PacketID,PacketBase*>
typedef std::map<int,PacketBase*> PACKETTEMPLATEMAP;//全局对象m_mapPacketTemplate构造时间不能晚于第一次使用(PacketTemplateRegister构造调用PushInTemplate时使用)
//不定义为指针将难以控制两个全局变量的构造顺序
//static PACKETTEMPLATEMAP m_mapPacketTemplate = PACKETTEMPLATEMAP();
PACKETTEMPLATEMAP* m_mapPacketTemplate = NULL;//new PACKETTEMPLATEMAP();
//static PacketBase* PacketTemplate[1024*10];
char PacketBase::PacketLogFlag[1024*10];class PacketTemplateGarbage
{
public:~PacketTemplateGarbage(){for(PACKETTEMPLATEMAP::iterator it=m_mapPacketTemplate->begin();it!=m_mapPacketTemplate->end();++it){delete (it->second);}m_mapPacketTemplate->clear();delete(m_mapPacketTemplate);m_mapPacketTemplate = NULL;}
};static PacketTemplateGarbage __PacketTemplateGarbage;#ifdef DebugPacketID
int PacketBase::HeadSize = 12;
#else
int PacketBase::HeadSize = 8;
#endifunsigned int PacketBase::DebugSendID = 0;
unsigned int PacketBase::DebugRecvID = 0;
PacketBase::PacketBase(int packetID)
:m_overlapedRecv(NULL)
{m_packetID = packetID;m_streamSize = 0;m_curPos = 0;m_res = 0;m_parm = NULL;m_debugID = 0;m_buffer[0] = 0;m_fromSocket = CreateSocket();
}PacketBase::~PacketBase()
{SafeDelete(m_fromSocket);
}PacketGrabRes  PacketBase::GenPacketFromBuf(PacketBase*& packet, const char* buf, int &readSsize, int streamSize, PSocket fromSocket)
{//todo 缓冲池子packet = NULL;if (buf==NULL||streamSize<=0){return PR_EmptyStream;}if (streamSize<HeadSize){//不够头部 可能是正常断包//return PR_ErrorStream;return PR_Incomplete;}//消息的前三个int分别是checksum&ID、size、debugID int *pIntBuffer = (int *)buf;int size = pIntBuffer[1];int packetID = (pIntBuffer[0]&0x0000ffff);//if ((*m_mapPacketTemplate).find(packetID)==(*m_mapPacketTemplate).end()) {packet = NULL;return PR_ErrorStream;}if (size <HeadSize/*= 0*/ || size > MaxPacketSize){return PR_ErrorStream;}if (size > streamSize){//等待流的重组return PR_Incomplete;}//else if (size < streamSize)//{可能是正常粘包//}//收到完整包后才能校验short int checksum = (pIntBuffer[0]&0xffff0000)>>16;pIntBuffer[0] &= 0x0000ffff; //校验前先把校验和清掉bool bRight = false;IsCheckSumRightFun(checksum,buf,size,bRight);if (bRight==false){packet = NULL;return PR_ErrorChecksum;}{packet = ((*m_mapPacketTemplate)[packetID])->Alloc();//包含消息的前三个int checksum&ID、size、debugID //packet->m_buffer = new char[size];memcpy( packet->m_buffer, buf, size );packet->m_streamSize = size;packet->m_packetID = packetID;//packet->m_fromSocket = fromSocket;if(fromSocket){Ptr2Socket(packet->m_fromSocket) = Ptr2Socket(fromSocket);}//若解析错误,则下一个packetID基本上是错误的//挪到主线程,list线程不安全//packet->FromBuffer(syncGameInfo);readSsize = size;}return PR_Complete;
}void PacketBase::PushInTemplate(PacketBase* packet)
{if (!m_mapPacketTemplate){m_mapPacketTemplate = new PACKETTEMPLATEMAP();}//map查找效率一万次1ms,10000人*10个消息=10ms消耗较大? 用数组可以忽略?PACKETTEMPLATEMAP& paks = (*m_mapPacketTemplate);if (paks.find(packet->m_packetID) == paks.end()){paks[packet->m_packetID] = packet;//Clone();}else{MsgBoxFormat("There is another package that use the same ID!");}}bool PacketBase::PreProcess()
{return true;
}bool PacketBase::Process()
{return true;
}
void PacketBase::ToBuffer()
{WriteHeader();
}
void PacketBase::FromBuffer(void* syncGameInfo)
{ReadHeader();
}
char* PacketBase::GetBuffer()
{return m_buffer;
}int  PacketBase::GetBufferSize()
{return m_streamSize;
}int  PacketBase::GetPacketID()
{return m_packetID;
}PacketBase* PacketBase::Alloc()
{return NULL;
}void PacketBase::WriteString(const char* value)
{int lenstr=strlen(value);if(lenstr>MaxValueLength)lenstr=MaxValueLength;if(lenstr<0){MsgBoxFormat("PacketWriteString len <0 string" );return;}if (m_streamSize+sizeof(int)+lenstr>MaxPacketSize){MsgBoxFormat("PacketWriteString buffsize overflow");return;}WriteValue(lenstr);WriteArray(value,lenstr);m_streamSize=m_curPos;
}void PacketBase::WriteArray(const void* value,int size)
{int lenarr=size;if(size<0){MsgBoxFormat("PacketAddArray len <0 " );return;}if (m_streamSize+sizeof(int)+lenarr>MaxPacketSize){MsgBoxFormat("PacketAddArray buffsize overflow");return;}/*if(lenarr>MaxValueLength){MsgBoxFormat("PacketAddArray arr too big");lenarr=MaxValueLength;return;}*/memcpy(m_buffer+m_curPos,value, lenarr);m_curPos+=lenarr;m_streamSize=m_curPos;
}//避免缓冲溢出
void PacketBase::ReadString(char*value,int size)
{int lenstr=0;ReadValue(lenstr);if(lenstr>MaxValueLength || lenstr>MaxPacketSize-m_curPos){MsgBoxFormat("PacketGetString too long string" );return;}if(lenstr<0){MsgBoxFormat("PacketGetString len <0 string" );return;}if(lenstr>=size){memcpy(value, m_buffer+m_curPos,size-1);value[size-1]='\0';}else{memcpy(value, m_buffer+m_curPos,lenstr);value[lenstr]='\0';}m_curPos+=lenstr;
}void PacketBase::ReadArray(void*value,int size)
{int lenarr=size;if(lenarr<0){MsgBoxFormat("PacketReadArray len <0 " );return;}if(/*lenarr>MaxValueLength||*/ lenarr>MaxPacketSize-m_curPos){MsgBoxFormat("PacketReadArray too long arr" );lenarr=MaxValueLength;return;}memcpy(value, m_buffer+m_curPos,lenarr);m_curPos+=lenarr;
}void PacketBase::WriteHeader()
{//消息的前三个int分别是checksum&ID、size、debugID int * head = (int *)this->m_buffer;*head = m_packetID;//*(int *)(m_buffer+sizeof(int)) = buffreSize;m_curPos = HeadSize;m_streamSize=m_curPos;
}void PacketBase::ReadHeader()
{//消息的前三个int分别是checksum&ID、size、debugID int * head = (int *)this->m_buffer;//*(int *)m_buffer = m_packetID;//*(int *)(m_buffer+sizeof(int)) = buffreSize;m_curPos = HeadSize;
#ifdef DebugPacketIDm_debugID = *(head+2);
#endif
}void* PacketBase::GetParm()
{return m_parm;
}void PacketBase::CopyPacket(PacketBase* packet)
{//sprintf(m_buffer,packet->m_buffer);memcpy(m_buffer,packet->m_buffer,packet->m_streamSize);//消息的前三个int分别是checksum&ID、size、debugID *(int *)m_buffer = m_packetID;m_streamSize = packet->m_streamSize;m_curPos = m_streamSize;
}void PacketBase::CopyPacketRes(PacketBase* packet)
{int StreamSize = packet->m_streamSize+4;//sprintf(m_buffer,packet->m_buffer);memcpy(m_buffer+4,packet->m_buffer,packet->m_streamSize);//消息的前三个int分别是checksum&ID、size、debugID *(int *)m_buffer = m_packetID;m_streamSize = StreamSize;m_curPos = m_streamSize;int* res = (int *)(m_buffer+HeadSize);//头部后第一个字段为m_res*res = m_res;
}void PacketBase::PrepareSend()
{this->ToBuffer();//消息的前三个int分别是checksum&ID、size、debugID //统一填写长度int * head = (int *)this->m_buffer;//*head = m_packetID;*(head+1) = this->m_streamSize;m_debugID = DebugSendID++;
#ifdef DebugPacketID*(head+2) = m_debugID;
#endifshort int checksum;GetCheckSumFun(checksum,this->m_buffer,this->m_streamSize);*(int *)(this->m_buffer) += (checksum<<16);
}double PacketBase::GetAccumTime()
{return G_Timer->GetAccumTime();
}void PacketBase::SeekPos(int pos)
{m_curPos = pos;
}//==================^_^==================^_^==================^_^==================^_^
typedef std::list<PacketBase*>  PacketsList;
PacketQueue::PacketQueue()
{m_listPackets = new PacketsList;
}PacketQueue::~PacketQueue()
{DestroyList();if (m_listPackets){delete( (PacketsList*)m_listPackets );m_listPackets = NULL;}
}void PacketQueue::DestroyList()
{while (GetSize()) {delete Pop();}
}PacketBase *PacketQueue::Pop()
{if (((PacketsList*)m_listPackets)->empty()) return NULL;PacketBase * temp = ((PacketsList*)m_listPackets)->back();((PacketsList*)m_listPackets)->pop_back();return temp;
}bool PacketQueue::Push( PacketBase *packet )
{((PacketsList*)m_listPackets)->push_front(packet);return true;
}int  PacketQueue::GetSize()
{return (int)((PacketsList*)m_listPackets)->size();
}CriticalSection* PacketQueue::GetCriticalSection()
{return &m_criticalSection;
}int TcpStream::ErrorPacketNum = 0;
TcpStream::TcpStream()
:m_incompleteStreamSize(0)
,m_overlappedRecv(NULL)
{
}TcpStream::~TcpStream()
{
}int  TcpStream::RecvStream(PSocket socket, char* buffer, int bufferSize, OverlappedCustumRecv* overlappedRecv)
{if (buffer==NULL||bufferSize<=0){return 0;}//默认没有缓存流streamSize = bufferSize;curPos = buffer;//有缓存断包流if(m_incompleteStreamSize > 0){//这里基本走不到,除了大包或高并发?未经测试//重定向//新收到的拼接在缓存后面,已保证缓冲流前面不被取空memcpy(m_buffer+m_incompleteStreamSize,curPos,streamSize);streamSize += m_incompleteStreamSize;curPos = m_buffer;if (m_socket!=socket){//assert(0);}}else{//不拷贝,提高速度}m_socket = socket;m_overlappedRecv = overlappedRecv;return 1;
}PacketGrabRes TcpStream::GrabPacket(PacketBase*& packet)
{//网络层ip协议保证了包的完整性?如果收到某个socketA的半个包,中间不会插入其它socketB的包,直至该socketA剩下的半个包接收完毕,这样就阻塞了。//网络层为了保证包的完整性,必然限制最大包(约1.5k),向下根据数据链路层的MTU(最大传输单元)还会分割(?传输层已经分割)。//将大包(比如上传文件)在应用层拆成独立packet小包,每个包都带有packet包头。类似商城道具列表,收到一段即刻保存或转发一段。//一般少量消息不拥堵缓冲时(即时发送且包少)不会分包,一个大消息超出缓冲直接发送失败?if ( curPos==NULL || streamSize <= 0) {return PR_EmptyStream;}//粘包分包问题:可能合并了2.5个包,最后一个包被分拆int readSize = 0;PacketGrabRes res = PacketBase::GenPacketFromBuf(packet, curPos, readSize, streamSize,m_socket);if (res==PR_Complete){//拆出一个完整包curPos += readSize;streamSize -= readSize;//仅最后一个包m_incompleteStreamSize=0有效,否则会被重置m_incompleteStreamSize = 0;//packet->m_overlapedRecv = m_overlapedRecv;//断开连接时,overlapped为野指针?}else if (res==PR_Incomplete){//缓冲满 正常情况//这里基本走不到,除了大包或高并发导致传输层缓存累积?因为网络层保证了一次包的完整性。//当前流有剩余 ,将剩余流拷贝到buff,等待拼接m_incompleteStreamSize = streamSize;if(curPos!=m_buffer){//1 m_buffer   头部被取空了一部分//2 recvBuffer 头部被取空了一部分memmove(m_buffer,curPos,streamSize);}else{//本来就是断包,且本次未取空头部}}else if (res==PR_EmptyStream){}else if (res==PR_ErrorChecksum){//校验错误m_incompleteStreamSize = 0;ErrorPacketNum++;}else if (res==PR_ErrorStream){//包解析失败,可能导致后面所有包都解析出错。//清空缓冲,等待到下一个正确包,直到下一段流没跨缓冲区,是正常包头,则接收恢复正常。m_incompleteStreamSize = 0;ErrorPacketNum++;}else{//assert(0);}return res;
}

IOCP代码:

//========================================================
//  @Date:     2016.05
//  @File:     Include/Net/NetServerIocp.h
//  @Brief:     NetServerIocp
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once#ifndef  __NetServerIocp__H__
#define  __NetServerIocp__H__
#include "General/singleton.h"
#include "Net/InnerServer.h"//!服务器只做win32平台
#if (defined WIN32APP) && (defined SERVER_APP)
typedef void* PSocket;#include "General/Thread.h"class OverlappedCustum;
class OverlappedCustumRecv;
class PacketQueue;
class PacketBase;
class TcpStream;#define G_Server      NetServerIocp::GetSingleton()typedef void (*OutPutFun)(const char *msg);//!服务器放在单独的进程,同样可以把地址广播到局域网
//!即使放在主机客户端同一进程,收到消息也需主线程异步处理,已经延迟单帧时间,相比之下本机网络延迟可以忽略,或者可以通过进程间通信来解决?
//!高性能 在同一台计算机上,RakNet可以实现在两个程序之间每秒传输25,000条信息
//!联机方案
class NetServerIocp
{
public:NetServerIocp();~NetServerIocp();bool InitServer(int serverPort);bool Close();//!子线程可调用,控制台可以立即输出,但ui无法立即刷新,需要主线程异步处理void LogSub(const char* lpszFormat, ...);void SetLogFun(OutPutFun fun);virtual const char* GetPacketLogStr(PacketBase* packet);void TerminateServer();//!处理消息队列void MainThreadProcessPacketPool();int  RecvOverlapped( OverlappedCustumRecv* overlapped );int  RecvStream( PSocket socket, char* buffer, int bufferSize ,OverlappedCustumRecv* overlapped);int  SendMsg( int packetID, PSocket socket, char* buffer, int bufferSize );int  SendPacketToSocket( PacketBase* packet,PSocket socket,bool prepareSend=true);virtual void KickPlayer(PSocket socket);int  AcceptThread(void* threadParam );int  WorkerThread(void* threadParam);void LogError( const char* msg );void SetSpecialPacketID(int login,int disconnect);static const char* classname(){return "NetServerIocp";}static NetServerIocp* GetSingleton(){return m_this;};static NetServerIocp* m_this;protected:bool        m_bRunning;HANDLE      m_iocp;PSocket      m_listenSocket;int         m_port;//消息队列PacketQueue* m_packetQueue;//线程HANDLE      m_acceptThread;HANDLE      m_workThread;//登录断开协议idint         m_loginPacketID;int         m_disconnectPacketID;//主线程可以直接调用logfun控制台输出,子线程不可以OutPutFun   m_logFun;char        m_logBuff[1024*10];int         m_logSize;CriticalSection m_logCriticalSection;//!不是每个工作线程配备一个tcpstream,而是每个连接的socket都可能要配备一个tcpstream//有断包时保存在(overlapped)完成键中,共用TcpStream//TcpStream*   m_tcpStream;};#endif#endif//========================================================
//  @Date:     2016.05
//  @File:     SourceLib/Net/NetServerIocp.cpp
//  @Brief:     NetServerIocp
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================#include "General/Pch.h"
#include <list>#include "General/AES.h"
//通过socket 获得player//每帧 数据库批量插入 事务原子性#if (defined WIN32APP) && (defined SERVER_APP)
#include <stdlib.h>
#include <winsock2.h> //<winsock2.h> 必须在<windows.h> 前面
#include <windows.h>
#include "Net/NetServerIocp.h"
#include "Net/PacketList.h"
#include "General/General.h"
#include "General/ObjectPool.h"
#include "General/Pce.h"
//#include "../SourceDemoSync/Packet/Protocol.h"#pragma message(__FILE__": linking with Ws2_32.lib")
#pragma comment(lib, "Ws2_32.lib")
#define OP_READ             0
#define OP_WRITE            1
#define OP_ACCEPT           2ObjectPool<TcpStream> G_TcpStreamParserPool(10);// OVERLAPPED 扩展
class OverlappedCustum
{
public:OVERLAPPED      overlapped;SOCKET          socket;int             op;char            buf[MaxPacketSize];DWORD           buflen;  int             zeroTrans;
};class OverlappedCustumSend
{
public:OVERLAPPED      overlapped;SOCKET          socket;int             op;char            buf[MaxPacketSize];DWORD           buflen;  int             zeroTrans;int             sendedLen;
};class OverlappedCustumRecv
{
public:OverlappedCustumRecv():tcpStream(NULL){}~OverlappedCustumRecv(){if (tcpStream){G_TcpStreamParserPool.DelObject(tcpStream);}}OVERLAPPED      overlapped;SOCKET          socket;int             op;char            buf[MaxPacketSize];DWORD           buflen;  int             zeroTrans;TcpStream*      tcpStream;//NetPlayer*      player;//char            bLogin; //是否通过登录验证 可以通过player==NULL来判断? 未成功登录前只有有限的几个消息允许处理!!
};ObjectPool<OverlappedCustumSend> G_OverlappedPool(10);
typedef std::list<PacketBase*>  PacketsList;DWORD WINAPI AcceptThread( void* threadParam )
{return NetServerIocp::GetSingleton()->AcceptThread(threadParam);
}
DWORD WINAPI WorkerThread(void* threadParam)
{return NetServerIocp::GetSingleton()->WorkerThread(threadParam);
}NetServerIocp* NetServerIocp::m_this = NULL;
NetServerIocp::NetServerIocp()
:m_bRunning(false)
,m_packetQueue(NULL)
,m_logFun(NULL)
,m_loginPacketID(-1)
,m_disconnectPacketID(-1)
{m_this = this;m_logBuff[0]='\0';m_logSize = 0;//m_tcpStream = new TcpStream;m_listenSocket = CreateSocket();
}NetServerIocp::~NetServerIocp()
{Close();//SafeDelete(m_tcpStream);FreeSocket(m_listenSocket);
}bool NetServerIocp::Close()
{if(m_bRunning == false) return true;m_bRunning = false;closesocket(Ptr2Socket(m_listenSocket));//放在前面,否则accept阻塞acceptThreadWaitForSingleObject(m_acceptThread, INFINITE);CloseHandle(m_acceptThread);CloseHandle(m_iocp);//放在前面,否则GetQueuedCompletionStatus阻塞workThread WaitForSingleObject(m_workThread, INFINITE);CloseHandle(m_workThread);SafeDelete(m_packetQueue);return true;
}bool NetServerIocp::InitServer(int serverPort)
{UnitTestRegister::UnitTestAll();m_port = serverPort;WSADATA           wsd;// 初始化socket库,   保证ws2_32.dll已经加载if (WSAStartup(MAKEWORD(2, 2), &wsd) != NO_ERROR)return false;//SOCKET listenSocket;listenSocket = socket(AF_INET,     // IPv4SOCK_STREAM, // 顺序的、可靠的、基于连接、双向的数据流通信IPPROTO_IP   // 使用TCP协议);if (listenSocket == INVALID_SOCKET){WSACleanup();return false;}Ptr2Socket(m_listenSocket) = listenSocket;// bind 服务端的通信协议、IP地址、端口SOCKADDR_IN local;local.sin_addr.s_addr = htonl(INADDR_ANY);local.sin_family   = AF_INET;local.sin_port       = htons(m_port);int ret = bind(listenSocket,(struct sockaddr *)&local,sizeof(local));if(ret == SOCKET_ERROR){TerminateServer();return false;}// 创建完成端口m_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,(ULONG_PTR)0,//completion key 附加参数0);if ( !m_iocp ) {TerminateServer();return false;}//ZeroMemory( G_ServerPlayers, sizeof(GamePlayers) );// 开始监听if ( listen(listenSocket,SOMAXCONN) != 0 ) {TerminateServer();return false;}m_bRunning = true;//监听线程m_acceptThread = CreateThread( NULL, 0, ::AcceptThread, NULL, 0, NULL );//工作线程的个数一般设置为processors *2+2//工作线程m_workThread = CreateThread( NULL, 0, ::WorkerThread, NULL, 0, NULL );m_packetQueue = new PacketQueue;LogSub("Server start success! address=%s;port=%d;","this",serverPort);return true;
}void NetServerIocp::LogSub(const char* lpszFormat, ...)
{AutoLock lock(&m_logCriticalSection);va_list   args;int       nBuf;char     szBuffer[512]; va_start(args, lpszFormat);nBuf = _vsnprintf_s(szBuffer, sizeof(szBuffer)*sizeof(TCHAR), lpszFormat, args);va_end(args);int size = strlen(szBuffer);if (m_logSize+size<10200){m_logSize += size;m_logSize += 2;strcat(m_logBuff,szBuffer);strcat(m_logBuff,"\r\n");}
}void NetServerIocp::LogError(const char* msg )
{int error = WSAGetLastError();switch (error){case WSA_IO_PENDING://   LogSub("%s :WSA_IO_PENDING!",msg);break;case WSAENOBUFS:LogSub("%s :No buffers!",msg);break;case WSANOTINITIALISED:LogSub("%s :Need Startup()!",msg);break;case WSAEINVAL:LogSub("%s :Need listen()!",msg);break;case WSAENOTSOCK:LogSub("%s :Not a socket!",msg);break;default:;LogSub("%s :unknow error!",msg);}
}void NetServerIocp::TerminateServer()
{closesocket( Ptr2Socket(m_listenSocket) );WSACleanup();
}int  NetServerIocp::RecvOverlapped( OverlappedCustumRecv* overlappedRecv )
{DWORD  flags = 0;overlappedRecv->op = OP_READ;overlappedRecv->buflen = 0;//有些客户端在关闭时会连续不断触发GetQueuedCompletionStatus(返回true,BytesTransferred值为0,GetLastError()==WSA_IO_PENDING)。可能是采用了不正确的关闭方式//如果不清除buf,则连续接收最后一条消息ZeroMemory(&(overlappedRecv->buf),MaxPacketSize);ZeroMemory(&(overlappedRecv->overlapped),sizeof(OVERLAPPED));WSABUF wbuf;wbuf.buf = overlappedRecv->buf;wbuf.len = MaxPacketSize;//传递一个OVERLAPPEDPLUS结构(WSASend、 WSARecv等函数)//这个函数投递一个异步IO请求,非阻塞直接返回。系统完成时通过GetQueuedCompletionStatus通知,GetQueuedCompletionStatus是阻塞的。//如果接收操作立即完成,overlappedRecv->bytes会返回函数调用所接收到的字节数? 否则为0int ret = WSARecv((overlappedRecv->socket),&wbuf,1,&(overlappedRecv->buflen),&flags,&(overlappedRecv->overlapped),NULL);if ( ret == SOCKET_ERROR  ) {int error = WSAGetLastError();//注意nagle算法//返回WSA_IO_PENDING,是因为TCP/IP层缓冲区中没有数据可取,系统将会锁定我们投递的WSARecv的buffer,直到TCP/IP层缓冲区中有新的数据到来。LogError("recv error");}return ret;
}int  NetServerIocp::SendMsg( int packetID, PSocket socket, char* buffer, int bufferSize )
{if (!m_bRunning){return 0;}//LogSub("send %d" ,packetID);if (buffer==NULL){return 0;}int sendAccum = 0;//非阻塞:发送缓冲满了 重复send是错误的? 异步非阻塞,导致高频发送,挪到发送成功腾出缓冲时发送剩余部分//即使没发完 系统会自动后续发送,可以释放buf,buf会被系统锁定不会被重新new到,但是由于pool的原因如果释放到pool还是可能被改写,所以统一接到全部发送完成消息时再删除//while (sendAccum<bufferSize){//乱序问题?//多线程使用异步通信方式向同一个接收端(socket)同时发送数据,会导致接收端接收的数据混乱//线程1第一次发送:123456789,假设未发送完,只发送了123//线程2第一次发送:abcdefgh,假设未发送完,只发送了abc//线程1第二次发送:456789,发送完成//线程2第二次发送:defgh,发送完成//接收端最终接收的数据为:123abc456789defgh。//主线程发消息,工作线程释放OverlappedCustumSend*    overlappedSend = G_OverlappedPool.NewObject();//手动将packet包分为tcp包是安全的,因为客户端只连接了服务端一个socketif (bufferSize>=MaxPacketSize){LogError("send bufferSize>= MaxPacketSize");int a = 0;}int toSend = min(MaxPacketSize,bufferSize/*-sendAccum*/);overlappedSend->socket = Ptr2Socket(socket);overlappedSend->op = OP_WRITE;overlappedSend->sendedLen = 0;DWORD bytes = 0;ZeroMemory(&(overlappedSend->overlapped),sizeof(OVERLAPPED));memcpy(overlappedSend->buf, buffer/*+sendAccum*/, toSend );WSABUF wbuf;wbuf.buf = overlappedSend->buf;wbuf.len = toSend;int ret = WSASend(Ptr2Socket(socket), &wbuf, 1, &bytes, 0,&(overlappedSend->overlapped), NULL );overlappedSend->buflen = toSend;//overlappedSend->buflen = bytesif ( ret == SOCKET_ERROR  ) {int error = WSAGetLastError();//返回WSA_IO_PENDING表示TCP/IP层缓冲区已满,//系统将锁定用户的程序缓冲区到系统的非分页内存中。//直到TCP/IP层缓冲区有空余的地方来接受拷贝我们的程序缓冲区数据才拷贝走,并将给IOCP一个完成消息。if (error == WSAECONNABORTED){//非pending错误时 是否会泄露?//GetQueuedCompletionStatus能收到通知吗?//closesocket(socket);//delete overlappedSend;//某客户端断开LogError("send error:connect aborted");return -999;}else{////若某客户端死循环或断点挂起,会运行到此,Overlapped不断创建且无法被释放。//64次失败后断开客户端,允许客户端短暂调试?//实测:断开连接后 pending的overlappedSend会被GetQueuedCompletionStatus接收到//发送缓冲区占满时不会阻断其他socket也发送失败?每个tcp连接独立的发送缓冲区? 只有在心跳包保活期间才发送消息 ?//发送的内容比接收缓冲区大。系统完成通知会多次返回。?//若干次发送失败后必须剔除LogError("send error:io pending");return -1;}}else{sendAccum += bytes;}}return sendAccum;
}int  NetServerIocp::RecvStream( PSocket socket, char* buffer, int bufferSize ,OverlappedCustumRecv* overlapped)
{bool kick = false;if (!m_bRunning){return !kick;}if (buffer==NULL||bufferSize<=0){//kick = true;return !kick;}if (overlapped->tcpStream==NULL){overlapped->tcpStream = G_TcpStreamParserPool.NewObject();overlapped->tcpStream->m_incompleteStreamSize = 0;}TcpStream* tcpStream = overlapped->tcpStream;//m_tcpStreamtcpStream->RecvStream(socket,buffer,bufferSize,overlapped);PacketBase* packet = NULL;PacketGrabRes   res = tcpStream->GrabPacket(packet);while ( 1) {if (res==PR_Complete){//拆出一个完整包 消息进栈前上锁AutoLock lock(m_packetQueue->GetCriticalSection());m_packetQueue->Push(packet);//no breakres = tcpStream->GrabPacket(packet);}else if (res==PR_Incomplete){//缓冲满 正常情况LogSub("解析到正常断包!");break;}else if (res==PR_EmptyStream){//取完,没有断包,释放tcpStreamif (overlapped->tcpStream){G_TcpStreamParserPool.DelObject(overlapped->tcpStream);overlapped->tcpStream = NULL;}break;}else if (res==PR_ErrorChecksum){//校验错误//kick = true;if (overlapped->tcpStream){overlapped->tcpStream->m_incompleteStreamSize = 0;G_TcpStreamParserPool.DelObject(overlapped->tcpStream);overlapped->tcpStream = NULL;}LogSub("!!!!!!!!! 包校验错误! ");break;}else if (res==PR_ErrorStream){//流解析出错,可能导致后面所有包都解析出错。//清空缓冲,等待到下一个正确包,直到下一段流没跨缓冲区,是正常包头,则接收恢复正常。//kick = true;if (overlapped->tcpStream){overlapped->tcpStream->m_incompleteStreamSize = 0;G_TcpStreamParserPool.DelObject(overlapped->tcpStream);overlapped->tcpStream = NULL;}LogSub("!!!!!!!!! 包解析失败! ");break;}}return !kick;
}int  NetServerIocp::SendPacketToSocket( PacketBase* packet,PSocket socket ,bool prepareSend)
{if(prepareSend){packet->PrepareSend();}const char* txt = GetPacketLogStr(packet);if(txt)LogSub("send %s",txt);   return SendMsg( packet->m_packetID, socket,packet->m_buffer, packet->m_streamSize);
}void NetServerIocp::KickPlayer( PSocket socket )
{int                size = sizeof(sockaddr_in);sockaddr_in     client;getpeername(Ptr2Socket(socket),(sockaddr *)&client,&size);LogSub("断开连接:[%s], reason: kick player。\n",inet_ntoa(client.sin_addr));//?closesocket(Ptr2Socket(socket));
}//侦听线程
int  NetServerIocp::AcceptThread(void* threadParam )
{sockaddr_in        client;int              size;SOCKET         acceptSocket;OverlappedCustumRecv   *overlappedRecv;while(m_bRunning) { size = sizeof(sockaddr_in);//accept是阻塞函数,接收到连接时才返回,可以用select函数设置侦听的时间acceptSocket = accept(Ptr2Socket(m_listenSocket),(sockaddr *)&client,&size);if(acceptSocket != INVALID_SOCKET){LogSub("连接建立: %s!", inet_ntoa(client.sin_addr));//每一个玩家连接进来,则创建一个overlappedCustum//注意以下设置acceptSocket 而非listenSocket//阻塞方式的时候,会自动使用Nagle算法,Nagle虽然解决了小封包问题,提高了吞吐量,但导致了较高的不可预测的延迟//1.发送端使用Nagle算法,TCP包头需要占用一定的字节数,所以发送这样的包时,大部分的传输开销花在包头上了;//2.接收端使用clark算法或者delay ack。发送方Nagle算法的实质是拼接小数据包,如果当前数据不够一个MSS,则等待,直到有足够的数据能拼到一起或者新的ACK到来。接收方Clark算法是一有数据包就发送ACK,但通告窗口为0,直到有足够的接收缓存,再通告较大的窗口,delay ack是指不立即回复ACK,直到有多个数据包到来,或者有足够的缓冲区//window上只有delay ack 没有clark?? 接收端不会自动粘包??//禁止发送端粘包const char chOpt=1;   int ret=setsockopt(acceptSocket,IPPROTO_TCP,TCP_NODELAY,&chOpt,sizeof(char));   if(ret==SOCKET_ERROR)   {   LogSub("setsockopt TCP_NODELAY error."); }////系统默认的状态发送和接收一次最大(约为8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发:// 接收缓冲区int nRecvBuf = 64 * 1024; //设置为64Ksetsockopt(acceptSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int));//发送缓冲区int nSendBuf = 64*1024; setsockopt(acceptSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&nSendBuf, sizeof(int));//设置后,若断开,则在使用该socket读写时立即失败,并返回ETIMEDOUT错误int keepAlive = 1; // 开启keepalive属性int keepIdle = 60; // 60秒内没有数据往来,则进行探测 int keepInterval = 5; // 探测时间间隔int keepCount = 3; // 探测尝试的最大次数.setsockopt(acceptSocket, SOL_SOCKET, SO_KEEPALIVE, (const char *)&keepAlive, sizeof(keepAlive));//setsockopt(acceptSocket, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));//setsockopt(acceptSocket, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));//setsockopt(acceptSocket, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));overlappedRecv = new OverlappedCustumRecv;if ( overlappedRecv ){//套接字与完成端口的关联,在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知CreateIoCompletionPort((HANDLE)acceptSocket, m_iocp, NULL, 0);//保存套接字overlappedRecv->socket = acceptSocket;RecvOverlapped( overlappedRecv );//{//  //验证客户端合法性//  S2CPacketVerificate packet;//   //atoi(packet.m_VerificateCode,m_fromSocket);// packet.m_res = S2CPacketVerificate::Req_DoVerificate;//    G_Server->SendPacketToSocket(&packet,m_fromSocket);//}} else {closesocket( acceptSocket );LogSub("falied to new overlapped: %s!", inet_ntoa(client.sin_addr));}} else {    // accept errorLogError("accept error");}}return 0;
}/*
TCP/IP模型分为5层:应用层、传输层、网络层、数据链路层以及 物理层
应用层:http、ftp协议
传输层:TCP、UDP协议tcp(Transmission Control Protocol)面向连接(先要和对方确定连接、传输结束需要断开连接,类似打电话)、复杂可靠的、有很好的重传和查错机制。TCP协议没有收到对方的确认包,会有超时重传,每个数据包是有序列号的,传输层根据序列号来保证A,B包的顺序,即使B比A先到达了,TCP也会是等A到达之后,先把A提交给应用层udp(user datagram protocol)面向无连接(无需确认对方是否存在,类似寄包裹)、简单高效、没有重传机制。一般用于即时通讯、广播通信等网络层:IP协议,负责对数据加上IP地址和其他的数据(后面会讲到)以确定传输的目标。网络层用来处理网络中流动的数据包,数据包为最小的传递单位.保证包的完整性,若传输层发出的包过大,在网络层会被分包,同时在接收端的网络层会被组包,有一个完整的包才会交给传输层,若包不完整会丢弃
*///不断的查询 send receive 状态(完成端口发出的完成通知)
int  NetServerIocp::WorkerThread(void* threadParam)
{ULONG_PTR           ckey;OVERLAPPED             *localOverlapped  = NULL;OverlappedCustum     *overlappedCustum = NULL;OverlappedCustumRecv *overlappedRecv   = NULL;OverlappedCustumSend *overlappedSend   = NULL;DWORD                BytesTransferred;//每个acceptsocket各需要一个用于接收的overlappedCustum//所有acceptsocket同时只需要n个用于发送的overlappedCustum,n和可能发送的线程数有关while(m_bRunning) { //阻塞函数//ckey是关联完成端口与套接字时的完成键;BOOL ret = GetQueuedCompletionStatus(m_iocp, &BytesTransferred,&ckey,&localOverlapped,INFINITE);if (localOverlapped){//从成员变量指针反推对象指针,此处因为overlapped放在开头转换后地址不变overlappedCustum = CONTAINING_RECORD(localOverlapped, OverlappedCustum, overlapped);switch (overlappedCustum->op) {case OP_READ :overlappedRecv = CONTAINING_RECORD(localOverlapped, OverlappedCustumRecv, overlapped);break;case OP_WRITE:overlappedSend = CONTAINING_RECORD(localOverlapped, OverlappedCustumSend, overlapped);break;}}else{overlappedCustum = NULL;overlappedRecv = NULL;overlappedSend = NULL;}//断开连接:返回ERROR_SUCCESS,并且lpNumberOfBytes等于0if ( ret == FALSE) { //int error = WSAGetLastError();//switch(error)//{//   case ERROR_NETNAME_DELETED://       break;//}if(overlappedCustum){// 远端玩家断开连接int                size = sizeof(sockaddr_in);sockaddr_in     client;getpeername(overlappedCustum->socket,(sockaddr *)&client,&size);LogSub("断开连接:[%s], reason: GetQueuedCompletionStatus()==false, op: %s", inet_ntoa(client.sin_addr),overlappedCustum->op==OP_READ?"read":"write");//移除玩家AutoLock lock(m_packetQueue->GetCriticalSection());char buf[32];int *pIntBuffer=(int *)buf;*pIntBuffer++= m_disconnectPacketID;*pIntBuffer++ = PacketBase::HeadSize;*pIntBuffer++= PacketBase::DebugRecvID++;int readSize = 0;PacketBase* packet;PacketGrabRes res = PacketBase::GenPacketFromBuf(packet, buf,readSize,PacketBase::HeadSize,&overlappedCustum->socket);if (res==PR_Complete){//packet->m_overlapedRecv = overlappedRecv;//一定是recv?,下面delete后成为野指针?m_packetQueue->Push(packet);}//delete overlappedCustum;switch (overlappedCustum->op) {case OP_READ :delete overlappedRecv;break;case OP_WRITE:G_OverlappedPool.DelObject(overlappedSend);break;//m_sendFailNum>=3主动断开时,这里进来3次,保证了发送overlapped不会泄露}continue;}else{//服务器关闭continue;}}//有些客户端在关闭时会连续不断触发GetQueuedCompletionStatus(返回true,BytesTransferred值为0,GetLastError()==WSA_IO_PENDING)。可能是采用了不正确的关闭方式//如果直接断开,有时候会出现误断,同一个IP如果连续3次,则视为断开if (BytesTransferred<=0){//发送或接收长度为0//如果errno == EINTR 则是接收到信号后返回overlappedCustum->zeroTrans++;if (overlappedCustum->zeroTrans>3){//远端玩家断开连接 ,移除玩家// 消息进栈前上锁AutoLock lock(m_packetQueue->GetCriticalSection());char buf[32];int *pIntBuffer=(int *)buf;*pIntBuffer++= m_disconnectPacketID;*pIntBuffer++ = PacketBase::HeadSize;*pIntBuffer++= PacketBase::DebugRecvID++;int readSize = 0;PacketBase* packet;PacketGrabRes res = PacketBase::GenPacketFromBuf(packet, buf,readSize,PacketBase::HeadSize,&overlappedCustum->socket);if (res==PR_Complete){//packet->m_overlapedRecv = overlappedRecv;//下面delete后成为野指针?m_packetQueue->Push(packet);}int             size = sizeof(sockaddr_in);sockaddr_in     client;getpeername(overlappedCustum->socket,(sockaddr *)&client,&size);LogSub("断开连接:[%s], reason: zeroTrans>3, op: %s", inet_ntoa(client.sin_addr),overlappedCustum->op==OP_READ?"read":"write");//删除重叠,不要再RecvMsg投递该端口的读取请求//delete overlappedCustum;switch (overlappedCustum->op) {case OP_READ :delete overlappedRecv;break;case OP_WRITE:G_OverlappedPool.DelObject(overlappedSend);break;}continue;}}else{overlappedCustum->zeroTrans = 0;}// 有重叠操作完成switch (overlappedCustum->op) {//接收成功case OP_READ:{//OverlappedCustumRecv* overlappedRecv = CONTAINING_RECORD(localOverlapped, OverlappedCustumRecv, overlapped);//todo 完成键中保存 client 或 player 指针 传递到packet中,就无需再取一次  假如没秒1万消息 1万次map查找也要耗时数毫秒//玩家没断开 overlapedMy不会调用delete,socket引用保持有效int   streamSize = BytesTransferred;//overlappedCustum->bytes;int   continueRecv = RecvStream(&overlappedRecv->socket,overlappedRecv->buf,streamSize,overlappedRecv);//Log("RecvMsg: ", overlappedRecv->wbuf.buf+8);if(continueRecv){//继续接收下一条RecvOverlapped( overlappedRecv );}else{//收到5个错误包,踢掉//if(GetErrPacketNum(overlappedCustum->socket)>5)//todo 放到主线程kick?KickPlayer(&overlappedRecv->socket);}break;}//发送成功case OP_WRITE:{//OverlappedCustumSend* overlappedSend = CONTAINING_RECORD(localOverlapped, OverlappedCustumSend, overlapped);overlappedSend->sendedLen += BytesTransferred;//若没发送完if(overlappedSend->sendedLen < overlappedCustum->buflen){//系统自动发送剩余部分//如果连接异常,若干次发送长度为0后,也会删除,不会泄露?int a = 0;}else{//每发送一条消息new一个,发完deleteG_OverlappedPool.DelObject(overlappedSend);}break;}default:{LogSub("error:recv overlappedCustum->op %d" ,overlappedCustum->op);  break;}}}return 0;
}//!多个工作线程提高吞吐量,但是包都放到主线程池,主线程处理?
void NetServerIocp::MainThreadProcessPacketPool()
{if (!m_bRunning || !m_packetQueue){return;}if(m_logFun && m_logBuff[0]){AutoLock lock(&m_logCriticalSection);m_logFun(m_logBuff);m_logSize = 0;m_logBuff[0]='\0';}PacketBase   *packet;int         lastmsg = 0;//上锁int size = 0;{AutoLock lock(m_packetQueue->GetCriticalSection());size = m_packetQueue->GetSize();//其实无法完全避免,由于多线程的原因,包可能一次收不全,还在流里//如果同时收到2个以上玩家断开连接,先标记,否则给断开的玩家发消息出错PacketsList* packetList = (PacketsList*)m_packetQueue->m_listPackets;for (PacketsList::iterator it=packetList->begin();it!=packetList->end();++it){(*it)->PreProcess();}}while (size>0) {{AutoLock lock(m_packetQueue->GetCriticalSection());packet = m_packetQueue->Pop();size = m_packetQueue->GetSize();}//屏蔽大部分未登录发起的攻击//if (m_loginPacketID==-1 && packet->m_packetID!=m_loginPacketID)//{//    //因为应用层几乎每个消息都会GetPlayerBySocket,所以此处不做重复检查//    NetPlayer* player = m_netLobby->GetPlayerBySocket(packet->m_fromSocket);//   if (player==NULL)//   {//     kick(player);//     continue;// }//}//buffer拷贝浪费的效率?packet->FromBuffer();packet->m_debugID = PacketBase::DebugRecvID++;const char* txt = GetPacketLogStr(packet);if(txt){LogSub(" recv %s",txt);}packet->Process();delete packet;}
}void NetServerIocp::SetLogFun(OutPutFun fun)
{m_logFun = fun;
}void NetServerIocp::SetSpecialPacketID(int login,int disconnect)
{m_loginPacketID = login;m_disconnectPacketID = disconnect;
}const char* NetServerIocp::GetPacketLogStr(PacketBase* packet)
{return "";
}#endif

游戏服务器编程-iocp及封包处理相关推荐

  1. php游戏服务器教程,C++游戏服务器编程从入门到掌握视频教程(全)

    任务1: 课程预览PPT 2-课程概述.mp4 3-IP详解第一部分.mp4 任务4: 预览IP详解PPT 5-IP详解第二部分.mp4 6-TCP详解第一部分(介绍 + 工作原理 + 头部详解).m ...

  2. c语言游戏服务器源码,2018大师级C++游戏服务器编程实战(视频+源码)

    免费 任务1: 课程预览PPT 免费 任务2: 课程概述 27:18 免费 任务3: IP详解第一部分 35:22 免费 任务4: IP详解PPT 免费 任务5: IP详解第二部分 31:45 免费 ...

  3. 游戏服务器 c语言,C++游戏服务器编程从入门到掌握视频教程(全)

    资源介绍 任务1: 课程预览PPT 2-课程概述.mp4 3-IP详解第一部分.mp4 任务4: 预览IP详解PPT 5-IP详解第二部分.mp4 6-TCP详解第一部分(介绍 + 工作原理 + 头部 ...

  4. 百万用户级游戏服务器架构设计

    百万用户级游戏服务器架构设计 服务器结构探讨 -- 最简单的结构 所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求.所以,结构本无所谓正确与错误:当然,优秀的结构更有助于系统的 ...

  5. 牛人写的设计游戏服务器

    转载自 zeeman的博客 - 牛人写的设计游戏服务器 :http://blog.sina.com.cn/s/blog_55d572ca0100uvzt.html 有段时间没有研究技术了,这次正好看到 ...

  6. 1. 游戏服务器相关讨论(转)

    服务器结构探讨 – 最简单的结构 所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求.所以,结构本无所谓正确与错误:当然,优秀的结构更有助于系统的搭建,对系统的可扩展性及可维护性 ...

  7. 游戏服务器设计(转)

    有段时间没有研究技术了,这次正好看到了新版的mangos,较之以前我看的版本有了比较大的完善,于是再次浏览了下他的代码,也借此机会整理下我在游戏服务器开发方面的一些心得,与大家探讨. 另外由于为避免与 ...

  8. 百万用户级游戏服务器架构设计与游戏视频开发平台源码分享

    服务器结构探讨 -- 最简单的结构 所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求.所以,结构本无所谓正确与错误:当然,优秀的结构更有助于系统的搭建,对系统的可扩展性及可维护 ...

  9. 百万用户级游戏服务器架构介绍

    服务器结构探讨 -- 最简单的结构 所谓服务器结构,也就是如何将服务器各部分合理地安排,以实现最初的功能需求.所以,结构本无所谓正确与错误:当然,优秀的结构更有助于系统的搭建,对系统的可扩展性及可维护 ...

最新文章

  1. [Python爬虫] Selenium获取百度百科旅游景点的InfoBox消息盒
  2. Spark SQL中的DataFrame
  3. 搜狗浏览器收藏夹在哪_安卓Edge浏览器最新版42.0.2轻体验,整体优良但无特别惊喜...
  4. C4-Squid-Purge
  5. 数据结构--树形结构(1)
  6. tomcat配置前台访问日志记录
  7. excel文件损坏修复绝招_修复数据工具大盘点,让你快速掌握电脑数据恢复的秘密武器...
  8. 冯诺依曼计算机模型中存储器,在冯诺依曼计算机模型中存储器是指什么单元?...
  9. pythongui编程星期的中英文对照_编写一个程序,根据用户输入的一个英文字符翻译成相应的中文日期,如输入“M”返回“星期一”。...
  10. phython ji
  11. Loopback接口和Null接口配置
  12. Excel 插件使用教程
  13. Thoth-Tech靶机实验实战演练
  14. Oracle----Orcacle简介
  15. validation检查框架
  16. 数据库课程设计 人事管理系统
  17. 交流电源滤波器电路图及作用分析
  18. “大力丸”是壮阳药吗?
  19. 柴达木光伏+农业跨界融合新态势
  20. 阿里聚安全Webview安全攻防

热门文章

  1. 地铁 java_“地铁系统”简易代码
  2. 获取设备的型号信息,比如iPhone5s,iPhone5等等
  3. Halcon Qt 环境一次性配置
  4. 要求用户在Python中输入整数| 限制用户仅输入整数值
  5. android_rooting_tools 项目介绍(CVE-2012-4220)
  6. 想用FPGA加速神经网络,这两个开源项目你必须要了解
  7. mac 挂载 EFI 分区
  8. 网站使用CDN加速的5个优势
  9. Unity API——1
  10. 帧动画的多种实现方式与性能对比