//单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-1do 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请联系我。