利用DELPHI開發完成例程(1)

對於網絡開發者來講,完成例程應該是一個不陌生的概念(什麼?你不知道?去看看書吧)。
我在網上看了一下,發現完整實現完成例程的代碼不多。前些日子因爲工做不是很忙就本身寫了一個,今天將如何實現公佈出來,但願對你們有個幫助。因爲水平有限,代碼中不免會有不對的地方,但願各位看客能不吝指出。
言歸正傳。完成例程在其性能上僅次於IOCP。經過個人測試,以爲通常支持1000-2000的客戶端應該沒有什麼問題。並且實現起來比IOCP要簡單不少。
首先介紹一下我使用的IODATA結構:
  PIOData = ^TIOData;
  TIOData = record
    Overlapped: OVERLAPPED;
    DataBuf: TWSABUF;
    Socket:TSocket;                         //套接字
    BufferLen:Integer;                      //數據長度
    Buffer:array[0..DATA_BUFSIZE-1] of char;//數據信息,包括數據頭信息
    FNetClass:Pointer;                      //類的指針
  end;
其中須要說明的是FNetClass:Pointer,因爲完成例程主要使用的是WSARECVWSASEND函數的最後一個參數,即接收和發送完成之後使用的回調函數。而在幫助中這兩個的回調函數的定義爲:
void CALLBACK CompletionROUTINE(
 IN DWORD dwError,
 IN DWORD cbTransferred,
 IN LPWSAOVERLAPPED lpOverlapped,
 IN DWORD dwFlags
);
能夠看出當咱們對相應的數據包處理的時候若是沒有指定的類,那麼處理起來將比較麻煩。因此我在定義這個數據結構的時候,將類指針直接帶來進去。
下來是啓動網絡監聽。
FListensc := WSASocket(AF_INET, SOCK_STREAM, 0, nil, 0, WSA_FLAG_OVERLAPPED);
  if FListensc = SOCKET_ERROR then
  begin
    closesocket(FListensc);
    WSACleanup();
    if Assigned(OnError) then
    begin
      OnError(GetLastError);
    end;
    Exit;
  end;
  Fsto.sin_family := AF_INET;
  Fsto.sin_port := htons(FPort);
  Fsto.sin_addr.s_addr := htonl(INADDR_ANY);
  if bind(FListensc, Fsto, sizeof(Fsto)) = SOCKET_ERROR then
  begin
    closesocket(FListensc);
    if Assigned(OnError) then
    begin
      OnError(GetLastError);
    end;
    Exit;
  end;
  listen(FListensc, 200);
  FListenThreadID:=CreateThread(nil, 0, @AcceptThread, Self,0,ThreadID);
這個代碼就不用解釋了,開發過網絡的朋友都知道什麼意思。
看看監聽線程的處理代碼。
var
  FComletionRoutine:TCompletionRoutine;
  p_IOData:PIOData;
  Acceptsc:TSocket;
  FSendHash:PSendHash;
begin
  FComletionRoutine:=TCompletionRoutine(m_NetServer);
  while True do
  begin
    Acceptsc:= WSAAccept(FComletionRoutine.FListensc, nil, nil, nil, 0);
    if Acceptsc<>INVALID_SOCKET then
    begin
      if Assigned(FComletionRoutine.OnConect) then
      begin
        FComletionRoutine.OnConect(Acceptsc,'');
      end;
      p_IOData:=FComletionRoutine.RequestsMem;
      p_IOData.FNetClass:=FComletionRoutine;
      p_IOData.Socket:=Acceptsc;
      FSendHash:=FComletionRoutine.FSendHashCtrl.CreateSendHash(p_IOData.Socket);
      FComletionRoutine.PostRecv(p_IOData);
    end
    else
    begin
      break;
    end;
  end;
其中p_IOData:=FComletionRoutine.RequestsMem是我寫的一個函數,其做用是在一個內存池中爲p_IOData申請一塊空間。
FSendHash:=FComletionRoutine.FSendHashCtrl.CreateSendHash(p_IOData.Socket)的做用是將這個申請後的空間放入一個HASH表中。
而後投遞一個接收請求。
來看看這個投遞接收請求是如何實現的。
  Result:=true;
  Flags := 0;
  ZeroMemory(@(PerIoData.Overlapped), sizeof(OVERLAPPED));
  PerIoData.DataBuf.len   := DATA_BUFSIZE;
  ZeroMemory(@PerIoData.Buffer, sizeof(@PerIoData.Buffer));
  PerIoData.DataBuf.buf   := @PerIoData.Buffer;
  if (WSARecv(PerIoData.Socket, @(PerIoData.DataBuf), 1, @RecvBytes, @Flags, @(PerIoData.Overlapped), @RecvWorkerThread) = SOCKET_ERROR) then
  begin
    if (WSAGetLastError() <> ERROR_IO_PENDING) then
    begin
      Result:=false;
    end;
  end;
WSARecv函數的最後一個參數指定了當接收數據完成之後,咱們須要處理的回調函數。
而在這個回調函數中處理的方式是這樣的。
FComletionRoutine:=TCompletionRoutine(IoData.FNetClass);
  EnterCriticalSection(FComletionRoutine.FIOCPInter);
  try
    if (Error<>0) and (BytesTransferred=0) then
    begin
      if Assigned(FComletionRoutine.OnShutDown) then
      begin
        FComletionRoutine.OnShutDown(IoData.Socket);
      end;
      //刪除發送緩存列表
      FComletionRoutine.FSendHashCtrl.DelSort(IoData.Socket);
      closesocket(IoData.Socket);
      Exit;
    end;
    IoData.BufferLen:=BytesTransferred;
    FSendHash:=FComletionRoutine.FSendHashCtrl.SelSort(IoData.Socket);
    FComletionRoutine.FSendHashCtrl.DealData(FSendHash,IoData);
    //這裏進行粘包處理,並將處理後的整包數據發送給相應的代碼。(關於如何處理粘包,我此次就不寫出來了。不過建議你們不要使用我前面BLOG中寫的粘包處理方法,那種方法處理起來效率比較低。可是那種思路能夠借鑑,就是在發送的數據包前帶入本次發送的數據長度)。
      
    //再次投遞
    if not FComletionRoutine.PostRecv(IoData) then
    begin
      closesocket(IoData.Socket);
      Exit;
    end;
  finally
    LeaveCriticalSection(FComletionRoutine.FIOCPInter);
  end;
 
首先就是從IO數據中獲得處理的類,這爲之後咱們操做HASH、發送隊列將會有很大幫助。
而後判斷是否接收正常,對於接收不正常的,咱們將關閉這個套接字。
對於接收數據正常的,咱們從HASH表中獲得它上次須要處理剩餘的數據並和此次的數據結合進行處理。OK,這樣咱們就完成了對接收數據的回調函數的實現。下次我將寫出如何實現發送的部分。
相關文章
相關標籤/搜索