最精簡的IOCP封裝


最精簡的IOCP封裝,DELPHI XE8直接編譯通過。Winsock2.pas即使用DELPHI自帶的,相信XE7也能編譯,或者XE6,XE5也能。

單說Winsock2.pas,我見過無數種版本的了,各版本WINSOCK 2的API的方法的參數的數據類型居然都有出入,使用不同人封裝的Winsock2.pas源碼都要進行相應的調整,

否則無法編譯通過,我認為還是使用DELPHI官方的最為靠譜。

要用於實際應用的話,還要進行“粘包處理”。

我在DELPHI XE8下測試OK。

unit Unit1;

interface

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

const
BUF_SIZE=1024;

type
//單IO數據結構
LPER_IO_DATA = ^TPER_IO_DATA;
TPER_IO_DATA = packed record
Overlapped: WSAOverlapped;
DataBuf: WSABuf;
Buf: array [0..BUF_SIZE-1] of AnsiChar;
SendBytes: DWORD;
RecvBytes: 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;
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_DATA;
Flags: DWORD;
RecvBytes: DWORD;
begin
CompletionPort:= THandle(CompletionPortID);

while True do
begin
//獲取完成端口上的隊列的完成狀態
GetQueuedCompletionStatus(CompletionPort, BytesTransferred, ULONG_PTR(PerHandleData), POverlapped(PerIOData), INFINITE);
//判斷是客戶端發來的數據還是服務端發出的數據
if PerIOData.RecvBytes = 0 then
begin
PerIOData.RecvBytes:= BytesTransferred;
PerIOData.SendBytes:= 0;
end else
PerIOData.SendBytes:= PerIOData.SendBytes + BytesTransferred;

if PerIOData.RecvBytes > PerIOData.SendBytes then
begin
ZeroMemory(@(PerIOData.Overlapped), SizeOf(WSAOverlapped));
PerIOData.DataBuf.buf:= PerIOData.Buf + PerIOData.SendBytes;
PerIOData.DataBuf.len:= PerIOData.RecvBytes - PerIOData.SendBytes;

//顯示收到的數據,這樣做是不安全的,示例而已 :)
Form1.Memo1.Lines.Add(string(PerIOData.Buf));
end;

//重置數據
PerIOData.RecvBytes:= 0;
PerIOData.DataBuf.len:= BUF_SIZE;
PerIOData.DataBuf.buf:= @PerIOData.Buf;

//再次投遞
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;
SI: TSystemInfo;
Idx: Integer;
ThreadID: DWORD;
LocalAddr:sockaddr_in;
ClientAddr: sockaddr;
ClientSocket: TSocket;

PER_HANDLE_DATA: LPER_HANDLE_DATA;
PER_IO_DATA: LPER_IO_DATA;

RecvBytes: DWORD;
Flags: DWORD;
begin

//初始化Winsock
WSAStartUp($202, WSData);
//創建完成端口
CompletionPort:= CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
//根據處理器數量創建工作者線程的數量
GetSystemInfo(SI);
for Idx := 1 to SI.dwNumberOfProcessors 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(8000); //Host To Net Short,主機字節順序轉為網絡字節順序
//綁定本機IP地址、端口,綁定之前先設置好LocalAddr的參數
Bind(ServerSocket, sockaddr(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, ulong_ptr(PER_HANDLE_DATA), 0);
//創建TPER_IO_DATA結構的變量,關聯WSARecv函數
PER_IO_DATA:= LPER_IO_DATA(GlobalAlloc(GPTR, SizeOf(TPER_IO_DATA)));
ZeroMemory(@PER_IO_DATA.Overlapped, SizeOf(WSAOverlapped));
PER_IO_DATA.SendBytes:= 0;
PER_IO_DATA.RecvBytes:= 0;
PER_IO_DATA.DataBuf.len:= BUF_SIZE;
PER_IO_DATA.DataBuf.buf:= @PER_IO_DATA.Buf;

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


procedure TForm1.FormCreate(Sender: TObject);
begin
//創建監聽線程
TListenThread.Create();
end;


end.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM