Delphi-IOCP 学习笔记<六>=====IO内存池和扩展套接字(ClientContext)

简介: 规划下将要出炉的IOCP。 1.将接收IO数据改成内存池。 2.扩展lpCompletionKey: DWORD参数.扩展套接字对象。 3.借鉴java netty思路,使用decode –> handler的思路来处理客户端数据。

规划下将要出炉的IOCP。

1.将接收IO数据改成内存池。

2.扩展lpCompletionKey: DWORD参数.扩展套接字对象。

3.借鉴java netty思路,使用decode –> handler的思路来处理客户端数据。

 

 

//内存池

unit uMemPool;

interface

uses
  JwaWinsock2, Windows, SyncObjs;

const
  MAX_BUFFER_SIZE = 1024;

type
  LPPER_IO_OPERATION_DATA = ^PER_IO_OPERATION_DATA;

  PER_IO_OPERATION_DATA = packed record
    Overlapped: OVERLAPPED;
    IO_TYPE: Cardinal;
    DataBuf: TWSABUF;
    WorkBytes: Cardinal;    //如果是接收,接收的字节数
    WorkFlag: Cardinal;
    pre:LPPER_IO_OPERATION_DATA;
    next:LPPER_IO_OPERATION_DATA;
  end;
  
  TIODataMemPool = class(TObject)
  private
    FCs: TCriticalSection;

    //第一个可用的内存块
    FHead: LPPER_IO_OPERATION_DATA;

    //最后一个可用的内存卡
    FTail: LPPER_IO_OPERATION_DATA;

    //可用的内存个数
    FUseableCount:Integer;

    //正在使用的个数
    FUsingCount:Integer;

    /// <summary>
    ///   将一个内存块添加到尾部
    /// </summary>
    /// <param name="pvIOData"> (LPPER_IO_OPERATION_DATA) </param>
    procedure AddData2Pool(pvIOData:LPPER_IO_OPERATION_DATA);

    /// <summary>
    ///   得到一块可以使用的内存
    /// </summary>
    /// <returns> LPPER_IO_OPERATION_DATA
    /// </returns>
    function getUsableData: LPPER_IO_OPERATION_DATA;

    /// <summary>
    ///   创建一块内存空间
    /// </summary>
    /// <returns> LPPER_IO_OPERATION_DATA
    /// </returns>
    function InnerCreateIOData: LPPER_IO_OPERATION_DATA;

    procedure clearMemBlock(pvIOData:LPPER_IO_OPERATION_DATA);
  public
    class function instance: TIODataMemPool;
    constructor Create;
    destructor Destroy; override;

    //借一块内存
    function borrowIOData: LPPER_IO_OPERATION_DATA;

    //换会一块内存
    procedure giveBackIOData(const pvIOData: LPPER_IO_OPERATION_DATA);
  end;

implementation

var
  __IODATA_instance:TIODataMemPool;

constructor TIODataMemPool.Create;
begin
  inherited Create;
  FCs := TCriticalSection.Create();
  FUseableCount := 0;
  FUsingCount := 0;
end;

destructor TIODataMemPool.Destroy;
begin
  FCs.Free;
  inherited Destroy;
end;

{ TIODataMemPool }

procedure TIODataMemPool.AddData2Pool(pvIOData:LPPER_IO_OPERATION_DATA);
begin
  if FHead = nil then
  begin
    FHead := pvIOData;
    FHead.next := nil;
    FHead.pre := nil;
    FTail := pvIOData;
  end else
  begin
    FTail.next := pvIOData;
    pvIOData.pre := FTail;
    FTail := pvIOData;
  end;
  Inc(FUseableCount);
end;

function TIODataMemPool.InnerCreateIOData: LPPER_IO_OPERATION_DATA;
begin
  Result := LPPER_IO_OPERATION_DATA(GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA)));
  GetMem(Result.DataBuf.buf, MAX_BUFFER_SIZE);
  Result.DataBuf.len := MAX_BUFFER_SIZE;

  //清理一块内存
  clearMemBlock(Result);
end;

function TIODataMemPool.borrowIOData: LPPER_IO_OPERATION_DATA;
begin
  FCs.Enter;
  try
    Result := getUsableData;
    if Result = nil then
    begin
      //生产一个内存块
      Result := InnerCreateIOData;

      //直接借走<增加使用计数器>
      Inc(FUsingCount);
    end;
  finally
    FCs.Leave;
  end;
end;

