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;

 

工作者線程是發送和接收數據的主要部分。沒有這部分代碼,將無法實現網絡通信。下面一篇我會寫出我是如何編寫工作者線程的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章