核心提示:首先我们来了解一些在使用IOCP的时候需要使用的一些结构!(1):单IO数据结构LPVOID=Pointer;LPPER_IO_OPERATION_DATA=^PER_IO_OPERATION_DAT...
首先我们来了解一些在使用IOCP的时候需要使用的一些结构!(1):单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;
上面的结构中Overlapped: OVERLAPPED;和DataBuf: TWSABUF;是固定的结构类型。Buffer: array [0..1024] of CHAR;是用来保存接受数据的缓存。BytesSEND: DWORD;用来标志发送数据的长度。BytesRECV: DWORD;用来标志接受数据的长度。因为完成端口的工作者线程可以接受到来自客户端的数据,同时还可以接受到自己发送给客户端的数据,所以我们使用BytesSEND,BytesRECV变量来说是用来区分这次的数据是来自客户端的数据还是自己发送出去的数据。详细的使用方法,我会在下面详细说明。
(2):“单句柄数据结构”
LPPER_HANDLE_DATA = ^ PER_HANDLE_DATA;
PER_HANDLE_DATA = packed record
Socket: TSocket;
end;
下来我从编写一个完成端口的为例说明。
if WSAStartUp($202, wsData) <> 0 then
begin
WSACleanup();
end;
加载SOCKET。我使用的是2.2版为了后面方便加入心跳。
CompletionPort:=CreateIOCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
创建一个完成端口。
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;
根据CPU的数量创建CPU*2数量的工作者线程。
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(5500);
sto.sin_addr.s_addr:=htonl(INADDR_ANY);
if bind(Listensc,sto,sizeof(sto))=SOCKET_ERROR then
begin
closesocket(Listensc);
end;
listen(Listensc,20);
创建一个套接字,将此套接字和一个端口绑定并监听此端口。
while (TRUE) do
begin
Acceptsc:= WSAAccept(Listensc, nil, nil, nil, 0);
当客户端有连接请求的时候,WSAAccept函数会新创建一个套接字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;
创建一个“单句柄数据结构”将Acceptsc套接字绑定。
if (CreateIoCompletionPort(Acceptsc, CompletionPort, DWORD(PerHandleData), 0) = 0) then
begin
exit;
end;
将套接字、完成端口和“单句柄数据结构”三者绑定在一起。
PerIoData := LPPER_IO_OPERATION_DATA(GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA)));
if (PerIoData = nil) then
begin
exit;
end;
ZeroMemory(@PerIoData.Overlapped, sizeof(OVERLAPPED));
PerIoData.BytesSEND := 0;
PerIoData.BytesRECV := 0;
PerIoData.DataBuf.len := 1024;
PerIoData.DataBuf.buf := @PerIoData.Buffer;
Flags := 0;
创建一个“单IO数据结构”其中将PerIoData.BytesSEND 和PerIoData.BytesRECV 均设置成0。说明此“单IO数据结构”是用来接受的。
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;
用此“单IO数据结构”来接受Acceptsc套接字的数据。
end;
创建IOCP的工作已经完成,下一次我将写IOCP的工作者线程的处理方法,谢谢!
今天我写一下关于DELPHI编写完成端口(IOCP)的工作者线程中的东西。希望各位能提出批评意见。
上次我写了关于常见IOCP的代码,对于IOCP来说,接受到客户端发送过来和自己发送出去的数据都是从工作者线程中得到。代码和解释如下:
function ServerWorkerThread(CompletionPortID:Pointer):Integer;stdcall;
begin
CompletionPort:=THANDLE(CompletionPortID);
//得到创建线程是传递过来的IOCP
while(TRUE) do
begin
//工作者线程会停止到GetQueuedCompletionStatus函数处,直到接受到数据为止
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
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 你可以对数据进行相关的处理了。
//.......
//当我们将数据处理完毕以后,应该将此套接字设置为结束状态,同时初始化和它绑定在一起的数据结构。
ZeroMemory(@(PerIoData.Overlapped), sizeof(OVERLAPPED));
PerIoData.BytesRECV := 0;
Flags := 0;
ZeroMemory(@(PerIoData.Overlapped), sizeof(OVERLAPPED));
PerIoData.DataBuf.len := 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;
到此,工作者线程已经处理完成。接受数据已经没有问题了。下一篇中我将会写出,如何时候IOCP来发送数据代码。今天的代码中应该对PerIoData.BytesRECV > PerIoData.BytesSEND单另解说一下。其实如果按照上面的例子去编写工作者线程,会觉得编写出来的代码会很不稳定,接受到的数据总是有错位的现象。原因是TCP协议中有数据分片的概念,这个以后我会将我处理这个的代码分享给大家。