Delphi编写事件模型客户端(2)

简介:
上次写了事件模型类的定义,今天我来写一写如何实现这个类。
首先的两个函数我想稍微了解网络编程的人都会清楚。  
procedure WSAStatupSocket;
var
  WSData:TWSAData;
begin
  if WSAStartup($0202, WSData) <> 0 then
  begin
    raise Exception.Create('WSAStartup Error.');
  end;
end;  
procedure WSACleanupSocket;
begin
  if WSACleanup <> 0 then
  begin
    raise Exception.Create('WSACleanup Error.');
  end;
end;  
下来是真正类函数的实现。
procedure TIOEvents.ClearBuffer;
var
  FNext:PSendBuffer;
begin
  while Assigned(FFirstNode) do
  begin
    FNext:=FFirstNode.Next;
    FreeMem(FFirstNode.Buf);
    FFirstNode.BufLen:=0;
    Dispose(FFirstNode);
    Dec(FTotalCount);
    FFirstNode:=FNext;
  end;
  if not Assigned(FFirstNode) then
  begin
    FLastNode:=nil;
  end;
  FTotalCount:=0;
end;
此函数用来对发送队列中的的数据清空,此函数在释放网络的时候使用。
创建和销毁函数的定义和实现如下:
constructor TIOEvents.Create;
begin
  InitializeCriticalSection(FEventCS);   //定义临界区
  FMainIP           :=  '127.0.0.1';      //定义服务端默认IP和默认端口
  FMainPort         :=  5500;
  FActive           :=  false;
  FKeepAlive        :=  false;         //初始化心跳不开启
  FKeepTime         :=  1000;
  FSendLen          :=  DEFAULT_BUFSIZE;     //初始化发送数据长度
  FEventNums        :=  0;
end;
在创建函数中我对一些使用的变量进行了初始化。
在销毁函数中我将临界区进行释放。
destructor TIOEvents.Destroy;
begin
  DeleteCriticalSection(FEventCS);
  inherited;
end;
 
以下的两个函数是发送数据和接收数据中使用的主要函数。
首先是PostRecv函数,此函数用于投递接收。
function TIOEvents.PostRecv: Boolean;
var
  Flags,RecvBytes: DWORD;
begin
  Result:=true;
  Flags := 0;
  FRecvIOData.DataBuf.len   := DATA_BUFSIZE;
  ZeroMemory(@FRecvIOData.Buffer, sizeof(@FRecvIOData.Buffer));
  FRecvIOData.DataBuf.buf   := @FRecvIOData.Buffer;
  if (WSARecv(FSocket, @(FRecvIOData.DataBuf), 1, @RecvBytes, @Flags, @(FRecvIOData.Overlapped), nil) = SOCKET_ERROR) then
  begin
    if (WSAGetLastError() <> ERROR_IO_PENDING) then
    begin
      Result:=false;
    end;
  end;
end;
 
其次是PostSend函数,此函数是投递发送,大家会注意到投递发送的时候的代码和投递接收数据的代码有些不同,这里的不同主要在于,我们每次投递发送的时候,数据的大小最大为4K的数据。对于比较长的数据,我会将它放入到发送队列中,所以投递的时候是从发送队列中的First数据,进行投递,并将FirstNext数据设置为First
function TIOEvents.PostSend: Boolean;
var
  SendBytes: DWORD;
  FNext:PSendBuffer;
