WinSock学习笔记6:IOCP完成端口模型

WinSock学习笔记6:IOCP完成端口模型

 

unit  Unit1;

interface

uses
  WinSock2, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  
// 单IO数据结构
  LPER_IO_OPERATION_DATA 
=  ^TPER_IO_OPERATION_DATA;
  TPER_IO_OPERATION_DATA 
=   packed   record
    Overlapped: WSAOverlapped;
    DataBuf: WSABuf;
    Buff: 
array  [ 0 .. 10 of  Char;
    BytesSend: DWORD;
    BytesRecv: DWORD;
  
end ;

  
// 单句柄数据结构
  LPER_HANDLE_DATA 
=  ^TPER_HANDLE_DATA;
  TPER_HANDLE_DATA 
=   packed   record
    Socket: TSocket;
  
end ;

  TListenThread 
=   class (TThread)
  
private
  
protected
    
procedure  Execute; override ;
  
public
    
constructor  Create;
  
end ;

  TForm1 
=   class (TForm)
    Memo1: TMemo;
    Button1: TButton;
    
procedure  FormCreate(Sender: TObject);
  
private
    
{  Private declarations  }
  
public
    
{  Public declarations  }
  
end ;

var
  Form1: TForm1;
  ServerSocket: TSocket;

implementation

{ $R *.dfm }

// 工作者线程
function  WorkThread(CompletionPortID: Pointer):DWORD;  stdcall ;
var
  CompletionPort: THandle;
  BytesTransferred: DWORD;
  PerHandleData: LPER_HANDLE_DATA;
  PerIOData: LPER_IO_OPERATION_DATA;
  Flags: DWORD;
  RecvBytes: DWORD;
begin
  CompletionPort:
=  THandle(CompletionPortID);

  
while  True  do
  
begin
    
// 获取完成端口上的队列的完成状态
    GetQueuedCompletionStatus(CompletionPort, BytesTransferred, DWORD(PerHandleData),
      POverlapped(PerIOData), INFINITE);

    
// 判断是客户端发来的数据还是服务端发出的数据
    
if  PerIOData.BytesRecv  =   0   then
    
begin
      PerIOData.BytesRecv:
=  BytesTransferred;
      PerIOData.BytesSend:
=   0 ;
    
end   else
      PerIOData.BytesSend:
=  PerIOData.BytesSend  +  BytesTransferred;

    
if  PerIOData.BytesRecv  >  PerIOData.BytesSend  then
    
begin
      ZeroMemory(@(PerIOData.Overlapped), SizeOf(WSAOverlapped));
      PerIOData.DataBuf.buf:
=  PerIOData.Buff  +  PerIOData.BytesSend;
      PerIOData.DataBuf.len:
=  PerIOData.BytesRecv  -  PerIOData.BytesSend;

      
// 显示收到的数据,这样做是不安全的,示例而已 :)
      Form1.Memo1.Lines.Add(PerIOData.Buff);
    
end ;

    
// 重置数据
    PerIOData.BytesRecv:
=   0 ;
    PerIOData.DataBuf.len:
=   22 ;
    PerIOData.DataBuf.buf:
=  @PerIOData.Buff;

    
// 再次投递
    WSARecv(PerHandleData.Socket, @(PerIOData.DataBuf), 
1 , RecvBytes, Flags,
      @(PerIOData.Overlapped), 
nil );
  
end ;
end ;

{  TWorkThread  }

constructor  TListenThread.Create;
begin
  
inherited  Create(False);
  FreeOnTerminate:
=  True;
end ;

procedure  TListenThread.Execute;
var
  WSData: TWSAData;
  CompletionPort: THandle;
  lpSystemInfo: TSystemInfo;
  Idx: Integer;
  ThreadID: DWORD;
  LocalAddr: TSockaddr;
  ClientAddr: TSockaddr;
  ClientSocket: TSocket;

  PER_HANDLE_DATA: LPER_HANDLE_DATA;
  PER_IO_OPERATION_DATA: LPER_IO_OPERATION_DATA;

  RecvBytes: DWORD;
  Flags: DWORD;
begin
  
inherited ;

  
// 初始化Winsock
  WSAStartUp($
202 , WSData);
  
// 创建完成端口
  CompletionPort:
=  CreateIOCompletionPort(INVALID_HANDLE_VALUE,  0 0 0 );
  
// 根据处理器数量创建工作者线程的数量(网上流传的说法是线程数量 = cpu数量 * 2 + 2 )
  GetSystemInfo(lpSystemInfo);
  
for  Idx : =   1   to  lpSystemInfo.dwNumberOfProcessors  * 2   +   2   do
    
// 创建工作者线程,并将完成端口句柄传递给线程
    CreateThread(
nil 0 , @WorkThread, Pointer(CompletionPort),  0 , ThreadID);
  
// 创建监听套接字
  ServerSocket:
=  WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP,  nil 0 , WSA_FLAG_OVERLAPPED);
  
// 设置LocalAddr的参数
  LocalAddr.sin_family:
=  AF_INET;    // IPV4族
  LocalAddr.sin_addr.S_addr:
=  INADDR_ANY; // 这里不能写Inet_addr( ' 127.0.0.1 ' ),否则会绑定失败,不清楚原因是什么;
  LocalAddr.sin_port:
=  Htons( 1077 );  // Host To Net Short,主机字节顺序转为网络字节顺序
  
// 绑定本机IP地址、端口,绑定之前先设置好LocalAddr的参数
  Bind(ServerSocket, @LocalAddr, SizeOf(LocalAddr));
  
// 开始监听
  Listen(ServerSocket, 
5 );

  
while   not  Terminated  do
  
begin
    ClientSocket:
=  WSAAccept(ServerSocket, ClientAddr,  nil nil 0 );
    
// 创建TPER_HANDLE_DATA结构的变量保存客户端Socket
    PER_HANDLE_DATA:
=  LPER_HANDLE_DATA(GlobalAlloc(GPTR, SizeOf(TPER_HANDLE_DATA)));
    PER_HANDLE_DATA.Socket:
=  ClientSocket;
    
// 把完成端口和客户端套接字关联起来
    CreateIOCompletionPort(ClientSocket, CompletionPort, DWORD(PER_HANDLE_DATA), 
0 );
    
// 创建TPER_IO_OPERATION_DATA结构的变量,关联WSARecv函数
    PER_IO_OPERATION_DATA:
=  LPER_IO_OPERATION_DATA(GlobalAlloc(GPTR, SizeOf(TPER_IO_OPERATION_DATA)));
    ZeroMemory(@PER_IO_OPERATION_DATA.Overlapped, SizeOf(WSAOverlapped));
    PER_IO_OPERATION_DATA.BytesSend:
=   0 ;
    PER_IO_OPERATION_DATA.BytesRecv:
=   0 ;
    PER_IO_OPERATION_DATA.DataBuf.len:
=   22 ;
    PER_IO_OPERATION_DATA.DataBuf.buf:
=  @PER_IO_OPERATION_DATA.Buff;

    WSARecv(ClientSocket, @(PER_IO_OPERATION_DATA.DataBuf), 
1 , RecvBytes,
      Flags, @(PER_IO_OPERATION_DATA.Overlapped), 
nil );
  
end ;
end ;


procedure  TForm1.FormCreate(Sender: TObject);
begin
  
// 创建监听线程
  TListenThread.Create();
end ;


end .

 

 

该代码仅仅是练习IOCP的函数使用和大体的实现步骤,里面很多细节并不是唯一的方法,例如单IO数据结构和单句柄数据结构的定义等等。

 

你可能感兴趣的:(学习笔记)