IOCP完成端口介绍:
完成端口模型是Windows平台下SOCKET端口模型最为复杂的一种I/O模型。如果一个应用程序需要同时管理为数众多的套接字,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,采用完成端口模型,往往可以达到最佳的系统性能。
完成端口可以管理成千上万的连接,长连接传文件可以支持5000个以上,长连接命令交互可以支持20000个以上。这么大并发的连接,更需要考虑的是应用场景,按照100M的网卡传输速度12.5MB/S,如果是5000个传文件连接,则每个连接能分到的速度2.56KB/S;如果是20000个命令交互连接,则每个连接分到的吞吐量是655B/S,这种速度的吞吐量对很多应用是不满足,这时就要考虑加大网卡的传输速度或实现水平扩展,这个我们后续会介绍。
完成端口是由系统内核管理多个线程之间的切换,比外部实现线程池性能要高,CPU利用率上内核和用户态可以达到1:1,很多应用线程池是无法达到的。因此同等连接数的情况下,完成端口要比INDY的TCPServer传输速度要快,吞吐量更高。
要使用完成端口,主要是以下三个函数的使用:CreateIoCompletionPort、GetQueuedCompletionStatus、PostQueuedCompletionStatus。
CreateIoCompletionPort的功能是:1、创建一个完成端口对象;2、将一个句柄和完成端口关联在一起;GetQueuedCompletionStatus是获取完成端口状态,是阻塞式调用,在指定时间内如果没有事件通知,会一直等待;PostQueuedCompletionStatus用于向完成端口投递一个完成事件通知。
function CreateIoCompletionPort(FileHandle, ExistingCompletionPort: THandle; CompletionKey, NumberOfConcurrentThreads: DWORD): THandle; stdcall;NumberOfConcurrentThreads参数定义了在一个完成端口上,同时允许执行的线程数量。将NumberOfConcurrentThreads设为0表示每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程场景切换。因此可以使用下列语句来创建一个完成端口FIocpHandle := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
执行线程个数
创建完成端口后,就可以将套接字句柄与对象关联在一起,这时就需要创建工作者线程,以便在完成端口收到数据后,为完成端口提供处理数据线程。到底创建多少个线程为完成端口服务,这个是完成端口最为复杂的一方面,创建多了线程会造成频繁的线程场景切换;创建少了线程如果某一个处理非常耗时,如连接数据库、读写文件,又会造成完成端口拥塞,因此这个参数需要提供设置,并根据最终的应用场景反复测试得出一个结果。一般的经验值是设置为CPU的个数*2+4;
IOCP完成端口一般使用步骤
1、创建一个完成端口;
2、判断系统内安装了多少个处理器;
3、创建工作者线程;
4、创建一个SOCKET套接字开始监听;
5、使用Accept接收连接;
6、调用CreateIoCompletionPort将连接和完成端口绑定在一起;
7、投递接收数据请求
8、工作者线程调用GetQueuedCompletionStatus获取事件通知,处理数据;
IOCP控件核心代码
第1步到第4步实现代码:
- procedure TIocpServer.Open;
- var
- WsaData: TWsaData;
- iNumberOfProcessors, i, iWorkThreadCount: Integer;
- WorkThread: TWorkThread;
- Addr: TSockAddr;
- begin
- if WSAStartup($0202, WsaData) <> 0 then //初始化SOCKET
- raise ESocketError.Create(GetLastWsaErrorStr);
- FIocpHandle := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); //创建一个完成端口
- if FIocpHandle = 0 then
- raise ESocketError.Create(GetLastErrorStr);
- FSocket := WSASocket(PF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED); //创建一个SOCKET句柄
- if FSocket = INVALID_SOCKET then
- raise ESocketError.Create(GetLastWsaErrorStr);
- FillChar(Addr, SizeOf(Addr), 0);
- Addr.sin_family := AF_INET;
- Addr.sin_port := htons(FPort);
- Addr.sin_addr.S_addr := htonl(INADDR_ANY); //在任何地址上监听,如果有多块网卡,会每块都监听,也可以指定只监听某一个IP地址
- if bind(FSocket, @Addr, SizeOf(Addr)) <> 0 then //把SOCKET句柄绑定端口
- raise ESocketError.Create(GetLastWsaErrorStr);
- if listen(FSocket, MaxInt) <> 0 then
- raise ESocketError.Create(GetLastWsaErrorStr);
- iNumberOfProcessors := GetCPUCount; //获取CPU个数
- iWorkThreadCount := iNumberOfProcessors * 2 + 4; //由于服务器处理可能比较费时间,因此线程设为CPU*2+4
- if iWorkThreadCount < FMinWorkThrCount then //限定最大工作者线程和最小工作者线程
- iWorkThreadCount := FMinWorkThrCount;
- if iWorkThreadCount > FMaxWorkThrCount then
- iWorkThreadCount := FMaxWorkThrCount;
- for i := 0 to iWorkThreadCount - 1 do //创建工作者线程
- begin
- WorkThread := TWorkThread.Create(Self, True);
- FWorkThreads.Add(WorkThread);
- WorkThread.Resume;
- end;
- FAcceptThreadPool.Active := True; //启动监听线程池
- FAcceptThread := TAcceptThread.Create(Self, True); //启动监听线程
- FAcceptThread.Resume;
- end;
第5步和第6步实现代码:procedure TIocpServer.Open; var WsaData: TWsaData; iNumberOfProcessors, i, iWorkThreadCount: Integer; WorkThread: TWorkThread; Addr: TSockAddr; begin if WSAStartup($0202, WsaData) <> 0 then //初始化SOCKET raise ESocketError.Create(GetLastWsaErrorStr); FIocpHandle := CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); //创建一个完成端口 if FIocpHandle = 0 then raise ESocketError.Create(GetLastErrorStr); FSocket := WSASocket(PF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED); //创建一个SOCKET句柄 if FSocket = INVALID_SOCKET then raise ESocketError.Create(GetLastWsaErrorStr); FillChar(Addr, SizeOf(Addr), 0); Addr.sin_family := AF_INET; Addr.sin_port := htons(FPort); Addr.sin_addr.S_addr := htonl(INADDR_ANY); //在任何地址上监听,如果有多块网卡,会每块都监听,也可以指定只监听某一个IP地址 if bind(FSocket, @Addr, SizeOf(Addr)) <> 0 then //把SOCKET句柄绑定端口 raise ESocketError.Create(GetLastWsaErrorStr); if listen(FSocket, MaxInt) <> 0 then raise ESocketError.Create(GetLastWsaErrorStr); iNumberOfProcessors := GetCPUCount; //获取CPU个数 iWorkThreadCount := iNumberOfProcessors * 2 + 4; //由于服务器处理可能比较费时间,因此线程设为CPU*2+4 if iWorkThreadCount < FMinWorkThrCount then //限定最大工作者线程和最小工作者线程 iWorkThreadCount := FMinWorkThrCount; if iWorkThreadCount > FMaxWorkThrCount then iWorkThreadCount := FMaxWorkThrCount; for i := 0 to iWorkThreadCount - 1 do //创建工作者线程 begin WorkThread := TWorkThread.Create(Self, True); FWorkThreads.Add(WorkThread); WorkThread.Resume; end; FAcceptThreadPool.Active := True; //启动监听线程池 FAcceptThread := TAcceptThread.Create(Self, True); //启动监听线程 FAcceptThread.Resume; end;
- procedure TIocpServer.AcceptClient;
- var
- ClientSocket: TSocket;
- begin
- ClientSocket := WSAAccept(FSocket, nil, nil, nil, 0); //接收连接
- if ClientSocket <> INVALID_SOCKET then
- begin
- if not FActive then
- begin
- closesocket(ClientSocket);
- Exit;
- end;
- FAcceptThreadPool.PostSocket(ClientSocket); //这里使用线程池主要作用是为了判断发送的第一个字节身份标识,用来是判断协议类型
- end;
- end;
FAcceptThreadPool是一个用完成端口实现的线程池TIocpThreadPool,主要是有TCheckThread来判断完成端口的返回,并检测是否6S内有发送标志位上来,主要实现过程是响应OnConnect事件,并在OnConnect事件中判断是否允许连接。procedure TIocpServer.AcceptClient; var ClientSocket: TSocket; begin ClientSocket := WSAAccept(FSocket, nil, nil, nil, 0); //接收连接 if ClientSocket <> INVALID_SOCKET then begin if not FActive then begin closesocket(ClientSocket); Exit; end; FAcceptThreadPool.PostSocket(ClientSocket); //这里使用线程池主要作用是为了判断发送的第一个字节身份标识,用来是判断协议类型 end; end;
- procedure TIocpServer.CheckClient(const ASocket: TSocket);
- var
- SocketHandle: TSocketHandle;
- iIndex: Integer;
- ClientSocket: PClientSocket;
- begin
- SocketHandle := nil;
- if not DoConnect(ASocket, SocketHandle) then //如果不允许连接,则退出
- begin
- closesocket(ASocket);
- Exit;
- end;
- FSocketHandles.Lock; //加到列表中
- try
- iIndex := FSocketHandles.Add(SocketHandle);
- ClientSocket := FSocketHandles.Items[iIndex];
- finally
- FSocketHandles.UnLock;
- end;
- if CreateIoCompletionPort(ASocket, FIOCPHandle, DWORD(ClientSocket), 0) = 0 then //将连接和完成端口绑定在一起
- begin
- DoError('CreateIoCompletionPort', GetLastWsaErrorStr);
- FSocketHandles.Lock; //如果投递到列表中失败,则删除
- try
- FSocketHandles.Delete(iIndex);
- finally
- FSocketHandles.UnLock;
- end;
- end
- else
- begin
- SocketHandle.PreRecv(nil); //投递接收请求
- end;
- end;
procedure TIocpServer.CheckClient(const ASocket: TSocket); var SocketHandle: TSocketHandle; iIndex: Integer; ClientSocket: PClientSocket; begin SocketHandle := nil; if not DoConnect(ASocket, SocketHandle) then //如果不允许连接,则退出 begin closesocket(ASocket); Exit; end; FSocketHandles.Lock; //加到列表中 try iIndex := FSocketHandles.Add(SocketHandle); ClientSocket := FSocketHandles.Items[iIndex]; finally FSocketHandles.UnLock; end; if CreateIoCompletionPort(ASocket, FIOCPHandle, DWORD(ClientSocket), 0) = 0 then //将连接和完成端口绑定在一起 begin DoError('CreateIoCompletionPort', GetLastWsaErrorStr); FSocketHandles.Lock; //如果投递到列表中失败,则删除 try FSocketHandles.Delete(iIndex); finally FSocketHandles.UnLock; end; end else begin SocketHandle.PreRecv(nil); //投递接收请求 end; end;
- procedure TDMDispatchCenter.IcpSvrConnect(const ASocket: Cardinal;
- var AAllowConnect: Boolean; var SocketHandle: TSocketHandle);
- var
- BaseSocket: TBaseSocket;
- chFlag: Char;
- function GetSocket(const AEnable: Boolean; BaseSocketClass: TBaseSocketClass): TBaseSocket;
- begin
- if AEnable then
- Result := BaseSocketClass.Create(IcpSvr, ASocket)
- else
- Result := nil;
- end;
- begin
- if (GIniOptions.MaxSocketCount > 0) and (IcpSvr.SocketHandles.Count >= GIniOptions.MaxSocketCount) then
- begin
- AAllowConnect := False;
- Exit;
- end;
- if IcpSvr.ReadChar(ASocket, chFlag, 6*1000) then //必须在6S内收到标志
- begin
- case TSocketFlag(Byte(chFlag)) of
- sfSQL: BaseSocket := GetSocket(GIniOptions.SQLProtocol, TSQLSocket);
- sfUpload: BaseSocket := GetSocket(GIniOptions.UploadProtocol, TUploadSocket);
- sfDownload: BaseSocket := GetSocket(GIniOptions.DownloadProtocol, TDownloadSocket);
- sfControl: BaseSocket := GetSocket(GIniOptions.ControlProtocol, TControlSocket);
- sfLog: BaseSocket := GetSocket(GIniOptions.LogProtocol, TLogSocket);
- else
- BaseSocket := nil;;
- end;
- if BaseSocket <> nil then
- begin
- SocketHandle := BaseSocket;
- WriteLogMsg(ltDebug, Format('Client Connect, Local Address: %s:%d; Remote Address: %s:%d',
- [SocketHandle.LocalAddress, SocketHandle.LocalPort, SocketHandle.RemoteAddress, SocketHandle.RemotePort]));
- WriteLogMsg(ltDebug, Format('Client Count: %d', [IcpSvr.SocketHandles.Count + 1]));
- AAllowConnect := True;
- end
- else
- AAllowConnect := False;
- end
- else
- AAllowConnect := False;
- end;
其中ReadChar函数是用于判断指定时间是否有数据上来,函数实现过程用Select函数检测:procedure TDMDispatchCenter.IcpSvrConnect(const ASocket: Cardinal; var AAllowConnect: Boolean; var SocketHandle: TSocketHandle); var BaseSocket: TBaseSocket; chFlag: Char; function GetSocket(const AEnable: Boolean; BaseSocketClass: TBaseSocketClass): TBaseSocket; begin if AEnable then Result := BaseSocketClass.Create(IcpSvr, ASocket) else Result := nil; end; begin if (GIniOptions.MaxSocketCount > 0) and (IcpSvr.SocketHandles.Count >= GIniOptions.MaxSocketCount) then begin AAllowConnect := False; Exit; end; if IcpSvr.ReadChar(ASocket, chFlag, 6*1000) then //必须在6S内收到标志 begin case TSocketFlag(Byte(chFlag)) of sfSQL: BaseSocket := GetSocket(GIniOptions.SQLProtocol, TSQLSocket); sfUpload: BaseSocket := GetSocket(GIniOptions.UploadProtocol, TUploadSocket); sfDownload: BaseSocket := GetSocket(GIniOptions.DownloadProtocol, TDownloadSocket); sfControl: BaseSocket := GetSocket(GIniOptions.ControlProtocol, TControlSocket); sfLog: BaseSocket := GetSocket(GIniOptions.LogProtocol, TLogSocket); else BaseSocket := nil;; end; if BaseSocket <> nil then begin SocketHandle := BaseSocket; WriteLogMsg(ltDebug, Format('Client Connect, Local Address: %s:%d; Remote Address: %s:%d', [SocketHandle.LocalAddress, SocketHandle.LocalPort, SocketHandle.RemoteAddress, SocketHandle.RemotePort])); WriteLogMsg(ltDebug, Format('Client Count: %d', [IcpSvr.SocketHandles.Count + 1])); AAllowConnect := True; end else AAllowConnect := False; end else AAllowConnect := False; end;
- function TIocpServer.ReadChar(const ASocket: TSocket; var AChar: Char; const ATimeOutMS: Integer): Boolean;
- var
- iRead: Integer;
- begin
- Result := CheckTimeOut(ASocket, ATimeOutMS);
- if Result then
- begin
- iRead := recv(ASocket, AChar, 1, 0);
- Result := iRead = 1;
- end;
- end;
- function TIocpServer.CheckTimeOut(const ASocket: TSocket;
- const ATimeOutMS: Integer): Boolean;
- var
- tmTo: TTimeVal;
- FDRead: TFDSet;
- begin
- FillChar(FDRead, SizeOf(FDRead), 0);
- FDRead.fd_count := 1;
- FDRead.fd_array[0] := ASocket;
- tmTo.tv_sec := ATimeOutMS div 1000;
- tmTo.tv_usec := (ATimeOutMS mod 1000) * 1000;
- Result := Select(0, @FDRead, nil, nil, @tmTO) = 1;
- end;
第7步实现代码function TIocpServer.ReadChar(const ASocket: TSocket; var AChar: Char; const ATimeOutMS: Integer): Boolean; var iRead: Integer; begin Result := CheckTimeOut(ASocket, ATimeOutMS); if Result then begin iRead := recv(ASocket, AChar, 1, 0); Result := iRead = 1; end; end; function TIocpServer.CheckTimeOut(const ASocket: TSocket; const ATimeOutMS: Integer): Boolean; var tmTo: TTimeVal; FDRead: TFDSet; begin FillChar(FDRead, SizeOf(FDRead), 0); FDRead.fd_count := 1; FDRead.fd_array[0] := ASocket; tmTo.tv_sec := ATimeOutMS div 1000; tmTo.tv_usec := (ATimeOutMS mod 1000) * 1000; Result := Select(0, @FDRead, nil, nil, @tmTO) = 1; end;
接收连接之后要投递接收数据请求,实现代码:
- procedure TSocketHandle.PreRecv(AIocpRecord: PIocpRecord);
- var
- iFlags, iTransfer: Cardinal;
- iErrCode: Integer;
- begin
- if not Assigned(AIocpRecord) then
- begin
- New(AIocpRecord);
- AIocpRecord.WsaBuf.buf := @FIocpRecvBuf;
- AIocpRecord.WsaBuf.len := MAX_IOCPBUFSIZE;
- FIocpRecv := AIocpRecord;
- end;
- AIocpRecord.Overlapped.Internal := 0;
- AIocpRecord.Overlapped.InternalHigh := 0;
- AIocpRecord.Overlapped.Offset := 0;
- AIocpRecord.Overlapped.OffsetHigh := 0;
- AIocpRecord.Overlapped.hEvent := 0;
- //AIocpRecord.WsaBuf.buf := @FIocpRecvBuf;
- //AIocpRecord.WsaBuf.len := MAX_IOCPBUFSIZE;
- AIocpRecord.IocpOperate := ioRead;
- iFlags := 0;
- if WSARecv(FSocket, @AIocpRecord.WsaBuf, 1, iTransfer, iFlags, @AIocpRecord.Overlapped,
- nil) = SOCKET_ERROR then
- begin
- iErrCode := WSAGetLastError;
- if iErrCode = WSAECONNRESET then //客户端被关闭
- FConnected := False;
- if iErrCode <> ERROR_IO_PENDING then //不抛出异常,触发异常事件
- begin
- FIocpServer.DoError('WSARecv', GetLastWsaErrorStr);
- ProcessNetError(iErrCode);
- end;
- end;
- end;
第8步实现代码:procedure TSocketHandle.PreRecv(AIocpRecord: PIocpRecord); var iFlags, iTransfer: Cardinal; iErrCode: Integer; begin if not Assigned(AIocpRecord) then begin New(AIocpRecord); AIocpRecord.WsaBuf.buf := @FIocpRecvBuf; AIocpRecord.WsaBuf.len := MAX_IOCPBUFSIZE; FIocpRecv := AIocpRecord; end; AIocpRecord.Overlapped.Internal := 0; AIocpRecord.Overlapped.InternalHigh := 0; AIocpRecord.Overlapped.Offset := 0; AIocpRecord.Overlapped.OffsetHigh := 0; AIocpRecord.Overlapped.hEvent := 0; //AIocpRecord.WsaBuf.buf := @FIocpRecvBuf; //AIocpRecord.WsaBuf.len := MAX_IOCPBUFSIZE; AIocpRecord.IocpOperate := ioRead; iFlags := 0; if WSARecv(FSocket, @AIocpRecord.WsaBuf, 1, iTransfer, iFlags, @AIocpRecord.Overlapped, nil) = SOCKET_ERROR then begin iErrCode := WSAGetLastError; if iErrCode = WSAECONNRESET then //客户端被关闭 FConnected := False; if iErrCode <> ERROR_IO_PENDING then //不抛出异常,触发异常事件 begin FIocpServer.DoError('WSARecv', GetLastWsaErrorStr); ProcessNetError(iErrCode); end; end; end;
- function TIocpServer.WorkClient: Boolean;
- var
- ClientSocket: PClientSocket;
- IocpRecord: PIocpRecord;
- iWorkCount: Cardinal;
- begin
- IocpRecord := nil;
- iWorkCount := 0;
- ClientSocket := nil;
- Result := False;
- if not GetQueuedCompletionStatus(FIocpHandle, iWorkCount, DWORD(ClientSocket),
- POverlapped(IocpRecord), INFINITE) then //此处有可能多个线程处理同一个SocketHandle对象,因此需要加锁
- begin //客户端异常断开
- if Assigned(ClientSocket) and Assigned(ClientSocket.SocketHandle) then
- begin
- ClientSocket.SocketHandle.FConnected := False;
- Exit;
- end;
- end;
- if Cardinal(IocpRecord) = SHUTDOWN_FLAG then
- Exit;
- if not FActive then
- Exit;
- Result := True;
- if Assigned(ClientSocket) and Assigned(ClientSocket.SocketHandle) then
- begin
- if ClientSocket.SocketHandle.Connected then
- begin
- if iWorkCount > 0 then
- begin
- try
- ClientSocket.Lock.Enter;
- try
- if Assigned(ClientSocket.SocketHandle) then
- ClientSocket.SocketHandle.ProcessIOComplete(IocpRecord, iWorkCount);
- finally
- ClientSocket.Lock.Leave;
- end;
- if Assigned(ClientSocket.SocketHandle) and (not ClientSocket.SocketHandle.Connected) then
- begin
- ClientSocket.Lock.Enter;
- try
- if Assigned(ClientSocket.SocketHandle) then
- FreeSocketHandle(ClientSocket.SocketHandle);
- finally
- ClientSocket.Lock.Leave;
- end;
- end;
- except
- on E: Exception do
- DoError('ProcessIOComplete', E.Message);
- end;
- end
- else //如果完成个数为0,且状态为接收,则释放连接
- begin
- if IocpRecord.IocpOperate = ioRead then
- begin
- ClientSocket.Lock.Enter;
- try
- if Assigned(ClientSocket.SocketHandle) then
- FreeSocketHandle(ClientSocket.SocketHandle);
- finally
- ClientSocket.Lock.Leave;
- end;
- end
- else
- DoError('WorkClient', 'WorkCount = 0, Code: ' + IntToStr(GetLastError) + ', Message: ' + GetLastErrorStr);
- end;
- end
- else //断开连接
- begin
- ClientSocket.Lock.Enter;
- try
- if Assigned(ClientSocket.SocketHandle) then
- FreeSocketHandle(ClientSocket.SocketHandle);
- finally
- ClientSocket.Lock.Leave;
- end;
- end;
- end
- else //WorkCount为0表示发生了异常,记录日志
- DoError('GetQueuedCompletionStatus', 'Return SocketHandle nil');
- end;
第8步主要是使用GetQueuedCompletionStatus函数来获取完成端口事件通知,如果有事件完成,则可以通过lpCompletionKey来知道是哪个句柄有数据收到。function TIocpServer.WorkClient: Boolean; var ClientSocket: PClientSocket; IocpRecord: PIocpRecord; iWorkCount: Cardinal; begin IocpRecord := nil; iWorkCount := 0; ClientSocket := nil; Result := False; if not GetQueuedCompletionStatus(FIocpHandle, iWorkCount, DWORD(ClientSocket), POverlapped(IocpRecord), INFINITE) then //此处有可能多个线程处理同一个SocketHandle对象,因此需要加锁 begin //客户端异常断开 if Assigned(ClientSocket) and Assigned(ClientSocket.SocketHandle) then begin ClientSocket.SocketHandle.FConnected := False; Exit; end; end; if Cardinal(IocpRecord) = SHUTDOWN_FLAG then Exit; if not FActive then Exit; Result := True; if Assigned(ClientSocket) and Assigned(ClientSocket.SocketHandle) then begin if ClientSocket.SocketHandle.Connected then begin if iWorkCount > 0 then begin try ClientSocket.Lock.Enter; try if Assigned(ClientSocket.SocketHandle) then ClientSocket.SocketHandle.ProcessIOComplete(IocpRecord, iWorkCount); finally ClientSocket.Lock.Leave; end; if Assigned(ClientSocket.SocketHandle) and (not ClientSocket.SocketHandle.Connected) then begin ClientSocket.Lock.Enter; try if Assigned(ClientSocket.SocketHandle) then FreeSocketHandle(ClientSocket.SocketHandle); finally ClientSocket.Lock.Leave; end; end; except on E: Exception do DoError('ProcessIOComplete', E.Message); end; end else //如果完成个数为0,且状态为接收,则释放连接 begin if IocpRecord.IocpOperate = ioRead then begin ClientSocket.Lock.Enter; try if Assigned(ClientSocket.SocketHandle) then FreeSocketHandle(ClientSocket.SocketHandle); finally ClientSocket.Lock.Leave; end; end else DoError('WorkClient', 'WorkCount = 0, Code: ' + IntToStr(GetLastError) + ', Message: ' + GetLastErrorStr); end; end else //断开连接 begin ClientSocket.Lock.Enter; try if Assigned(ClientSocket.SocketHandle) then FreeSocketHandle(ClientSocket.SocketHandle); finally ClientSocket.Lock.Leave; end; end; end else //WorkCount为0表示发生了异常,记录日志 DoError('GetQueuedCompletionStatus', 'Return SocketHandle nil'); end;
这里有个技巧是我们在绑定的时候使用如下语句:CreateIoCompletionPort(ASocket, FIOCPHandle, DWORD(ClientSocket), 0),用lpCompletionKey来传递了一个指针,这其中就包含了一个锁和服务对象,定义结构如下:
- {* 客户端对象和锁 *}
- TClientSocket = record
- Lock: TCriticalSection;
- SocketHandle: TSocketHandle;
- end;
- PClientSocket = ^TClientSocket;
因而我们在收到事件通知后,就可以直接调用TSocketHandle对象来完成分包解包以及后续的逻辑处理。{* 客户端对象和锁 *} TClientSocket = record Lock: TCriticalSection; SocketHandle: TSocketHandle; end; PClientSocket = ^TClientSocket;
这样一个完成端口的整体骨架就有了,后续还有收发数据、分包解包和业务逻辑处理,以及对象和锁分离等细节处理。更详细代码见示例代码的IOCPSocket单元。下载地址:http://download.csdn.net/detail/sqldebug_fan/4510076