procedure TIODataMemPool.clearMemBlock(pvIOData: LPPER_IO_OPERATION_DATA);
begin
  //清理一块内存
  pvIOData.IO_TYPE := 0;
  
  pvIOData.WorkBytes := 0;
  pvIOData.WorkFlag := 0;

  ZeroMemory(@pvIOData.Overlapped, sizeof(OVERLAPPED));
  ZeroMemory(pvIOData.DataBuf.buf, pvIOData.DataBuf.len);
end;

procedure TIODataMemPool.giveBackIOData(const pvIOData:
    LPPER_IO_OPERATION_DATA);
begin
  FCs.Enter;
  try
    //清理内存块
    clearMemBlock(pvIOData);

    //加入到可以使用的内存空间
    AddData2Pool(pvIOData);

    //减少使用计数器
    Dec(FUsingCount);
  finally
    FCs.Leave;
  end;
end;

function TIODataMemPool.getUsableData: LPPER_IO_OPERATION_DATA;
var
  lvPre:LPPER_IO_OPERATION_DATA;
begin
  if FTail = nil then
  begin
    Result := nil;
  end else  
  begin   
    Result := FTail;

    lvPre := FTail.pre;
    if lvPre <> nil then
    begin
      lvPre.next := nil;
      FTail := lvPre;
    end else  //FTail是第一个也是最后一个,只有一个
    begin
      FHead := nil;
      FTail := nil;
    end;  

    Result.next := nil;
    Result.pre := nil;

    Dec(FUseableCount);
    Inc(FUsingCount);
  end;
end;

class function TIODataMemPool.instance: TIODataMemPool;
begin
  Result := __IODATA_instance;
end;


initialization
  __IODATA_instance := TIODataMemPool.Create;

finalization
  if __IODATA_instance <> nil then
  begin
    __IODATA_instance.Free;
    __IODATA_instance := nil;
  end;

end.

 

 

//扩展的套接字对象

unit uClientContext;

interface

uses
  Windows, JwaWinsock2, uBuffer, uBufferBuilder;

type
  TClientContext = class(TObject)
  private
    FSocket: TSocket;

    FBuffers: TBufferLink;
  public

    procedure CloseClientSocket;

    constructor Create(ASocket: TSocket);

    procedure AppendBuffer(const buf:WSABUF); overload; 

    function AppendBuffer(buf:PAnsiChar; len:Cardinal): Cardinal; overload;

    function readBuffer(buf:PAnsiChar; len:Cardinal): Cardinal;

    destructor Destroy; override;

    property Socket: TSocket read FSocket;

  end;

implementation

procedure TClientContext.AppendBuffer(const buf:WSABUF);
begin
  FBuffers.AddBuffer(buf.buf, buf.len);
end;

procedure TClientContext.CloseClientSocket;
begin
  if FSocket <> INVALID_SOCKET then
  begin
    closesocket(FSocket);
  end;
end;

constructor TClientContext.Create(ASocket: TSocket);
begin
  inherited Create;
  FSocket := ASocket;
  FBuffers := TBufferLink.Create();
end;

destructor TClientContext.Destroy;
begin
  FBuffers.Free;
  FBuffers := nil;  
  CloseClientSocket;
  inherited Destroy;
end;

function TClientContext.AppendBuffer(buf:PAnsiChar; len:Cardinal): Cardinal;
begin
  FBuffers.AddBuffer(buf, len);
end;

function TClientContext.readBuffer(buf:PAnsiChar; len:Cardinal): Cardinal;
begin
  Result := FBuffers.readBuffer(buf, len);
end;

end.

 

 

//修改后的代码工作线程和listener

unit uD10_IOCP;

interface

uses
  JwaWinsock2, Windows, SysUtils, uMemPool;

const
  DATA_BUFSIZE = 1024;

  IO_TYPE_Accept = 1;
  IO_TYPE_Recv = 2;



type
  //(1):单IO数据结构
  PWorkData = ^TWorkerData;
  TWorkerData = packed record
    IOCPHandle:THandle;
    WorkerID:Cardinal;
  end;


function D10_IOCPRun(pvData:Pointer): Integer; stdcall;


implementation

uses
  logClientWrapper, uClientContext;


function ServerWorkerThread(pData:Pointer): Integer; stdcall;
var
  CompletionPort:THANDLE;
  lvWorkID:Cardinal;
  BytesTransferred:Cardinal;
  PerIoData:LPPER_IO_OPERATION_DATA;
  Flags:Cardinal;
  RecvBytes:Cardinal;
  lvResultStatus:BOOL;
  lvRet:Integer;
  
  lvClientContext:TClientContext;