begin
  EnterCriticalSection(FEventCS);
  try
    Result:=true;
    FillChar(FSendIOData.Buffer,SizeOf(FSendIOData.Buffer),#0);
    ZeroMemory(@FSendIOData.Overlapped, sizeof(OVERLAPPED));
    FNext:=FFirstNode.Next;
    Move(FFirstNode.Buf^,FSendIOData.Buffer,FFirstNode.BufLen);
    FSendIOData.BufferLen     :=  FFirstNode.BufLen;
    FSendIOData.DataBuf.len   :=  FFirstNode.BufLen;
    FSendIOData.DataBuf.buf   :=  @FSendIOData.Buffer;
    FSendIOData.Socket        :=  FSocket;
    if (WSASend(FSocket, @(FSendIOData.DataBuf), 1, @SendBytes, 0, @(FSendIOData.Overlapped), nil) = SOCKET_ERROR) then
    begin
      if (WSAGetLastError() <> ERROR_IO_PENDING) then
      begin
        Result:=false;
      end;
    end;
    FreeMem(FFirstNode.Buf);
    Dispose(FFirstNode);
    FFirstNode:=FNext;
  finally
    LeaveCriticalSection(FEventCS);
  end;
end;
 
下来是设置心跳函数,此函数如果看过我以前BLOG的朋友应该知道如何使用的。见《网络通信中的心跳机制的实现!》
function TIOEvents.SetKPAlive: Boolean;
var
  inKeepAlive,OutKeepAlive:TTCP_KEEPALIVE;
  opt:Integer;
  insize,outsize,outByte:DWORD;
begin
  Result  :=true;
  opt     :=1;
  if setsockopt(FSocket,SOL_SOCKET,SO_KEEPALIVE,@opt,sizeof(opt))=SOCKET_ERROR then
  begin
    Exit;
  end;
  inKeepAlive.onoff             :=  1;
  inKeepAlive.keepalivetime     :=  FKeepTime;
  inKeepAlive.keepaliveinterval :=  1;
  insize                        :=  sizeof(TTCP_KEEPALIVE);
  outsize                       :=  sizeof(TTCP_KEEPALIVE);
  if WSAIoctl(FSocket,SIO_KEEPALIVE_VALS,@inKeepAlive,insize,@outKeepAlive,outsize,@outByte,nil,nil)=SOCKET_ERROR then
  begin
    Exit;
  end;
end;
 
上面的PostSend函数只是一个投递发送数据的函数,可是如何将发送的时候放入发送队列呢?这个工作主要依靠一下的函数来实现。
 
function TIOEvents.SocketWrite(Data: Pchar; DataLen: Integer):Boolean;
var
  iPos:Integer;
  PNode:PSendBuffer;
begin
  Result:=true;
  if Assigned(Self) then
  begin
    if (DataLen<=0) or (FSendLen<DataLen) then
    begin
      Result:=false;
      Exit;
    end;
    iPos:=0;
    while DataLen - iPos > 0 do
    begin
      New(PNode);
      if (DataLen - iPos)>=DATA_BUFSIZE then
      begin
        PNode.BufLen := DATA_BUFSIZE;
        GetMem(PNode.Buf, DATA_BUFSIZE);
        Move((Data+iPos)^, PNode.Buf^, DATA_BUFSIZE);
        PNode.Next := nil;
        Inc(iPos,DATA_BUFSIZE);
      end
      else
      begin
        PNode^.BufLen := DataLen - iPos;
        GetMem(PNode.Buf, DataLen - iPos);
        Move((Data+iPos)^, PNode.Buf^, DataLen - iPos);
        PNode.Next := nil;
        Inc(iPos,DataLen - iPos);
      end;
      //加入发送队列
      EnterCriticalSection(FEventCS);
      try
        if not Assigned(FFirstNode) then
        begin
          FFirstNode:=PNode;
        end
        else
        begin
          FLastNode.Next:=PNode;
        end;
        FLastNode:=PNode;
        Inc(FTotalCount);
      finally
        LeaveCriticalSection(FEventCS);
      end;
      if not Sending then
      begin
        Sending:=true;
        if not PostSend then
        begin
          closesocket(FSocket);
          break;
        end;
      end;
    end;
  end;
end;
 
由于要注意粘包缓存,所以发送的时候需要小于粘包函数大小,所以此函数的开始就是对发送数据的长度进行了判断。if (DataLen<=0) or (FSendLen<DataLen) then 当发送数据长度合适的时候,就需要将发送的数据分割成4k大小的数据块,并将此数据块放入到发送队列中。
当放置完毕以后,需要调用一次PostSend来发送数据。大家会看到在我调用PostSend函数之前对一个变量进行了判断。if not Sending then 这个变量Sending是用来判断当前是否正在发送数据,如果正在发送数据的话,那么就无需投递PostSend.
TIOEvents类的最后一个函数就是Start函数了,此函数用于创建、连接服务端、设置套接字的事件和创建一个工作者线程。
procedure TIOEvents.Start;
var
  Addr:TSockAddr;
  Event:Cardinal;
begin
  FSocket := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED);
  if FSocket = SOCKET_ERROR then
  begin
    closesocket(FSocket);
    FSocket:=INVALID_SOCKET;
    raise Exception.Create(Format('WSASocket Error ErrorID=%d',[GetLastError]));
    Exit;
  end;
  Addr.sin_addr.s_addr:=inet_addr(Pchar(FMainIP));
  Addr.sin_family:=AF_INET;
  Addr.sin_port:=htons(FMainPort);
  if (connect(FSocket,@Addr,sizeof(Addr))=SOCKET_ERROR) then
  begin
    closesocket(FSocket);
    FSocket:=INVALID_SOCKET;
    raise Exception.Create(Format('connect Error ErrorID=%d',[GetLastError]));
    Exit;
  end;
  if FKeepAlive then
  begin
    SetKPAlive;
  end;
  Event:=WSACreateEvent;
  FillChar(FRecvIOData,SizeOf(FRecvIOData),0);
  FillChar(FSendIOData,SizeOf(FSendIOData),0);
  FEventArray[FEventNums] :=Event;
  FSocketArray[FEventNums]:=FSocket;
  FRecvIOData.Overlapped.hEvent :=  Event;
  FSendIOData.Overlapped.hEvent :=  Event;
  Inc(FEventNums);
  //注册此套接字上的事件
  if WSAEventSelect(FSocket,Event, FD_READ or FD_WRITE or FD_CLOSE) = SOCKET_ERROR then
  begin
    raise Exception.Create(Format('WSAEventSelect Error ErrorID=%d',[GetLastError]));
    Exit;
  end;
  //创建工作者线程
  FWorkThread:=TWorkThread.Create(Self);
end;
 
工作者线程是发送和接收数据的主要部分。没有这部分代码,将无法实现网络通信。下面一篇我会写出我是如何编写工作者线程的。
本文转自狗窝博客51CTO博客,原文链接http://blog.51cto.com/fxh7622/164247如需转载请自行联系原作者

fxh7622
相关文章
|
3月前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
167 0
|
SQL 缓存 监控
纯干货:客户端代码框架设计!
纯干货:客户端代码框架设计!
纯干货:客户端代码框架设计!
下一篇
无影云桌面