• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

规划下将要出炉的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


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
DELPHIGetClassName-获取指定窗口的类名发布时间:2022-07-18
下一篇:
c++转化成delphi的代码发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap