DELPHI高性能大容量SOCKET并发.docx

上传人:b****5 文档编号:7813365 上传时间:2023-01-26 格式:DOCX 页数:60 大小:121.39KB
下载 相关 举报
DELPHI高性能大容量SOCKET并发.docx_第1页
第1页 / 共60页
DELPHI高性能大容量SOCKET并发.docx_第2页
第2页 / 共60页
DELPHI高性能大容量SOCKET并发.docx_第3页
第3页 / 共60页
DELPHI高性能大容量SOCKET并发.docx_第4页
第4页 / 共60页
DELPHI高性能大容量SOCKET并发.docx_第5页
第5页 / 共60页
点击查看更多>>
下载资源
资源描述

DELPHI高性能大容量SOCKET并发.docx

《DELPHI高性能大容量SOCKET并发.docx》由会员分享,可在线阅读,更多相关《DELPHI高性能大容量SOCKET并发.docx(60页珍藏版)》请在冰豆网上搜索。

DELPHI高性能大容量SOCKET并发.docx

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  

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1