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