RO代码跟踪 之 客户端网络通信的工作原理

  我们还是顺着代理类中的一个方法来跟踪,如下所示:
function TFirstSampleService_Proxy.Nicknames(const FullName: Widestring): Widestring;
begin
  try
    __Message.InitializeRequestMessage(__TransportChannel, 'FirstSample', __InterfaceName, 'Nicknames');
    __Message.Write('FullName', TypeInfo(Widestring), FullName, []);
    __Message.Finalize;

    __TransportChannel.Dispatch(__Message);

    __Message.Read('Result', TypeInfo(Widestring), result, []);
  finally
    __Message.UnsetAttributes(__TransportChannel);
    __Message.FreeStream;
  end
end;

  从上一节我们知道__Message就是我们指定的Message控件,InitializeRequestMessage方法将接口名称,消息名称,消息类型等信息传递给Message控件;Write方法实现:Serializer.Write(aName, aTypeInfo, Ptr);开始流化数据。这里的Serializer对象将会根据不同的Message类型创建不同的序列化类型,关键的流化方法如WriteInt64,WriteInteger,WriteWideString等方法在Serializer中实现。并由__Message.Write方法的第二个参数指定参数类型,据此调用具体的流化方法.好了,消息控件的任务完成了,他已经收集了所有需要传输的数据,接着就由通道调用Dispatch方法来使用消息控件了.
  在uROClient单元中,找到procedure TROTransportChannel.Dispatch(aMessage: IROMessage);方法.这个方法中调用了Dispatch(lRequest, lResponse);方法,看看参数名称,是不是豁然开朗了?请求流和应答流.接着就是调用ReadFromStream方法来获取服务端传回来的结果了.我们继续看Dispatch(lRequest, lResponse);方法.这个方法中判断通道是否繁忙,如果需要,会进行信息流压缩,然后调用IntDispatch方法向下传输,并对返回的信息解压缩.IntDispatch是一个虚方法,由派生类实现.我们现在看看实现:
procedure TROWinInetHTTPChannel.IntDispatch(aRequest, aResponse: TStream);
var lRequest:hInternet;
begin
  CheckProperties;
  Connected := true;
  try
    lRequest := SendData(aRequest);
    try
      ReceiveData(lRequest,aResponse);
    finally
      InternetCloseHandle(lRequest);
    end;
  finally
    if not KeepConnection then Connected := false;
  end;
end;
看到两个关键的函数,SendData和ReceiveData.数据在这里穿墙并返回.下面重点研究这两个函数.
function TROWinInetHTTPChannel.SendData(iData: TStream):hInternet;
var
   RetVal: DWORD;
   bRes: Boolean;

   procedure _Check;
   begin
     if not bRes then begin
       RetVal := GetLastError;
       if (RetVal > const_WinInetErrorCode_MIN) and (RetVal < const_WinInetErrorCode_MAX) then begin
         if (RetVal = ERROR_INTERNET_SEC_CERT_ERRORS) or
            (RetVal = ERROR_INTERNET_INVALID_CA) or
            (RetVal = ERROR_INTERNET_SEC_CERT_CN_INVALID) or
            (RetVal = ERROR_INTERNET_SEC_CERT_DATE_INVALID) or
            (RetVal = ERROR_INTERNET_SEC_CERT_REV_FAILED) then begin
           RetVal := AllowInvalidCA(Result, RetVal);
           // MODIFIED BY TRINET
           RetVal := ERROR_RETRY;
           // MODIFIED BY TRINET END
           bRes := True;
         end else if (RetVal = ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) then begin
           { you are accessing Secure Socket Layer (SSL)-protected resource
             on a Web server that requires a valid client certificate. }
           RetVal := CheckInetError(Result, RetVal);
           bRes := True;
           // MODIFIED BY TRINET
           RetVal := ERROR_RETRY;
           // MODIFIED BY TRINET END
         end else if (RetVal = HTTP_STATUS_DENIED)
                  or (RetVal = HTTP_STATUS_PAYMENT_REQ) then begin
           RetVal := CheckInetError(Result, RetVal);
           // MODIFIED BY TRINET
           RetVal := ERROR_RETRY;
           // MODIFIED BY TRINET END
           bRes := True;
         end else if (RetVal = ERROR_INTERNET_CONNECTION_ABORTED) then begin
//         slavad
//           RetVal := ERROR_RETRY;
           RetVal := ERROR_CANCELLED;
//           slavad
           bRes := True;
         end else if (RetVal = ERROR_INTERNET_FORCE_RETRY) then
         begin
          RetVal := ERROR_RETRY;
          bRes   := True;
         end
       end;
     end else begin
       RetVal := CheckInetError(Result, GetLastError);
     end;
     Check(not bRes);
   end;

