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.
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数据结构和单句柄数据结构的定义等等。