网络编程笔记<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...

原创粉丝点击