//
单IO数据结构
LPVOID = Pointer;
LPPER_IO_OPERATION_DATA = ^ PER_IO_OPERATION_DATA ;
PER_IO_OPERATION_DATA = packed record
Overlapped: OVERLAPPED;
DataBuf: TWSABUF;
Buffer: array [ 0 .. 1024 ] of CHAR;
BytesSEND: DWORD;
BytesRECV: DWORD;
end;
// 单句柄数据结构
LPPER_HANDLE_DATA = ^ PER_HANDLE_DATA;
PER_HANDLE_DATA = packed record
Socket: TSocket;
end;
/**/
/// 代码测试用过 ,一直没有机会在实际项目中应用,遗憾!~~~~~~~~~
procedure TServerClientThread.Execute;
var
i ,Flags: Integer;
sto : sockaddr_in;
begin
// 加载SOCKET。
if WSAStartUp($ 202 , wsData) <> 0 then
begin
WSACleanup();
end;
// 创建一个完成端口。
CompletionPort: = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0 , 0 , 0 );
// 根据CPU的数量创建CPU*2数量的工作者线程。
GetSystemInfo(LocalSI);
for I: = 0 to LocalSI.dwNumberOfProcessors * 2 - 1 do
begin
hThread : = CreateThread(nil, 0 , @ServerWorkerThread, Pointer(CompletionPort), 0 , ThreadID);
if (hThread = 0 ) then
begin
Exit;
end;
CloseHandle(hThread);
end;
// 创建一个套接字,将此套接字和一个端口绑定并监听此端口
Listensc: = WSASocket(AF_INET,SOCK_STREAM, 0 ,Nil, 0 ,WSA_FLAG_OVERLAPPED);
if Listensc = SOCKET_ERROR then
begin
closesocket(Listensc);
WSACleanup();
end;
sto.sin_family: = AF_INET;
sto.sin_port: = htons( 18715 );
sto.sin_addr.s_addr: = htonl(INADDR_ANY); // inet_addr('10.0.0.187');
// 将此套接字和一个端口绑定并监听此端口
if bind(Listensc,sto, sizeof (sto)) = SOCKET_ERROR then
begin
closesocket(Listensc);
end;
listen(Listensc, 20 );
while (TRUE) do
begin
// 当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字Acceptsc。
// 这个套接字就是和客户端通信的时候使用的套接字。
Acceptsc: = WSAAccept(Listensc, nil, nil, nil, 0 );
// 判断Acceptsc套接字创建是否成功,如果不成功则退出
if (Acceptsc = SOCKET_ERROR) then
begin
closesocket(Listensc);
exit;
end;
// 创建一个“单句柄数据结构”将Acceptsc套接字绑定
PerHandleData : = LPPER_HANDLE_DATA(GlobalAlloc(GPTR, sizeof (PER_HANDLE_DATA)));
if (PerHandleData = nil) then
begin
exit;
end;
PerHandleData.Socket : = Acceptsc;
// 。将套接字、完成端口和“单句柄数据结构”三者绑定在一起。
if (CreateIoCompletionPort(Acceptsc, CompletionPort, DWORD(PerHandleData), 0 ) = 0 ) then
begin
exit;
end;
// 创建一个“单IO数据结构”其中将PerIoData.BytesSEND 和PerIoData.BytesRECV 均设置成0。
// 说明此“单IO数据结构”是用来接受的。
ZeroMemory(@PerIoData.Overlapped, sizeof (OVERLAPPED));
PerIoData.BytesSEND : = 0 ;
PerIoData.BytesRECV : = 0 ;
PerIoData.DataBuf.len : = 1024 ;
// ????????????????
PerIoData.DataBuf.buf : = @PerIoData.Buffer;
Flags : = 0 ;
// 用此“单IO数据结构”来接受Acceptsc套接字的数据。
if (WSARecv(Acceptsc, @(PerIoData.DataBuf), 1 , @RecvBytes, @Flags,@(PerIoData.Overlapped), nil) = SOCKET_ERROR) then
begin
if (WSAGetLastError() <> ERROR_IO_PENDING) then
begin
exit;
end
end;
end;
end;
/**/
// 工作者线程
function ServerWorkerThread(CompletionPortID:Pointer):Integer;stdcall;
var
CompletionPort: THANDLE;
BytesTransferred: DWORD ;
// Overlapped: POVERLAPPED;
PerHandleData: LPPER_HANDLE_DATA ;
PerIoData: LPPER_IO_OPERATION_DATA ;
SendBytes, RecvBytes: DWORD;
Flags: DWORD;
TempSc : TSocket;
begin
CompletionPort: = THANDLE(CompletionPortID);
// 得到创建线程是传递过来的IOCP
while (TRUE) do
begin
// 工作者线程会停止到GetQueuedCompletionStatus函数处,直到接受到数据为止
// PerIoData
if (GetQueuedCompletionStatus(CompletionPort, BytesTransferred,DWORD(PerHandleData), POverlapped(PerIoData), INFINITE) = False) then
begin
// 当客户端连接断开或者客户端调用closesocket函数的时候,函数GetQueuedCompletionStatus会返回错误。如果我们加入心跳后,在这里就可以来判断套接字是否依然在连接。
if PerHandleData <> nil then
begin
closesocket(PerHandleData.Socket);
GlobalFree(DWORD(PerHandleData));
end;
if PerIoData <> nil then
begin
GlobalFree(DWORD(PerIoData));
end;
continue ;
end;
if (BytesTransferred = 0 ) then
begin
// 当客户端调用shutdown函数来从容断开的时候,我们可以在这里进行处理。
if PerHandleData <> nil then
begin
// Client断开事件 ???
TempSc: = PerHandleData.Socket;
shutdown(PerHandleData.Socket, 1 );
closesocket(PerHandleData.Socket);
GlobalFree(DWORD(PerHandleData));
end;
if PerIoData <> nil then
begin
GlobalFree(DWORD(PerIoData));
end;
continue ;
end;
// 在上一篇中我们说到IOCP可以接受来自客户端的数据和自己发送出去的数据,
// 两种数据的区别在于我们定义的结构成员BytesRECV和BytesSEND的值。
// 所以下面我们来判断数据的来自方向。因为我们发送出去数据的时候我们设置了结构成员BytesSEND。
// 所以如果BytesRECV=0同时BytesSEND=0那么此数据就是我们接受到的客户端数据。
// (这种区分方法不是唯一的,个人可以有自己的定义方法。只要可以区分开数据来源就可以。)
if (PerIoData.BytesRECV = 0 ) and (PerIoData.BytesSEND = 0 ) then
begin
PerIoData.BytesRECV : = BytesTransferred;
PerIoData.BytesSEND : = 0 ;
end else
begin
PerIoData.BytesSEND : = BytesTransferred;
PerIoData.BytesRECV : = 0 ;
end;
// 当是接受来自客户端的数据是,我们进行数据的处理。
if (PerIoData.BytesRECV > PerIoData.BytesSEND) then
begin
PerIoData.DataBuf.buf : = PerIoData.Buffer + PerIoData.BytesSEND;
PerIoData.DataBuf.len : = PerIoData.BytesRECV - PerIoData.BytesSEND;
// 这时变量PerIoData.Buffer就是接受到的客户端数据。数据的长度是PerIoData.DataBuf.len 你可以对数据进行相关的处理了。
// 产生一个事件?????????
// FOnClientRead
// if Assigned(FOnClientRead) then
// FOnClientRead(TempSc,PerIoData.DataBuf.buf,PerIoData.DataBuf.len);
// 当我们将数据处理完毕以后,应该将此套接字设置为结束状态,同时初始化和它绑定在一起的数据结构。
ZeroMemory(@(PerIoData.Overlapped), sizeof (OVERLAPPED));
PerIoData.BytesRECV : = 0 ;
Flags : = 0 ;
ZeroMemory(@(PerIoData.Overlapped), sizeof (OVERLAPPED));
PerIoData.DataBuf.len : = 0 ; // DATA_BUFSIZE
ZeroMemory(@PerIoData.Buffer, sizeof (@PerIoData.Buffer));
PerIoData.DataBuf.buf : = @PerIoData.Buffer;
if (WSARecv(PerHandleData.Socket, @(PerIoData.DataBuf), 1 , @RecvBytes, @Flags,@(PerIoData.Overlapped), nil) = SOCKET_ERROR) then
begin
if (WSAGetLastError() <> ERROR_IO_PENDING) then
begin
if PerHandleData <> nil then
begin
TempSc: = PerHandleData.Socket;
closesocket(PerHandleData.Socket);
GlobalFree(DWORD(PerHandleData));
end;
if PerIoData <> nil then
begin
GlobalFree(DWORD(PerIoData));
end;
continue ;
end;
end;
end // 当我们判断出来接受的数据是我们发送出去的数据的时候,在这里我们清空我们申请的内存空间
else
begin
GlobalFree(DWORD(PerIoData));
end;
end;
end;
/**/ /////
// 1年前写的代码,根据网上一位强哥写的blog 根据自己的理解,琢磨出来的。
// 只是实现了的功能,没有在仔细调试过,也没有实际环境中经过压力测试。
// 如果哪位大哥要用到iocp请联系我。
LPVOID = Pointer;
LPPER_IO_OPERATION_DATA = ^ PER_IO_OPERATION_DATA ;
PER_IO_OPERATION_DATA = packed record
Overlapped: OVERLAPPED;
DataBuf: TWSABUF;
Buffer: array [ 0 .. 1024 ] of CHAR;
BytesSEND: DWORD;
BytesRECV: DWORD;
end;
// 单句柄数据结构
LPPER_HANDLE_DATA = ^ PER_HANDLE_DATA;
PER_HANDLE_DATA = packed record
Socket: TSocket;
end;
/**/
/// 代码测试用过 ,一直没有机会在实际项目中应用,遗憾!~~~~~~~~~
procedure TServerClientThread.Execute;
var
i ,Flags: Integer;
sto : sockaddr_in;
begin
// 加载SOCKET。
if WSAStartUp($ 202 , wsData) <> 0 then
begin
WSACleanup();
end;
// 创建一个完成端口。
CompletionPort: = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0 , 0 , 0 );
// 根据CPU的数量创建CPU*2数量的工作者线程。
GetSystemInfo(LocalSI);
for I: = 0 to LocalSI.dwNumberOfProcessors * 2 - 1 do
begin
hThread : = CreateThread(nil, 0 , @ServerWorkerThread, Pointer(CompletionPort), 0 , ThreadID);
if (hThread = 0 ) then
begin
Exit;
end;
CloseHandle(hThread);
end;
// 创建一个套接字,将此套接字和一个端口绑定并监听此端口
Listensc: = WSASocket(AF_INET,SOCK_STREAM, 0 ,Nil, 0 ,WSA_FLAG_OVERLAPPED);
if Listensc = SOCKET_ERROR then
begin
closesocket(Listensc);
WSACleanup();
end;
sto.sin_family: = AF_INET;
sto.sin_port: = htons( 18715 );
sto.sin_addr.s_addr: = htonl(INADDR_ANY); // inet_addr('10.0.0.187');
// 将此套接字和一个端口绑定并监听此端口
if bind(Listensc,sto, sizeof (sto)) = SOCKET_ERROR then
begin
closesocket(Listensc);
end;
listen(Listensc, 20 );
while (TRUE) do
begin
// 当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字Acceptsc。
// 这个套接字就是和客户端通信的时候使用的套接字。
Acceptsc: = WSAAccept(Listensc, nil, nil, nil, 0 );
// 判断Acceptsc套接字创建是否成功,如果不成功则退出
if (Acceptsc = SOCKET_ERROR) then
begin
closesocket(Listensc);
exit;
end;
// 创建一个“单句柄数据结构”将Acceptsc套接字绑定
PerHandleData : = LPPER_HANDLE_DATA(GlobalAlloc(GPTR, sizeof (PER_HANDLE_DATA)));
if (PerHandleData = nil) then
begin
exit;
end;
PerHandleData.Socket : = Acceptsc;
// 。将套接字、完成端口和“单句柄数据结构”三者绑定在一起。
if (CreateIoCompletionPort(Acceptsc, CompletionPort, DWORD(PerHandleData), 0 ) = 0 ) then
begin
exit;
end;
// 创建一个“单IO数据结构”其中将PerIoData.BytesSEND 和PerIoData.BytesRECV 均设置成0。
// 说明此“单IO数据结构”是用来接受的。
ZeroMemory(@PerIoData.Overlapped, sizeof (OVERLAPPED));
PerIoData.BytesSEND : = 0 ;
PerIoData.BytesRECV : = 0 ;
PerIoData.DataBuf.len : = 1024 ;
// ????????????????
PerIoData.DataBuf.buf : = @PerIoData.Buffer;
Flags : = 0 ;
// 用此“单IO数据结构”来接受Acceptsc套接字的数据。
if (WSARecv(Acceptsc, @(PerIoData.DataBuf), 1 , @RecvBytes, @Flags,@(PerIoData.Overlapped), nil) = SOCKET_ERROR) then
begin
if (WSAGetLastError() <> ERROR_IO_PENDING) then
begin
exit;
end
end;
end;
end;
/**/
// 工作者线程
function ServerWorkerThread(CompletionPortID:Pointer):Integer;stdcall;
var
CompletionPort: THANDLE;
BytesTransferred: DWORD ;
// Overlapped: POVERLAPPED;
PerHandleData: LPPER_HANDLE_DATA ;
PerIoData: LPPER_IO_OPERATION_DATA ;
SendBytes, RecvBytes: DWORD;
Flags: DWORD;
TempSc : TSocket;
begin
CompletionPort: = THANDLE(CompletionPortID);
// 得到创建线程是传递过来的IOCP
while (TRUE) do
begin
// 工作者线程会停止到GetQueuedCompletionStatus函数处,直到接受到数据为止
// PerIoData
if (GetQueuedCompletionStatus(CompletionPort, BytesTransferred,DWORD(PerHandleData), POverlapped(PerIoData), INFINITE) = False) then
begin
// 当客户端连接断开或者客户端调用closesocket函数的时候,函数GetQueuedCompletionStatus会返回错误。如果我们加入心跳后,在这里就可以来判断套接字是否依然在连接。
if PerHandleData <> nil then
begin
closesocket(PerHandleData.Socket);
GlobalFree(DWORD(PerHandleData));
end;
if PerIoData <> nil then
begin
GlobalFree(DWORD(PerIoData));
end;
continue ;
end;
if (BytesTransferred = 0 ) then
begin
// 当客户端调用shutdown函数来从容断开的时候,我们可以在这里进行处理。
if PerHandleData <> nil then
begin
// Client断开事件 ???
TempSc: = PerHandleData.Socket;
shutdown(PerHandleData.Socket, 1 );
closesocket(PerHandleData.Socket);
GlobalFree(DWORD(PerHandleData));
end;
if PerIoData <> nil then
begin
GlobalFree(DWORD(PerIoData));
end;
continue ;
end;
// 在上一篇中我们说到IOCP可以接受来自客户端的数据和自己发送出去的数据,
// 两种数据的区别在于我们定义的结构成员BytesRECV和BytesSEND的值。
// 所以下面我们来判断数据的来自方向。因为我们发送出去数据的时候我们设置了结构成员BytesSEND。
// 所以如果BytesRECV=0同时BytesSEND=0那么此数据就是我们接受到的客户端数据。
// (这种区分方法不是唯一的,个人可以有自己的定义方法。只要可以区分开数据来源就可以。)
if (PerIoData.BytesRECV = 0 ) and (PerIoData.BytesSEND = 0 ) then
begin
PerIoData.BytesRECV : = BytesTransferred;
PerIoData.BytesSEND : = 0 ;
end else
begin
PerIoData.BytesSEND : = BytesTransferred;
PerIoData.BytesRECV : = 0 ;
end;
// 当是接受来自客户端的数据是,我们进行数据的处理。
if (PerIoData.BytesRECV > PerIoData.BytesSEND) then
begin
PerIoData.DataBuf.buf : = PerIoData.Buffer + PerIoData.BytesSEND;
PerIoData.DataBuf.len : = PerIoData.BytesRECV - PerIoData.BytesSEND;
// 这时变量PerIoData.Buffer就是接受到的客户端数据。数据的长度是PerIoData.DataBuf.len 你可以对数据进行相关的处理了。
// 产生一个事件?????????
// FOnClientRead
// if Assigned(FOnClientRead) then
// FOnClientRead(TempSc,PerIoData.DataBuf.buf,PerIoData.DataBuf.len);
// 当我们将数据处理完毕以后,应该将此套接字设置为结束状态,同时初始化和它绑定在一起的数据结构。
ZeroMemory(@(PerIoData.Overlapped), sizeof (OVERLAPPED));
PerIoData.BytesRECV : = 0 ;
Flags : = 0 ;
ZeroMemory(@(PerIoData.Overlapped), sizeof (OVERLAPPED));
PerIoData.DataBuf.len : = 0 ; // DATA_BUFSIZE
ZeroMemory(@PerIoData.Buffer, sizeof (@PerIoData.Buffer));
PerIoData.DataBuf.buf : = @PerIoData.Buffer;
if (WSARecv(PerHandleData.Socket, @(PerIoData.DataBuf), 1 , @RecvBytes, @Flags,@(PerIoData.Overlapped), nil) = SOCKET_ERROR) then
begin
if (WSAGetLastError() <> ERROR_IO_PENDING) then
begin
if PerHandleData <> nil then
begin
TempSc: = PerHandleData.Socket;
closesocket(PerHandleData.Socket);
GlobalFree(DWORD(PerHandleData));
end;
if PerIoData <> nil then
begin
GlobalFree(DWORD(PerIoData));
end;
continue ;
end;
end;
end // 当我们判断出来接受的数据是我们发送出去的数据的时候,在这里我们清空我们申请的内存空间
else
begin
GlobalFree(DWORD(PerIoData));
end;
end;
end;
/**/ /////
// 1年前写的代码,根据网上一位强哥写的blog 根据自己的理解,琢磨出来的。
// 只是实现了的功能,没有在仔细调试过,也没有实际环境中经过压力测试。
// 如果哪位大哥要用到iocp请联系我。