begin
  CompletionPort:=PWorkData(pData).IOCPHandle;
  lvWorkID := PWorkData(pData).WorkerID;
   //得到创建线程是传递过来的IOCP
   while(TRUE) do
   begin
        //工作者线程会停止到GetQueuedCompletionStatus函数处,直到接受到数据为止
        lvResultStatus := GetQueuedCompletionStatus(CompletionPort,
          BytesTransferred,
          Cardinal(lvClientContext),
          POverlapped(PerIoData), INFINITE);

        if (lvResultStatus = False) then
        begin
          //当客户端连接断开或者客户端调用closesocket函数的时候,函数GetQueuedCompletionStatus会返回错误。如果我们加入心跳后,在这里就可以来判断套接字是否依然在连接。
          if lvClientContext<>nil then
          begin
            lvClientContext.Free;
            lvClientContext := nil;
          end;
          if PerIoData<>nil then
          begin
            TIODataMemPool.instance.giveBackIOData(PerIoData);
          end;
          continue;
        end;

        if PerIoData = nil then
        begin
          lvClientContext.Free;
          lvClientContext := nil;
          Break;
        end else  if (PerIoData<>nil) then
        begin
          if PerIoData.IO_TYPE = IO_TYPE_Accept then  //连接请求
          begin
            //发送日志显示
            lvRet := TLogClientWrapper.logINfo('工作线程[' + intToStr(lvWorkID) + ']:有新的连接接入');
            TIODataMemPool.instance.giveBackIOData(PerIoData);
          end else if PerIoData.IO_TYPE = IO_TYPE_Recv then
          begin
            //加入到套接字对应的缓存中
            lvClientContext.AppendBuffer(PerIoData.DataBuf.buf, PerIoData.Overlapped.InternalHigh);
            lvRet := TLogClientWrapper.logINfo('工作线程[' + intToStr(lvWorkID) + ']:接收到数据!');
            TIODataMemPool.instance.giveBackIOData(PerIoData);
          end;

          /////分配内存<可以加入内存池>
          PerIoData := TIODataMemPool.instance.borrowIOData;
          PerIoData.IO_TYPE := IO_TYPE_Recv;



          /////异步收取数据
          if (WSARecv(lvClientContext.Socket,
             @PerIoData.DataBuf,
             1,
             PerIoData.WorkBytes,
             PerIOData.WorkFlag,
             @PerIoData^, nil) = SOCKET_ERROR) then
          begin
            lvRet := GetLastError();
            //重叠IO,出现ERROR_IO_PENDING是正常的,
            //表示数据尚未接收完成,如果有数据接收,GetQueuedCompletionStatus会有返回值
            if (lvRet <> ERROR_IO_PENDING) then
            begin
              lvClientContext.Free;
              if PerIoData <> nil then
              begin
                GlobalFree(DWORD(PerIoData));
              end;
              Continue;
            end;
          end;
        end;
   end;
end;

function D10_IOCPRun(pvData:Pointer): Integer;
var
  WSData: TWSAData;
  lvIOPort, lvPerIOPort:THandle;
  hThread, dwThreadId:DWORD;

  sSocket, cSocket:TSocket;
  lvAddr:TSockAddr;
  lvAddrSize:Integer;
  lvMsg:String;
  lvPort:Integer;

  lvSystemInfo: TSystemInfo;
  i:Integer;

  PerIoData:LPPER_IO_OPERATION_DATA;

  lvWorkerData:PWorkData;

  Flags:Cardinal;
  RecvBytes:Cardinal;
  lvCount:Integer;
  lvClientContext:TClientContext;
begin

  lvPort := Integer(pvData);

  //加载SOCKET。使用的是2.2版为了后面方便加入心跳。
  WSAStartup($0202, WSData);

  // 创建一个完成端口(内核对象)
  lvIOPort := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);


