最精简的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.