网络编程笔记<1>
来源:互联网 发布:淘宝开业牌匾 编辑:程序博客网 时间:2024/05/01 07:42
我是菜鸟,尽管拍砖,好好学习,天天向上,只说白话文体,不扯蛋。
目的很明确,一步一步将网络编程的学好,这儿理思路,记笔记,等拍砖,纠错。
WIN--->IOCP-->IOCP(TCP_SVR)--->IOCP(TCP_CL)--->IOCP(UDP)
网络基础-- | |---------->贯通
Linux--->Epoll->Epoll(TCP_SVR)--->Epoll(TCP_CL)----->Epoll(UDP)
IOCP
1.个人理解,完成端口就是事件分离器,可以通过告诉它你关心什么,
一旦你事件发生了,它会告诉你,你就可以从结果中分析出自己关
心的什么事情已然发生,发生了什么(如得到数据)。这种过程
必然是异步的,OK 理论完毕,开始干活。
2.开始设计实现 IOCP(TCP_SVR) 具体实现很多都是参考以前大牛门的代码。(巨人门的肩膀啊...这个可以有)
服务器端的传输层封装,
至少需要具备四个基本的接口函数来驱动传输。
a. 开始服务。
b. 结束服务器。
c. 发送数据。
d. 断开连接。
需要将结果通知服务应用层。
a. 来数据了。
b. 新连接来了。
c. 连接被关闭了。
准备一些基础的结构。
// TCP套接字信息
struct CTCPContext
{
Socket m_oSocket;
sockaddr_in m_oSockAddr;
SOverLappedEx* m_pOverLap; // 这里与 SOverLappedEx*互指
CTCPContext(void)
{
m_oSocket = INVALID_SOCKET;
m_pOverLap = NULL;
memset(&m_oSockAddr,0,sizeof(sockaddr_in));
}
};
// TCP WSAOVERLAPPED 扩展
struct SOverLappedEx
{
WSAOVERLAPPED m_oWsaOverlap;
EIOOperation m_eOperate;
WSABUF m_oWsaBuffer;
CTCPContext* m_lpRelateContect; // 这里与 CTCPContext 互指
SOverLappedEx()
{
m_oWsaBuffer.buf = 0;
m_oWsaBuffer.len = 0;
}
};
// IO动作也是必须的
enum EIOOperation
{
e_IoAccept = 1, // Accept ...
e_IoRead = 2, // WSARecv ...
e_IoWrite = 3, // WSASend ... 实际上这个动作用不上
};
// 回调虚接口
class IOHandleData
{
public:
virtual void OnRecv(ULong aulLen, CTCPContext* lpTcpContext, char* apBuf) = 0;
virtual void OnConnect(CTCPContext* lpTcpContext) = 0;
virtual void OnClose(CTCPContext* lpTcpContext) = 0;
};
下面可以设计具体的服务器传输层了。
// TCP完成端口服务端
class CTcpIocpServer
{
public:
CTcpIocpServer(void);
virtual ~CTcpIocpServer(void);
public:
// 启动服务
BOOL Start(SInt16 ai16LsnPort, IOHandleData* apoRecv, SInt8 ai8WorkThreadCnt = 0);
// 停止服务
BOOL Stop(void);
// 发送数据
BOOL SendTo(Socket ahSocket, const char *apszBuff, int aiLen);
// 结束指定的会话
BOOL StopSession(CTCPContext* apSession);
private:
// 结束指定的会话
BOOL LocalStopSession(CTCPContext* apSession);
// 初始化完成端口
BOOL InitCompletePort(SInt8 ai8WorkThreadCnt = 0);
// 初始化完成端口
void CloseCompletionPort(void);
// 完成端口工作线程入口
static ULong WorkThread(void* apObj);
// 完成端口工作线程切换
void WorkThread(void);
// 数据接受处理线程
static ULong HandleRecvThread(void* apObj);
void HandleRecvThread(void);
// 初始化监听
BOOL InitLsnSocket(SInt16 ai16Port);
// 连接线程入口
static ULong AcceptThread(void* apObj);
// 连接线程切换
void AcceptThread(void);
// 扩大AcceptList
BOOL EnlargeAcceptList(void);
// 申请OverLapped
SOverLappedEx* AllocSOverLappedEx(EIOOperation aeType);
// 归还OverLapped
BOOL GiveBackSOverLappedEx(SOverLappedEx* apOverlapped);
// 归还CTCPContext
BOOL GiveBackCTCPContext(CTCPContext* apCTCPContext);
// 清除TCP连接
BOOL RemoveTcpConnect(CTCPContext* apTcpSocket);
// 清除UnConnect
BOOL RemoveTcpUnConnect(SOverLappedEx* apOverlapped);
// 处理 e_IoRead状态
BOOL OnRead(ULong aulDataLen, SOverLappedEx* aOverlapex);
// 处理 e_IoAccept状态
BOOL OnAccept(ULong aulDataLen, SOverLappedEx* aOverlapex);
// 处理接收到的数据
BOOL HandleRecvData(CTCPContext* apTcpSocket, char* apBuf,ULong aulLen);
private:
// 最大已接数
SInt32 m_i32MaxConnectdeCnt;
// 工作状态
BOOL m_bWorkingState;
// 监听套接字
CTCPContext m_oLsnSocket;
// 完成端口
HANDLE m_hCompLetePort;
// 工作线程记数
SLong m_slWorkThreadCnt;
// 工作线程记数锁
CThreadLock m_oThreadCntLock;
// 监听事件
HANDLE m_hAcceptEvent;
// 监听端口
SInt16 m_i16Port;
// 未连接OverLappedE队列
stdext::hash_set<SOverLappedEx*> m_oUnConnect;
// 未连接OverLappedE队列锁
CThreadLock m_oUnConnectLock;
// 最大OverLappedE未接数
UInt32 m_i32UnConnectCnt;
// 已连接队列,OnAccept被响应的时候会调用插入,各种断开会剔除
stdext::hash_set<CTCPContext*> m_oConnected;
// 已连接队列锁
CThreadLock m_oConnectedLock;
// 最大已接数
UInt32 m_i32MaxConnectedCnt;
// 接收OverLap缓冲队列
CushionQueue<SOverLappedEx> m_oRecvOverlapCache;
// 当每创建一个新的连接时候 分配一个重叠拓展,用于标识状态并提供数据存储
// 的空间初始状态为 e_IoAccept 受到一份数据之后复用且状态修改为e_IoRead
// 各种断开连接都需归还本对象
// 未连接队列缓冲队列
CushionQueue<CTCPContext> m_oConnectedCache;
// 当每创建一个新的连接时候OnAccept 中索取一个 CTCPContext 交给m_oUnConnect使用为
// 了避免重复创建此类型对象,故而提供缓冲,用完后归还其申请继m_oRecvOverlapCache之后
// 归还于其同时
// 单位时间发包数
UInt64 m_i64PerTimeSendCnt;
// 单位时间收包数
UInt64 m_i64PerTimeRecvCnt;
// 单位时间有效接入
UInt64 m_i64PerTimeConnected;
// 单位时间有效断开
UInt64 m_i64PerTimeCutDown;
// 写数据重叠结构
WSAOVERLAPPED m_oWriteOverlap;
CThreadLock m_oWriteCriSection;
// 数据处理回调
IOHandleData* m_poRecv;
// 内存池
static CMemPool* m_oMemPool;
// TCP原始数据包大小
SInt16 m_OrgnlPackSize;
// 收到的数据缓存
CNodeQueue m_oRecvDataQueue;
};
动作分解:
/*******************************************
开始服务
*******************************************/
@ai16LsnPort 服务端监听端口(只允许开一个)
@aiRecv 应用层数据通知回调(我理解回调是种行为,当然了这个说法与常规意义的回调有所出入)
@ai8WorkThreadCnt 完成端口工作线程数
BOOL CTcpIocpServer::Start(SInt16 ai16LsnPort, IOHandleData* aiHandleIo, SInt8 ai8WorkThreadCnt/* = 0*/)
{
if (0 == aiHandleIo)
return FALSE;
m_pHandleIO = aiHandleIo;
if (NULL == CTcpIocpServer::m_oMemPool)
{
m_bWorkingState = FALSE;
ERROR_LOG("ERROR:CTcpIocpServer::Start CMemPool::GetPool() 失败!");
return FALSE;
}
WSADATA loWsaData;
int liError = WSAStartup(MAKEWORD(2,2),& loWsaData);
if (0 != liError)
{
m_bWorkingState = FALSE;
ERROR_LOG("ERROR: CTcpIocpServer::CTcpIocpServer WSAStartup 失败! liError:%d;",liError);
return FALSE;
}
// 初始化完成端口和监听这个或的先后顺序很重要
if (( FALSE == InitCompletePort(ai8WorkThreadCnt)) ||
( FALSE == InitLsnSocket(ai16LsnPort)))
{
Stop();
return FALSE;
}
if(NULL == m_hAcceptEvent)
{
m_hAcceptEvent = WSACreateEvent();
if (NULL == m_hAcceptEvent)
{
Stop();
ERROR_LOG("ERROR: CTcpIocpServer::Start m_hAcceptEvent = WSACreateEvent() 失败! GetLastError():%d",GetLastError());
m_hAcceptEvent = FALSE;
}
}
if (SOCKET_ERROR == WSAEventSelect(m_oLsnSocket.m_oSocket, m_hAcceptEvent,FD_ACCEPT))
{
Stop();
ERROR_LOG("ERROR: CTcpIocpServer::Start 监听SOCKET关联ACCEPT事件失败!");
return FALSE;
}
for(int i = 0;i <20; ++i)
EnlargeAcceptList();
// 开启Accept线程
CThreadMgr loThrdMgr;
loThrdMgr.BeginThread(AcceptThread,this);
loThrdMgr.BeginThread(HandleRecvThread,this);
return TRUE;
}
这个初始化过程主要做了四件事:
1.参数初始化。
2.对象/环境初始化
3.开启必要线程。
4.判断必要条件是否具备。
InitCompletePort 初始化了完成端口
/*******************************************
初始化完成端口
@ai8WorkThreadCnt 可以自己设置用多少个工作线程,也可以不填写,使用cpu*2
*******************************************/
BOOL CTcpIocpServer::InitCompletePort(SInt8 ai8WorkThreadCnt/* = 0*/)
{
// 已初始化了,则直接返回
if (INVALID_HANDLE_VALUE != m_hCompLetePort)
return FALSE;
// 创建完成端口
m_hCompLetePort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if(INVALID_HANDLE_VALUE == m_hCompLetePort)
{
ERROR_LOG("ERROR:CTcpIocpServer::InitCompletePort CreateIoCompletionPort 失败!");
return FALSE;
}
// 计算需开启的工作线程数 cpu*2(常规打法)
if (0 >= ai8WorkThreadCnt)
{
SYSTEM_INFO loSystemInfo;
memset(&loSystemInfo,0,sizeof(SYSTEM_INFO));
GetSystemInfo(&loSystemInfo);
ai8WorkThreadCnt = (SInt8)loSystemInfo.dwNumberOfProcessors*2;
}
// 启动工作线程
CThreadMgr loThreadMgr;
for (int i = 0; i<ai8WorkThreadCnt; ++i)
loThreadMgr.BeginThread(WorkThread,this);
return TRUE;
}
InitLsnSocket 初始化了服务端监听
/*******************************************
初始化监听
@ai16Port 监听端口
*******************************************/
BOOL CTcpIocpServer::InitLsnSocket(SInt16 ai16Port)
{
// 端口值检查
if (DEF_MIN_LSN_PORT > ai16Port|| DEF_MAX_LSN_PORT < ai16Port)
{
ERROR_LOG("ERROR:CTcpIocpServer::InitLsnSocket 端口不在范围内 ai16Port:%d!",ai16Port);
return FALSE;
}
m_i16Port = ai16Port;
// 启动状态检查
if (INVALID_SOCKET != m_oLsnSocket.m_oSocket)
{
ERROR_LOG("ERROR:CTcpIocpServer::InitLsnSocket 监听已启动!");
return TRUE;
}
// 创建监听套接字
m_oLsnSocket.m_oSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == m_oLsnSocket.m_oSocket)
{
ERROR_LOG("ERROR:CTcpIocpServer::InitLsnSocket WSASocket 失败 GetLastError:%d!",GetLastError());
return FALSE;
}
// 将监听套接字同完成端口关联
if (NULL == CreateIoCompletionPort(HANDLE(m_oLsnSocket.m_oSocket), m_hCompLetePort, ULONG_PTR(&m_oLsnSocket), 0))
{
DEF_CLOSE_SOCKET(m_oLsnSocket.m_oSocket);
ERROR_LOG("ERROR: CTcpIocpServer::InitLsnSocket CreateIoCompletionPort 失败 GetLastError:%d!",GetLastError());
return FALSE;
}
// 服务端地址设置
m_oLsnSocket.m_oSockAddr.sin_family = AF_INET;
m_oLsnSocket.m_oSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
m_oLsnSocket.m_oSockAddr.sin_port = htons(ai16Port);
// 绑
if (SOCKET_ERROR == bind(m_oLsnSocket.m_oSocket, (struct sockaddr*)&m_oLsnSocket.m_oSockAddr,sizeof(sockaddr)))
{
DEF_CLOSE_SOCKET(m_oLsnSocket.m_oSocket);
ERROR_LOG("ERROR: CTcpIocpServer::InitLsnSocket bind 失败 GetLastError:%d!",GetLastError());
return FALSE;
}
// 听
if (SOCKET_ERROR == listen(m_oLsnSocket.m_oSocket,50))
{
DEF_CLOSE_SOCKET(m_oLsnSocket.m_oSocket);
ERROR_LOG("ERROR: CTcpIocpServer::InitLsnSocket listen 失败 GetLastError:%d!",GetLastError());
return FALSE;
}
INFO_LOG("INFO: 监听成功 %s:%d", inet_ntoa(CPubfuncs::GetLocalAddr()),ai16Port);
return TRUE;
}
...下回继续ing...
- 网络编程笔记1
- 网络编程笔记<1>
- 网络编程笔记1
- "网络编程"学习笔记(1)
- 网络编程学习笔记1
- linux网络编程笔记1
- Java网络编程笔记1
- 网络编程笔记(1)
- Java编程笔记之网络编程1
- python网络编程学习笔记(1)--网络编程背景
- c++网络编程学习笔记(1)
- Unix网络编程学习笔记(1)
- "黑马程序员"网络编程学习笔记1
- 网络编程学习笔记(1)
- linux网络编程笔记(1)
- IOS网络编程笔记(1)
- C++网络编程学习笔记1
- unix网络编程学习笔记1
- C# 不重复随机数的产生算法!
- 数塔 最优路径问题 -- (第九题)
- How to Find A Memory Leak
- 算法面试:精选微软经典的算法面试100题
- PB读取配置文件ini(ProfileString)
- 网络编程笔记<1>
- 初识 Service(二) 演示: Activity 调用Service的接口
- Linux内核通知链机制的原理及实现
- linux send recv函数详解
- volatile在debug和release模式下的意义
- 堆和栈的区别——转载自:百度
- 每对顶点间的最短路径C++实现
- phpExcel导出到Excel和pdf
- Java编写的五子棋游戏