//  GetSystemInfo(lvSystemInfo);
//  lvCount := lvSystemInfo.dwNumberOfProcessors * 2 -1;

  lvCount := 1;

  //ServerWorkerThread 是工作线程
  for I:=1 to lvCount do
  begin
     lvWorkerData := AllocMem(SizeOf(TWorkerData));
     lvWorkerData.IOCPHandle := lvIOPort;
     lvWorkerData.WorkerID := i;
     hThread := CreateThread(nil, 0, @ServerWorkerThread,
       lvWorkerData,0, dwThreadId);
     if (hThread = 0) then
     begin
         Exit;
     end;
     CloseHandle(hThread);
  end;

  //创建一个套接字,将此套接字和一个端口绑定并监听此端口。
  sSocket:=WSASocket(AF_INET,SOCK_STREAM,0,Nil,0,WSA_FLAG_OVERLAPPED);
  if sSocket=SOCKET_ERROR then
  begin
      closesocket(sSocket);
      WSACleanup();
  end;
  lvAddr.sin_family:=AF_INET;
  lvAddr.sin_port:=htons(lvPort);
  lvAddr.sin_addr.s_addr:=htonl(INADDR_ANY);
  if bind(sSocket,@lvAddr,sizeof(lvAddr))=SOCKET_ERROR then
  begin
     closesocket(sSocket);
     exit;
  end;

  listen(sSocket,20);

  //下面循环进行循环获取客户端的请求。
  while (TRUE) do
  begin
     //当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字cSocket。这个套接字就是和客户端通信的时候使用的套接字。
     cSocket:= WSAAccept(sSocket, nil, nil, nil, 0);

     //判断cSocket套接字创建是否成功,如果不成功则退出。
     if (cSocket= SOCKET_ERROR) then
     begin
        closesocket(sSocket);
        exit;
     end;

     //start----将套接字、完成端口绑定在一起。

     //     最开始的时候没有明白为什么还要调用一次createIoCompletionPort
     //
     //     后来经过google,和测试
     //
     //     是将新的套接字(socket)加入到iocp端口<绑定>
     //     这样工作线程才能处理这个套接字(socket)的数据包
     //如果把下面注释掉,WSARecv这个套接字时,GetQueuedCompletionStatus无法处理到收到的数据包
     //    注意第三个参数也需要进行绑定, 否则在工作线程中GetQueuedCompletionStatus时completionKey会取不到cSocket值

     lvClientContext := TClientContext.Create(cSocket);

     //将套接字、完成端口客户端对象绑定在一起。
     //2013年4月20日 13:45:10
     lvPerIOPort := CreateIoCompletionPort(cSocket, lvIOPort, Cardinal(lvClientContext), 0);
     if (lvPerIOPort = 0) then
     begin
        Exit;
     end;
     ////----end

     //初始化数据包
     PerIoData := TIODataMemPool.instance.borrowIOData;

     //数据包中的IO类型:有连接请求
     PerIoData.IO_TYPE := IO_TYPE_Accept;

     //通知工作线程,有新的套接字连接<第三个参数>
     PostQueuedCompletionStatus(
        lvIOPort, 0,
        Cardinal(lvClientContext),
        POverlapped(PerIOData));
  end;


end;

initialization

finalization
   WSACleanup;

end.

 

//在ClientContext中使用

unit uBuffer;
{
   套接字对应的接收缓存,使用链条模式。
}

interface

uses
  Windows;

type
  PBufRecord = ^_BufRecord;
  _BufRecord = packed record
    len: Cardinal; // the length of the buffer
    buf: PAnsiChar; // the pointer to the buffer

    preBuf:PBufRecord;   //前一个buffer
    nextBuf:PBufRecord;  //后一个buffer
  end;

  TBufferLink = class(TObject)
  private
    FHead:PBufRecord;
    FTail:PBufRecord;

    //当前读到的Buffer
    FRead:PBufRecord;
    //当前读到的Buffer位置
    FReadPosition: Cardinal;

    FMark:PBufRecord;
    FMarkPosition: Cardinal;

    function InnerReadBuf(const pvBufRecord: PBufRecord; pvStartIndex: Cardinal;
        buf: PAnsiChar; len: Cardinal): Cardinal;
  public
    constructor Create;

    procedure markReadIndex;

    procedure AddBuffer(buf:PAnsiChar; len:Cardinal);

    function readBuffer(buf:PAnsiChar; len:Cardinal):Cardinal;
  end;


implementation

constructor TBufferLink.Create;
begin
  inherited Create;
  FReadPosition := 0;
end;

{ TBufferLink }

procedure TBufferLink.AddBuffer(buf: PAnsiChar; len: Cardinal);
var
  lvBuf:PBufRecord;
