库地址:
=========================
ST**表示单线程
//开始监听
bool Start( int nMaxMonitor );
//增加一个监听对象
bool AddMonitor( SOCKET socket );
//等待事件发生,block无作用
bool WaitEvent( void *eventArray, int &count, bool block );
//增加一个接受连接的操作,有连接进来,WaitEvent会返回
bool AddAccept(SOCKET listenSocket);
//增加一个接收数据的操作,有数据到达,WaitEvent会返回
bool AddRecv( SOCKET socket, char* recvBuf, unsigned short bufSize );
//增加一个发送数据的操作,发送完成,WaitEvent会返回
bool AddSend( SOCKET socket, char* dataBuf, unsigned short dataSize );
抽象控制的api
监听套接字对象的方法,
IOCPMonitor
==========================
typedef struct IO_EVENT
{
SOCKET sock;
EventType type;
SOCKET client;
char *pData;
unsigned short uDataSize;
}IO_EVENT;
MemoryPool m_iocpDataPool;//iocp投递参数池
typedef struct IOCP_OVERLAPPED
{
/**
* OVERLAPPED类型指针
* 指向完成操作参数
* 传递给AcceptEx()的最后一个参数
* 传递给WSARecv()的第个参数
* GetQueuedCompletionStatus()返回的第个参数
*/
OVERLAPPED m_overlapped;
/**
* 指向存有连接进来的客户端局域网和外网地址的内存
* 必须使用动态分配的内存块
* 传递给AcceptEx()的第个参数
*
*/
char m_outPutBuf[sizeof(SOCKADDR_IN)*2+32];
/**
* 客户端局域网IP信息长度
* 传递给AcceptEx()的第个参数
*/
unsigned long m_dwLocalAddressLength;
/**
* 客户端外网IP信息长度
* 传递给AcceptEx()的第个参数
*/
unsigned long m_dwRemoteAddressLength;
WSABUF m_wsaBuffer;//WSARecv接收缓冲数据,传递给WSARecv()的第个参数
SOCKET sock;
EventType completiontype;//完成类型recv 2send
}IOCP_OVERLAPPED;
IOCPMonitor::Start( int nMaxMonitor )
端口启动
创建完全端口
线程数cpu数*2+2
IOCPMonitor::AddMonitor( SOCKET sock )
加入套接字到 IOCP列队
IOCPMonitor::WaitEvent( void *eventArray, int &count, bool block )
等待一次完全端口的 事件
GetQueuedCompletionStatus( )
返回不同的iocp事件类型 或 数据 供上层循环控制
IOCPFrame : public NetEngine
继承了 netengine 的通用和抽象的方法,
同时针对不同的os平台实例化不同的网络模型
IOCPFrame::IOCPFrame()
{
#ifdef WIN32
m_pNetMonitor = new IOCPMonitor;
#endif
IOCPFrame 控制监听 接收 发送,集成 NetEngine 抽象
Child: one new IOCPMonitor;
class NetServer
{
friend class NetEngine;
NetEngine* m_pNetCard;
Netserver为 主要控制对象,里面抽象了逻辑的控制的 函数,可以继承城市
同时 NetServer 和 NetEngine 是friend class
NetEngine 里面有监听过程中的 一些写业务的逻辑调用到 Netserver,几个抽象的业务 接口 实现的地方 就独立出来了。
NetEngine 业务控制抽象的几种 业务调用
Threeadpool的任务
三种:业务实现
OnConnect
Onclose
OnMsg
比如
监听到链接的 调用NetEngine 调用了 iocpframe监听 到连接的事件
调用父辈方法
NetEngine::OnConnect( SOCKET sock, bool isConnectServer )
创建一个 NetConnect对象,通过内存池分配的对象
投递一个 连接的任务的task给线程池
Onclose
OnMsg
类似
内存池设计
内存池 存储 NetConnect对象,存储管理netconnect对象 使用memorypool
//初始化内存池 预分配内存池
bool Init(unsigned short uMemorySize, unsigned short uMemoryCount);
// //分配内存(链表方法)
一个线程读 一个线程写 无锁队列
Iobuffer
包含vector 一张 Iobufferblock 表,存储多个缓冲块
读取的时候
Iobufferblock 托管在内存池mempool BUFBLOCK_SIZE大小的内存块
Iobuffer
Readdata
/**
* IO缓冲
* 定义宏BUFBLOCK_SIZE
* 编译期静态确定类大小
* 不能使用指针在运行时动态指定大小,参考池对象使用限制二
*/
unsigned char m_buffer[BUFBLOCK_SIZE];
//已收到数据的长度
unsigned short m_uLength;
//Recv()函数下次读取数据的开始位置
unsigned short m_uRecvPos;
unsigned short IOBufferBlock::ReadData( unsigned char *data, unsigned short uLength, bool bDel )
从当前读取位置开始读取,如果越界 就用剩下的大小获取
bool IOBuffer::ReadData( unsigned char *data, int uLength, bool bDel )
//这里检查m_uDataSize不需要原子操作
//cpu与内存次交换最小长度就是byte,对于小于等于byte的类型的取值/赋值操作,
//本身就是原子操作
从头来
遍历 所有bufferblock 如果是需要删除的,那么原子操作减一,标志不使用了内存池,同时删除清理block
一读一写,没有线程安全问题
====================
SharedPtr
通过原子加减操作达到 引用计数操作的线程安全
ShareMemory
依靠viewmap实现 内存映射文件
/*
* 创建/打开共享内存
* 参数
* key 全局标识,linux下只接收数字id
* size 共享大小
* if ( 是创建)
* 则创建size大小的共享内存,
* else //是打开已存在的共享内存
* if ( 是文件映射&& 是linux系统)
* 则使用当前共享内存大小与size中较大的一个,作为共享内存的大小
* else 无效
* else 无效
* path 使用文件映射的共享内存,文件路径,key为文件名
*/
ShareMemory(const char *key, unsigned long size, const char *path);
/*
* 创建/打开共享内存
* 参数
* key 全局标识
* size 共享大小
* if ( 是创建)
* 则创建size大小的共享内存,
* else //是打开已存在的共享内存
* if ( 是文件映射&& 是linux系统)
* 则使用当前共享内存大小与size中较大的一个,作为共享内存的大小
* else 无效
* else 无效
* path 使用文件映射的共享内存,文件路径,key为文件名
*/
ShareMemory(const int key, unsigned long size, const char *path);
public:
void* GetBuffer();
unsigned long GetSize();
void Destory();
一次性获取数据,初始化的时候指定 了大小了。
内存映射文件的方法,没啥好看的
Signal
采用事件做信号通知,
=========================================
ThreadPool
包含一个vect的任务队列 里面带的就是task
线程池 采用 事件信号通知。执行的函数 ThreadFunc
每次从 从m_tasks取任务,加了锁地取法。
然后执行task的里面的Execute 方法 这样就调用到 Executor的CallMethod方法 参数从入队进入就提交了。
Memorypool
一、
一块对象数据MEMERY_INFO + uMemorySize
二、一个 MemoryPool的 数据结构
存储对象本身 8Byte
每一块数据包含信息8byte 然后就是指向 目标管理对象的对象内存
nBlockStartPos += MEMERY_INFO;
pObject = &(m_pMemery[nBlockStartPos]);
内存块数 初始化的时候 设置的 m_uMemoryCount
三、Alloc()
找寻本身memorypool是否存在可用内存,
有,就直接获取地址出去使用
没有的的话遍历到最后会发现 pBlock->m_pNext 为空,这个时候重新new memorypool对象 教导链表上去
m_uFreeCount 未分配的 个数
通过遍历 查询AtomDec pBlock->m_uFreeCount 减1
if ( 0 < (int32)AtomDec(&pBlock->m_uFreeCount, 1) ) break; 执行分配
class MemoryPool void* Alloc();
* 无锁堆(n读n写并发,不需加锁)
* 内存池结构
* n个池组成链表
*
* 池结构
* 每个池都是一段连续内存保存在char数组中
* 0~7byte为池地址,后面是n个用于固定长度分配的内存块(即内存块是定长的)
*
* 内存块结构
* 状态byte+留空byte+内存块块序号byte
* 所以一个内存池最大可以保存的对象(内存块)数量为unsigned short的最大值
*
* 状态就个(分配/未分配),个byte就可以表示,使用byte是为了使用原子操作实现lock-free,而原子操作操作的是byte地址
* 2byte留空,是为了保证后面用于分配的内存守地址都是byte对齐
* 因为真实new出来的对象首地址是保证这点的,
* 且IOCP投递缓冲struct,也要求首地址对齐,否则返回错误
初始化
//预分配内存=MemoryPool对象自身地址,byte,支持位机寻址
//记录对象地址
//头个字节保存对象自身地址,用于从内存地址找到内存池对象地址
if ( 8 == uAddrSize )
{
m_pMemery[nPos++] = (unsigned char)(uThis >> 56);
m_pMemery[nPos++] = (unsigned char)(uThis >> 48);
m_pMemery[nPos++] = (unsigned char)(uThis >> 40);
m_pMemery[nPos++] = (unsigned char)(uThis >> 32);
}
else
{
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
}
m_pMemery[nPos++] = (unsigned char)(uThis >> 24);
m_pMemery[nPos++] = (unsigned char)(uThis >> 16);
m_pMemery[nPos++] = (unsigned char)(uThis >> 8);
m_pMemery[nPos++] = (unsigned char)uThis;
//初始化内存
unsigned short i;
for ( i = 0; i < m_uMemoryCount; i++ )
{
//状态未分配
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
//留空字节
m_pMemery[nPos++] = 0;
m_pMemery[nPos++] = 0;
//保存内存序号
m_pMemery[nPos++] = (unsigned char) (i >> 8);
m_pMemery[nPos++] = (unsigned char) i;
nPos += uMemorySize;
}
链表 内存池组
每个内存池
通过对象算出索引
通过索引算出对象都很方便
详细参考另外一个笔记