DELPHI高性能大容量SOCKET并发.docx
- 文档编号:7813365
- 上传时间:2023-01-26
- 格式:DOCX
- 页数:60
- 大小:121.39KB
DELPHI高性能大容量SOCKET并发.docx
《DELPHI高性能大容量SOCKET并发.docx》由会员分享,可在线阅读,更多相关《DELPHI高性能大容量SOCKET并发.docx(60页珍藏版)》请在冰豆网上搜索。
DELPHI高性能大容量SOCKET并发
DELPHI高性能大容量SOCKET并发
(一):
IOCP完成端口例子介绍
例子主要包括IOCP控件封装、服务端实现、传输协议和日志、控制、SQL查询、上传、下载等协议实现,并包括一些初步的性能测试结果。
服务端:
界面截图如下:
提供服务和桌面方式运行,桌面方式可直接打开程序,方便日常调试,可以使用命令行注册或卸载服务,在CMD中输入D:
\DEMO\IOCPDemo\Bin\IOCPDemoSvr.exe-install来注册服务,在CMD输入D:
\DEMO\IOCPDemo\Bin\IOCPDemoSvr.exe-uninstall来卸载服务。
客户端:
界面截图如下:
主要实现了服务端日志查看,服务端协议类表查看,SQL语句执行协议,上传、下载协议实现,其中对上传、下载实现了一个多线程同时传,用于测试服务器并发性能。
性能:
支持超过2000个链接及以上同时上传文件,不过每个连接上传速度只有1到2K。
支持超过2W个连接同时在线传输命令。
单实例上传下载测试结果:
从测试结果可以看出随着发送包增大,速度变快。
这里存在一个风险,就是SOCKET传输失败的次数也会增加。
(二):
IOCP完成端口控件封装
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用于向完成端口投递一个完成事件通知。
functionCreateIoCompletionPort(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步实现代码:
[delphi] viewplaincopy
1.procedure TIocpServer.Open;
2.var
3. WsaData:
TWsaData;
4. iNumberOfProcessors, i, iWorkThreadCount:
Integer;
5. WorkThread:
TWorkThread;
6. Addr:
TSockAddr;
7.begin
8. if WSAStartup($0202, WsaData) <> 0 then //初始化SOCKET
9. raise ESocketError.Create(GetLastWsaErrorStr);
10. FIocpHandle :
= CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); //创建一个完成端口
11. if FIocpHandle = 0 then
12. raise ESocketError.Create(GetLastErrorStr);
13. FSocket :
= WSASocket(PF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED); //创建一个SOCKET句柄
14. if FSocket = INVALID_SOCKET then
15. raise ESocketError.Create(GetLastWsaErrorStr);
16. FillChar(Addr, SizeOf(Addr), 0);
17. Addr.sin_family :
= AF_INET;
18. Addr.sin_port :
= htons(FPort);
19. Addr.sin_addr.S_addr :
= htonl(INADDR_ANY); //在任何地址上监听,如果有多块网卡,会每块都监听,也可以指定只监听某一个IP地址
20. if bind(FSocket, @Addr, SizeOf(Addr)) <> 0 then //把SOCKET句柄绑定端口
21. raise ESocketError.Create(GetLastWsaErrorStr);
22. if listen(FSocket, MaxInt) <> 0 then
23. raise ESocketError.Create(GetLastWsaErrorStr);
24. iNumberOfProcessors :
= GetCPUCount; //获取CPU个数
25. iWorkThreadCount :
= iNumberOfProcessors * 2 + 4; //由于服务器处理可能比较费时间,因此线程设为CPU*2+4
26. if iWorkThreadCount < FMinWorkThrCount then //限定最大工作者线程和最小工作者线程
27. iWorkThreadCount :
= FMinWorkThrCount;
28. if iWorkThreadCount > FMaxWorkThrCount then
29. iWorkThreadCount :
= FMaxWorkThrCount;
30. for i :
= 0 to iWorkThreadCount - 1 do //创建工作者线程
31. begin
32. WorkThread :
= TWorkThread.Create(Self, True);
33. FWorkThreads.Add(WorkThread);
34. WorkThread.Resume;
35. end;
36. FAcceptThreadPool.Active :
= True; //启动监听线程池
37. FAcceptThread :
= TAcceptThread.Create(Self, True); //启动监听线程
38. FAcceptThread.Resume;
39.end;
第5步和第6步实现代码:
[delphi] viewplaincopy
1.procedure TIocpServer.AcceptClient;
2.var
3. ClientSocket:
TSocket;
4.begin
5. ClientSocket :
= WSAAccept(FSocket, nil, nil, nil, 0); //接收连接
6. if ClientSocket <> INVALID_SOCKET then
7. begin
8. if not FActive then
9. begin
10. closesocket(ClientSocket);
11. Exit;
12. end;
13. FAcceptThreadPool.PostSocket(ClientSocket); //这里使用线程池主要作用是为了判断发送的第一个字节身份标识,用来是判断协议类型
14. end;
15.end;
FAcceptThreadPool是一个用完成端口实现的线程池TIocpThreadPool,主要是有TCheckThread来判断完成端口的返回,并检测是否6S内有发送标志位上来,主要实现过程是响应OnConnect事件,并在OnConnect事件中判断是否允许连接。
[delphi] viewplaincopy
1.procedure TIocpServer.CheckClient(const ASocket:
TSocket);
2.var
3. SocketHandle:
TSocketHandle;
4. iIndex:
Integer;
5. ClientSocket:
PClientSocket;
6.begin
7. SocketHandle :
= nil;
8. if not DoConnect(ASocket, SocketHandle) then //如果不允许连接,则退出
9. begin
10. closesocket(ASocket);
11. Exit;
12. end;
13. FSocketHandles.Lock; //加到列表中
14. try
15. iIndex :
= FSocketHandles.Add(SocketHandle);
16. ClientSocket :
= FSocketHandles.Items[iIndex];
17. finally
18. FSocketHandles.UnLock;
19. end;
20. if CreateIoCompletionPort(ASocket, FIOCPHandle, DWORD(ClientSocket), 0) = 0 then //将连接和完成端口绑定在一起
21. begin
22. DoError('CreateIoCompletionPort', GetLastWsaErrorStr);
23. FSocketHandles.Lock; //如果投递到列表中失败,则删除
24. try
25. FSocketHandles.Delete(iIndex);
26. finally
27. FSocketHandles.UnLock;
28. end;
29. end
30. else
31. begin
32. SocketHandle.PreRecv(nil); //投递接收请求
33. end;
34.end;
[delphi] viewplaincopy
1.procedure TDMDispatchCenter.IcpSvrConnect(const ASocket:
Cardinal;
2. var AAllowConnect:
Boolean; var SocketHandle:
TSocketHandle);
3.var
4. BaseSocket:
TBaseSocket;
5. chFlag:
Char;
6.
7. function GetSocket(const AEnable:
Boolean; BaseSocketClass:
TBaseSocketClass):
TBaseSocket;
8. begin
9. if AEnable then
10. Result :
= BaseSocketClass.Create(IcpSvr, ASocket)
11. else
12. Result :
= nil;
13. end;
14.begin
15. if (GIniOptions.MaxSocketCount > 0) and (IcpSvr.SocketHandles.Count >= GIniOptions.MaxSocketCount) then
16. begin
17. AAllowConnect :
= False;
18. Exit;
19. end;
20. if IcpSvr.ReadChar(ASocket, chFlag, 6*1000) then //必须在6S内收到标志
21. begin
22. case TSocketFlag(Byte(chFlag)) of
23. sfSQL:
BaseSocket :
= GetSocket(GIniOptions.SQLProtocol, TSQLSocket);
24. sfUpload:
BaseSocket :
= GetSocket(GIniOptions.UploadProtocol, TUploadSocket);
25. sfDownload:
BaseSocket :
= GetSocket(GIniOptions.DownloadProtocol, TDownloadSocket);
26. sfControl:
BaseSocket :
= GetSocket(GIniOptions.ControlProtocol, TControlSocket);
27. sfLog:
BaseSocket :
= GetSocket(GIniOptions.LogProtocol, TLogSocket);
28. else
29. BaseSocket :
= nil;;
30. end;
31. if BaseSocket <> nil then
32. begin
33. SocketHandle :
= BaseSocket;
34. WriteLogMsg(ltDebug, Format('Client Connect, Local Address:
%s:
%d; Remote Address:
%s:
%d',
35. [SocketHandle.LocalAddress, SocketHandle.LocalPort, SocketHandle.RemoteAddress, SocketHandle.RemotePort]));
36. WriteLogMsg(ltDebug, Format('Client Count:
%d', [IcpSvr.SocketHandles.Count + 1]));
37. AAllowConnect :
= True;
38. end
39. else
40. AAllowConnect :
= False;
41. end
42. else
43. AAllowConnect :
= False;
44.end;
其中ReadChar函数是用于判断指定时间是否有数据上来,函数实现过程用Select函数检测:
[delphi] viewplaincopy
1.function TIocpServer.ReadChar(const ASocket:
TSocket; var AChar:
Char; const ATimeOutMS:
Integer):
Boolean;
2.var
3. iRead:
Integer;
4.begin
5. Result :
= CheckTimeOut(ASocket, ATimeOutMS);
6. if Result then
7. begin
8. iRead :
= recv(ASocket, AChar, 1, 0);
9. Result :
= iRead = 1;
10. end;
11.end;
12.
13.function TIocpServer.CheckTimeOut(const ASocket:
TSocket;
14. const ATimeOutMS:
Integer):
Boolean;
15.var
16. tmTo:
TTimeVal;
17. FDRead:
TFDSet;
18.begin
19. FillChar(FDRead, SizeOf(FDRead), 0);
20. FDRead.fd_count :
= 1;
21. FDRead.fd_array[0] :
= ASocket;
22. tmTo.tv_sec :
= ATimeOutMS div 1000;
23. tmTo.tv_usec :
= (ATimeOutMS mod 1000) * 1000;
24. Result :
= Select(0, @FDRead, nil, nil, @tmTO) = 1;
25.end;
第7步实现代码
接收连接之后要投递接收数据请求,实现代码:
[delphi] viewplaincopy
1.procedure TSocketHandle.PreRecv(AIocpRecord:
PIocpRecord);
2.var
3. iFlags, iTransfer:
Cardinal;
4. iErrCode:
Integer;
5.begin
6. if not Assigned(AIocpRecord) then
7. begin
8. New(AIocpRecord);
9. AIocpRecord.WsaBuf.buf :
= @FIocpRecvBuf;
10. AIocpRecord.WsaBuf.len :
= MAX_IOCPBUFSIZE;
11. FIocpRecv :
= AIocpRecord;
12. end;
13. AIocpRecord.Overlapped.Internal :
= 0;
14. AIocpRecord.Overlapped.InternalHigh :
= 0;
15. AIocpRecord.Overlapped.Offset :
= 0;
16. AIocpRecord.Overlapped.OffsetHigh :
= 0;
17. AIocpRecord.Overlapped.hEvent :
= 0;
18. //AIocpRecord.WsaBuf.buf :
= @FIocpRecvBuf;
19. //AIocpRecord.WsaBuf.len :
= MAX_IOCPBUFSIZE;
20. AIocpRecord.IocpOperate :
= ioRead;
21. iFlags :
= 0;
22. if WSARecv(FSocket, @AIocpRecord.WsaBuf, 1, iTransfer, iFlags, @AIocpRecord.Overlapped,
23. nil) = SOCKET_ERROR then
24. begin
25. iErrCode :
= WSAGetLastError;
26. if iErrCode = WSAECONNRESET then //客户端被关闭
27. FConnected :
= False;
28. if iErrCode <> ERROR_IO_PENDING then //不抛出异常,触发异常事件
29. begin
30. FIocpServer.DoError('WSARecv', GetLastWsaErrorStr);
31. ProcessNetError(iErrCode);
32. end;
33. end;
34.end;
第8步实现代码:
[delphi] viewplaincopy
1.function TIocpServer.WorkClient:
Boolean;
2.var
3. ClientSocket:
PClientSocket;
4. IocpRecord:
PIocpRecord;
5. iWorkCount:
Cardinal;
6.begin
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- DELPHI 性能 容量 SOCKET 并发