begin
  New(lvBuf);
  lvBuf.preBuf := nil;
  lvBuf.nextBuf := nil;
  lvBuf.buf := GetMemory(len);
  lvBuf.len := len;
  CopyMemory(lvBuf.buf, Pointer(LongInt(buf)), len);
  if FHead = nil then
  begin
    FHead := lvBuf;
  end;
  
  if FTail = nil then
  begin
    FTail := lvBuf;
  end else
  begin
    FTail.nextBuf := lvBuf;
    lvBuf.preBuf := FTail;
  end;
end;

function TBufferLink.InnerReadBuf(const pvBufRecord: PBufRecord; pvStartIndex:
    Cardinal; buf: PAnsiChar; len: Cardinal): Cardinal;
var
  lvValidCount:Cardinal;
begin
  Result := 0;
  if pvBufRecord <> nil then
  begin
    lvValidCount := pvBufRecord.len-pvStartIndex;
    if lvValidCount <= 0 then
    begin
      Result := 0;
    end else
    begin
      if len <= lvValidCount then
      begin
        CopyMemory(buf, Pointer(Cardinal(pvBufRecord.buf) + pvStartIndex), len);
        Result := len;
      end else
      begin
        CopyMemory(buf, Pointer(Cardinal(pvBufRecord.buf) + pvStartIndex), lvValidCount);
        Result := lvValidCount;
      end;
    end;

  end;
end;

procedure TBufferLink.markReadIndex;
begin
  FMark := FRead;
  FMarkPosition := FReadPosition;
end;

function TBufferLink.readBuffer(buf: PAnsiChar; len: Cardinal): Cardinal;
var
  lvBuf:PBufRecord;
  lvPosition, l, lvReadCount, lvRemain:Cardinal;
begin
  lvReadCount := 0;
  lvBuf := FRead;
  lvPosition := FReadPosition;
  if lvBuf = nil then
  begin
    lvBuf := FHead;
    lvPosition := 0;
  end;

  if lvBuf <> nil then
  begin
    lvRemain := len;
    while lvBuf <> nil do
    begin
      l := InnerReadBuf(lvBuf, lvPosition, Pointer(Cardinal(buf) + lvReadCount), lvRemain);
      if l = lvRemain then
      begin
        //读完
        inc(lvReadCount, l);
        Inc(lvPosition, l);
        FReadPosition := lvPosition;
        FRead := lvBuf;
        lvRemain := 0;
        Break;
      end else if l < lvRemain then  //读取的比需要读的长度小
      begin
        lvRemain := lvRemain - l;
        inc(lvReadCount, l);
        Inc(lvPosition, l);
        FReadPosition := lvPosition;
        FRead := lvBuf;
        lvBuf := lvBuf.nextBuf;
        if lvBuf <> nil then   //读下一个
        begin
          FRead := lvBuf;
          FReadPosition := 0;
          lvPosition := 0;
        end;
      end;
    end;
    Result := lvReadCount;
  end else
  begin
    Result := 0;
  end;

end;

end.

>>>>后面研究Decoder

目录
相关文章
|
6月前
|
网络协议 Linux C++
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
116 0
【深入理解计算机系统】int 不是整数 | float 不是实数 | 内存引用错误的例子 | 学习笔记
【深入理解计算机系统】int 不是整数 | float 不是实数 | 内存引用错误的例子 | 学习笔记
75 0
|
6月前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
313 10
|
4月前
|
XML Ubuntu Linux
部署08---扩展-Win10配置WSL(Ubuntu)环境,WSL系统是什么意思,是Windows系统上的一个子系统, xml的一大特点是直链系统,直接链接你的CPU,硬盘和内存,如何用 WSL部署
部署08---扩展-Win10配置WSL(Ubuntu)环境,WSL系统是什么意思,是Windows系统上的一个子系统, xml的一大特点是直链系统,直接链接你的CPU,硬盘和内存,如何用 WSL部署
|
3月前
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
48 0
|
5月前
|
存储 编译器 C语言
【C++】学习笔记——内存管理
【C++】学习笔记——内存管理
55 15
|
4月前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
62 1
|
5月前
|
存储 C++
C primer plus 学习笔记 第12章 存储类别、链接和内存管理
C primer plus 学习笔记 第12章 存储类别、链接和内存管理
|
4月前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
58 0
|
4月前
|
设计模式 存储 缓存
Java面试题:结合单例模式与Java内存模型,设计一个线程安全的单例类?使用内存屏障与Java并发工具类,实现一个高效的并发缓存系统?结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:结合单例模式与Java内存模型,设计一个线程安全的单例类?使用内存屏障与Java并发工具类,实现一个高效的并发缓存系统?结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
39 0