The original article "Windows Network Programming Technology" Chapter 8 Completed Port Model
Since the original book is attached to C code, I translated it into a Delphi code.
Where winsock2.pas does not bring in Delphi, you have another download http://jungla.dit.upm.es/~bti/files/winsock2.pas
Program completionio;
{$ Apptype console}
Uses sysutils, Winsock2 in 'Winsock2.Pas', MAINS IN 'MAINS.PAS'
Begin main (); end.
// Module Name: iocmplt.cpp //// Description: //// This sample illustrates how to develop a simple echo server Winsock // application using the completeion port I / O model This // sample is implemented as a console. -style application and simply prints // messages when connections are established and removed from the server.// The application listens for TCP connections on port 5150 and accepts them // as they arrive. When this application receives data from a client, it / / Simply echos (this is why we call it an echo server) The Data Back In // It's Original Form Until The Close Closes The connection.//// 2005-2-5 // CPP Convert to Delphi PAS by johnson //
UNIT MAINS;
Interface
Uses Windows, Winsock2, Winsock, Sysutils;
Const port = 5150; data_bufsize = 8192;
type LPVOID = Pointer; LPPER_IO_OPERATION_DATA = ^ PER_IO_OPERATION_DATA; PER_IO_OPERATION_DATA = packed record Overlapped: OVERLAPPED; DataBuf: TWSABUF; Buffer: array [0..DATA_BUFSIZE] of CHAR; BytesSEND: DWORD; BytesRECV: DWORD; end;
LPPER_HANDLE_DATA = ^ per_handle_data; per_handle_data = packet: tsocket;
PROCEDURE main;
IMPLEMENTATION
Function ServerWorkerthRead (CompletionPortid: LPVOID): DWORD; stdcall;
Procedure Printf (FMT: String; Num: Integer); Begin Writeln (Format (FMT, [NUM]); END;
procedure main; var InternetAddr: SOCKADDR_IN; Listen: TSOCKET; Accept: TSOCKET; CompletionPort: THANDLE; SystemInfo: SYSTEM_INFO; PerHandleData: LPPER_HANDLE_DATA; PerIoData: LPPER_IO_OPERATION_DATA; i: Integer; RecvBytes: DWORD; Flags: DWORD; ThreadID: DWORD; wsaData: TWSADATA; RET: DWORD; ThreadHandle: Thandle; Begin Ret: = WSAStartup ($ 0202, WSADATA); IF (RET <> 0) THEN BEGINTF ('WSAStartup Failed with Error% D', RET); End;
// Setup an I / O completion port CompletionPort: = CreateIoCompletionPort (INVALID_HANDLE_VALUE, 0, 0, 0); if (CompletionPort = 0) then begin printf ( 'CreateIoCompletionPort failed with error:% d', GetLastError ());. Exit ;
// DETERMINE How Many Processors Are On The System.
GetSystemInfo (SystemInfo);
.
For i: = 0 to systemInfo.dwnumberofprocessors * 2 - 1 do begin
// Create a server worker thread and pass the completion port to the thread ThreadHandle: = CreateThread (nil, 0, @ServerWorkerThread, Pointer (CompletionPort), 0, ThreadID); if (ThreadHandle = 0) then begin printf ( 'CreateThread. () Failed with error% d ', getLastError ()); EXIT; END;
// Close The Thread Handle CloseHandle (ThreadHandle); END;
// Create a listening socket Listen: = WSASocket (AF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED); if (Listen = INVALID_SOCKET) then begin printf ( 'WSASocket () failed with error% d', WSAGetLastError ()); EXIT;
InternetAddr.sin_family: = AF_INET; InternetAddr.sin_addr.s_addr: = htonl (INADDR_ANY); InternetAddr.sin_port: = htons (PORT); if (bind (Listen, InternetAddr, sizeof (InternetAddr)) = SOCKET_ERROR) then begin printf ( ' Bind () failed with error% d ', wsagetlasterror ()); exit;
// Prepare Socket for Listening
IF (Winsock.Listen (Listen, 5) = Socket_ERROR) THEN BEGINTF ('listen () failed with error% d', wsagetlasterror ()); EXIT; ELSE BEGIN PRINTF ('Server Listen on Port =% D .. . ', Port);
// Accept connections and assign to the completion port while (TRUE) do begin Accept:. = WSAAccept (Listen, nil, nil, nil, 0); if (Accept = SOCKET_ERROR) then begin printf ( 'WSAAccept () failed with error % d ', wsagetlasterror ()); exit;
// Create a socket information structure to associate with the socket PerHandleData: = LPPER_HANDLE_DATA (GlobalAlloc (GPTR, sizeof (PER_HANDLE_DATA))); if (PerHandleData = nil) then begin printf ( 'GlobalAlloc () failed with error% d', WSAGetLastError ());
// Associate The Accepted Socket with The Original Completion Port.
Printf ('socket number% d connection ", accept); perhandaTa.socket: = accept;
IF (CreateiocompletionPort (Accept, Completionport, DWORD (Perhandata), 0) = 0) The begin Printf ('createioCompletionPort () failed with error% d', wsagetlasterror (); exit;
// Create Per I / O Socket Information Structure To Associate With the // Wsarecv Call Below.
PerIoData: = LPPER_IO_OPERATION_DATA (GlobalAlloc (GPTR, sizeof (PER_IO_OPERATION_DATA))); if (PerIoData = nil) then begin printf ( 'GlobalAlloc () failed with error% d', WSAGetLastError ()); exit; end; ZeroMemory (@PerIoData .Overlapped); periodata.bytessend: = 0; Periodata.bytesRecv: = 0; Periodata.Databuf.len: = DATA_BUFSIZE; Periodata.DATABUF.BUF: = @ periodata.buffer;
Flags: = 0; IF (Wsarecv (Accept, @ (Periodata.Database), 1, @recvbytes, @flags, @ (PERIODATA.OVERLAPPED), NIL) = SOCKET_ERROR) THEN BEGIN IF (Wsagetlasterror () " Begin Printf ('wsarecv () failed with error% d', wsagetlasterror ()); exit; end end;
END;
function ServerWorkerThread (CompletionPortID: LPVOID): DWORD; stdcall; var CompletionPort: THANDLE; BytesTransferred: DWORD; // Overlapped: POVERLAPPED; PerHandleData: LPPER_HANDLE_DATA; PerIoData: LPPER_IO_OPERATION_DATA; SendBytes, RecvBytes: DWORD; Flags: DWORD; begin CompletionPort: = THANDLE CompletionPortID;
Result: = 0;
While (True) Do Begin
if (GetQueuedCompletionStatus (CompletionPort, BytesTransferred, DWORD (PerHandleData), POverlapped (PerIoData), INFINITE) = False) then begin printf ( 'GetQueuedCompletionStatus failed with error% d', GetLastError ()); exit; end;
.
if (BytesTransferred = 0) then begin printf ( 'Closing socket% d /', PerHandleData.Socket); if (closesocket (PerHandleData.Socket) = SOCKET_ERROR) then begin printf ( 'closesocket () failed with error% d', WSAGetLastError ());
GlobalFree (DWORD (PerhandaTa)); GlobalFree (DWORDATA); Continue; End;
// Check to see if the BytesRECV field equals zero. If this is so, then // this means a WSARecv call just completed so update the BytesRECV field // with the BytesTransferred value from the completed WSARecv () call.
IF (Periodata.bytesRecv = 0) The begin Periodata.bytesRecv: = bytestransferred; periodata.bytessend: = 0; Else Begin Periodata.bytessend: = periodata.bytessend bytestransferred; end;
IF (Periodata.BytesRecv> Periodata.bytessend) THEN Begin
// Post Another Wsasend () Request. // Since Wsasend () IS NOT Gauranteed To send all of the bytes request, // Continue Posting wsasend () Calls UnTil All Received Bytes Are SENT.
ZeromeMory (@ (periodata.overlapped), sizeof (overlapped));
Periodata.DatabaseBuf.buf: = periodata.buffer periodata.bytessend; periodata.Databasesend;
if (WSASend (PerHandleData.Socket, @ (PerIoData.DataBuf), 1, @SendBytes, 0, @ (PerIoData.Overlapped), nil) = SOCKET_ERROR) then begin if (WSAGetLastError () <> ERROR_IO_PENDING) then begin printf ( ' Wsasend () failed with error% d ', wsagetlasterror ()); exit; end; end; end else begin periodata.bytesRecv: = 0; // Now That there is no moretes to send post another wsarecv () Request.
Flags: = 0; ZeromeMory (@ (periodata.overlapped), sizeof (overlapped);
Periodata.Database; = data_bufsize; periodata.databasef.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 printf ( 'Wsarecv () failed with error% d', wsagetlasterror ()); EXIT; END; end; end; end;
End.