const
   bufsize = 64*1024; //32kb
var
   lHeaders: string;
   Flags: DWord;
   AcceptTypes: array of PChar;
   Mask: DWORD;
   buf: Pointer;
   lTotalsize: cardinal;
   lCurSize: cardinal;
   lSendSize,lwrittensize: cardinal;
   intbuf: INTERNET_BUFFERS;
   lStreamSize: integer;
begin
   SetLength(AcceptTypes, 2);

   TriggerProgress(ptStart, pdSending, 0, iData.Size);  //更新进度条

   AcceptTypes[0] := PChar('application/octet-stream');
   AcceptTypes[1] := nil;
   //设置标志位:
   Flags := INTERNET_FLAG_NO_CACHE_WRITE; 

   if (KeepConnection) or (ProxyLogin.Username<>'') then Flags := Flags or INTERNET_FLAG_KEEP_CONNECTION;  //保持连接
   if fURLScheme = INTERNET_SCHEME_HTTPS then begin
     Flags := Flags or INTERNET_FLAG_SECURE;   //安全特性
     if fTrustInvalidCA then begin             //使用证书
       Flags := Flags or INTERNET_FLAG_IGNORE_CERT_CN_INVALID
                      or INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
     end;
   end;
   //调用事件
   if assigned(fOnBeforeOpenRequest) then
     fOnBeforeOpenRequest(Self);
   //打开Http通道
   result := HttpOpenRequest(FInetConnect, 'POST', PChar(fURLSite), nil, nil, Pointer(AcceptTypes), Flags, Integer(Self));
   Check(not Assigned(result));
   //调用事件
   if assigned(fOnAfterOpenRequest) then
     fOnAfterOpenRequest(Self, result);
   //设置Http通道的超时属性
   { Time Out }
   if fTimeOut > 0 then begin
     InternetSetOption(Result, INTERNET_OPTION_RECEIVE_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
     InternetSetOption(FInetConnect, INTERNET_OPTION_RECEIVE_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));

     InternetSetOption(Result, INTERNET_OPTION_SEND_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
     InternetSetOption(FInetConnect, INTERNET_OPTION_SEND_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
   end;
   //
   { Certificates Errors }
   Mask := INTERNET_ERROR_MASK_COMBINED_SEC_CERT;
   InternetSetOption(FInetConnect, INTERNET_OPTION_ERROR_MASK, @Mask, SizeOf(Mask));
   // MODIFIED BY TRINET

   lStreamSize:=iData.Size;
   GetMem(buf, bufsize);
   try
     if ProxyLogin.Username<>'' then
     begin
       bRes := HttpSendRequest(Result,nil,0,nil,0);
       repeat
         bRes := InternetReadFile (Result, buf, bufsize, lSendSize);
       until (lSendSize = 0);
     end;
     //开始组织包头 分包
     RetVal := ERROR_RETRY;
     while RetVal=ERROR_RETRY do
     begin
       iData.Position := 0;
       //包头数据
       FillChar(intbuf, sizeof(INTERNET_BUFFERS),0);
       intbuf.dwStructSize := sizeof(INTERNET_BUFFERS);
       SetHeaders('Content-Length',IntToStr(lStreamSize));
       intbuf.lpcszHeader:= PChar(fHeaders);
       intbuf.dwHeadersLength:= Length(fHeaders);
       intbuf.dwBufferTotal:=lStreamSize;

       lSendSize := 0;
       lTotalsize:= iData.Size;
       repeat            //发送HTTP包头
         bRes:= HttpSendRequestEx(Result, @intbuf,nil, 0, 0);
         _Check;
       until RetVal <> ERROR_INTERNET_FORCE_RETRY;
       if RetVal=ERROR_RETRY then continue;
       while lTotalsize > 0 do begin    //分包传输
         if lTotalsize > bufsize then
           lCurSize := bufsize
         else
           lCurSize := lTotalsize;
         iData.Read(buf^,lCurSize);
         inc(lSendSize, lCurSize);
         dec(lTotalsize,lCurSize);
         while true do begin
           lHeaders := fHeaders;
           fHeaders := '';
           while lCurSize > 0 do begin
             bRes := InternetWriteFile(Result,buf,lcursize, lwrittensize);  //写包
             if not bRes and (lwrittensize <> lcursize) and (lwrittensize<>0) then begin 
               //如果一个包没有发生成功或发送出去一部分,将没发送的部分下次循环发送
               Dec(lCurSize,lwrittensize);
               Move( (PAnsichar(buf)+lwrittensize)^ ,buf^, lCurSize);
             end
             else begin
               Break;
             end;
           end;

           _Check;
           case RetVal of
             ERROR_SUCCESS:  begin
               SetLastError(ERROR_SUCCESS);
               Break;
             end;
             ERROR_CANCELLED: SysUtils.Abort;
             ERROR_INTERNET_FORCE_RETRY: {Retry the operation};
           end;
         end;
         TriggerProgress(ptInProgress, pdSending, lSendSize, lStreamSize);
       end;
       bres:= HttpEndRequest(Result,nil,0,0);
       _Check;
       if RetVal =  ERROR_INTERNET_FORCE_RETRY then RetVal := ERROR_RETRY;
     end;
   finally
     Freemem(buf);
   end;
   // MODIFIED BY TRINET - END
   TriggerProgress(ptDone, pdSending, 0, lStreamSize);
end;

procedure TROWinInetHTTPChannel.ReceiveData(iRequest:hInternet; ioData:TStream);
const MaxStatusText     : Integer = 4096;
var
  Size, Status, Len, Index: DWord;
  S:string;

  lpszData: PAnsiChar; // buffer for the data
  dwSize: DWORD; // size of the data available
  dwDownloaded: DWORD; // size of the downloaded data

  lTotalSize,lReceivedSize:dword;
begin
  Len := SizeOf(Status);
  Index := 0;

  { Get Status Code }
  if not HttpQueryInfo(iRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER,
                       @Status, Len, Index) then RaiseLastOSError();

  { Throw Exception is StatusCode >= 300 BUT not 500. SOAP faulty envelopes comes with that set }
  if (Status >= 300) and (Status <> 500) then begin
    Index := 0;
    Size := MaxStatusText;
    SetLength(S, Size);
    if HttpQueryInfo(iRequest, HTTP_QUERY_STATUS_TEXT, @S[1], Size, Index) then begin
      SetLength(S, Size);
      raise EROException.CreateFmt('%s (%d)', [S, Status]);
    end;
  end;

  Index := 0;
  { get total size }
  if not HttpQueryInfo(iRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, @lTotalSize, Len, Index) then begin
    { do not raise an exception when there is no "Content-Length" value in result header }
    if GetLastError <> ERROR_HTTP_HEADER_NOT_FOUND then begin
      RaiseLastOSError(); //TotalSize = 0;//
    end else begin
      SetLastError(0);
      lTotalSize := 0;
    end;
  end;

  TriggerProgress(ptStart, pdReceiving, 0, lTotalSize);

  lReceivedSize := 0;
  dwSize := 0;
  dwDownloaded := 0;
    while (true) do begin  //循环检测
      // The call to InternetQueryDataAvailable determines the amount of data available to download.
      while (not InternetQueryDataAvailable(iRequest, dwSize, 0, 0))  //检测数据不可读 则暂停一下
      and (GetLastError = ERROR_IO_PENDING) do begin                  //可读则在dwSize返回数据大小
        Sleep(1);
      end;

      // Allocates a buffer of the size returned by InternetQueryDataAvailable
      GetMem(lpszData, dwSize + 1);
      try
        // Reads the data from the HINTERNET handle.
        while (not InternetReadFile(iRequest, lpszData, dwSize, dwDownloaded))
        and (GetLastError = ERROR_IO_PENDING) do begin
          Sleep(1);
        end;
        if (GetLastError >= const_WinInetErrorCode_MIN) and (GetLastError <= const_WinInetErrorCode_MAX) then begin
          Check(True);
        end;
        if GetLastError <> 0 then begin
          // Adds a null terminator to the end of the data buffer
          lpszData[dwDownloaded] := #0;
        end;

        ioData.Write(lpszData[0], dwDownloaded);
        Inc(lReceivedSize, dwDownloaded);
      finally
        FreeMem(lpszData);
      end;

      TriggerProgress(ptInProgress, pdReceiving, lReceivedSize, lTotalSize); //更新进度

      // Check the size of the remaining data. If it is zero, break.
      if (dwDownloaded = 0) then begin
        break;
      end;
    end;

  ioData.seek(0, soFromBeginning);

  TriggerProgress(ptDone, pdReceiving, 0,0);
end;

 

由于WinInet没有提供源码,不过从帮助上看,好像就是将API函数做了对应的参数转换,网络底层通信暂时不跟进了.等以后再研究.其实上面的代码已经很清楚了,使用HTTP进行客户端编程的主要几个函数都有体现.等有空再看看Indy通道的实现.客户端的通信实现起来毕竟比较简单,关键还是要看看服务端.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值