2012-04-14 43 views
4

我有一个用Delphi 6编写的应用程序,它使用Indy 9.0.18 HTTP客户端组件驱动机器人。如果我记得我在做Indy安装时是否正确,版本10和更新版本不适用于Delphi 6,所以我使用的是9.0.18。在较新的桌面上,程序运行得很好。但是我在旧笔记本电脑上遇到了一些问题。注意在所有情况下,我绝对没有错误或例外。改进旧系统(特别是笔记本电脑(Delphi 6))上的Indy HTTP客户端性能?

机器人是一个响应HTTP请求来驱动它的HTTP服务器。要获得连续运动,您必须发送一个驱动器命令(例如 - 向前移动)以连续循环。驱动器命令是对机器人响应的数字IP地址的HTTP请求,请求中使用的URL不包含域名,以便排除域名解析的问题(我相信)。一旦得到最后一次HTTP请求的响应,您立即转身并发送下一个。您可以知道系统何时难以跟上环路,因为机器人会产生小的抖动,永远无法达到平滑运动所需的持续动量,因为电机有时间安顿下来并停下来。

即有麻烦,这两个系统是膝上型计算机,并具有以下的CPU和内存,并运行Windows XP SP3:

  • AMD的Turion 64 X2移动技术TL-50(双核),1 GB主内存,1.6 GHz,双核。
  • AMD Sempron(TM)140处理器,1 GB主内存,2.7 GHZ。请注意,此CPU是双核心,但只启用一个核心。

这两个系统都不能获得平稳的运动,除了瞬态如下所示。

我说这是笔记本电脑问题的原因是因为上述两个系统都是笔记本电脑。相比之下,我有一个带超线程(2.8 GHz,2.5 GB内存)的老式Pentium 4单核。它可以获得平稳的动作。然而,尽管机器人持续运动的速度明显较慢,表明HTTP请求之间仍然存在轻微延迟,但不足以完全停止电机,因此运动仍然是连续的,尽管速度明显慢于我的四核或双核桌面。

我知道数据点中的另一个判别式是旧Pentium 4桌面在笔记本电脑上的内存是2.5倍,尽管它是一台几乎过时的PC。也许真正的罪魁祸首是一些记忆打乱?时不时机器人运行顺畅,但很快又恢复为口吃,表明没有任何东西堵塞插口上的相互作用,平稳运动偶尔是可能的。 请注意,机器人也将音频流式传输到PC和流式传输视频到PC(而不是其他方向),因此在驱动机器人时会进行大量的处理。

Indy HTTP客户端在没有睡眠状态的紧密循环中创建并在后台线程上运行,而不是在主Delphi线程上运行。它确实在循环中执行PeekMessage()调用,以查看是否有新的命令进入,应循环而不是当前循环的命令。循环中GetMessage()调用的原因是,当机器人应该处于空闲状态时,线程会阻塞,也就是说,直到用户决定再次驱动它时,才会向它发送HTTP请求。在这种情况下,向线程发布新命令将取消对GetMessage()调用的阻止,并且新命令将循环。

我试着将线程的优先级提高到THREAD_PRIORITY_TIME_CRITICAL,但那绝对没有影响。注意我确实使用了GetThreadPriority()来确保优先级确实提高,并且在SetThreadPriority()调用之前最初返回0后返回值15。

1)所以我能做些什么来改善这些老的低功耗系统性能,因为我的几个最好的用户拥有它们呢?

2)另一个问题我已经是没有人知道是否有印重建连接的每个HTTP请求,或者它智能缓存插座连接,这样就不会是什么问题?如果我诉诸使用较低级别的Indy客户端套接字并执行了自己的HTTP请求,它会起什么作用?我想避免这种可能性,因为这将是一个重要的重写,但如果这是一个高度确定的解决方案,让我知道。

我已经包含了以下的情况下,你可以看到什么低效的后台线程循环。要执行的命令通过主线程的异步PostThreadMessage()操作发布到线程。

// Does the actual post to the robot. 
function doPost(
      commandName, // The robot command (used for reporting purposes) 
      // commandString, // The robot command string (used for reporting purposes) 
      URL,   // The URL to POST to. 
      userName,  // The user name to use in authenticating. 
      password,  // The password to use. 
      strPostData  // The string containing the POST data. 
       : string): string; 
var 
    RBody: TStringStream; 
    bRaiseException: boolean; 
    theSubstituteAuthLine: string; 
begin 
    try 
     RBody := TStringStream.Create(strPostData); 

     // Custom HTTP request headers. 
     FIdHTTPClient.Request.CustomHeaders := TIdHeaderList.Create; 

     try 
      FIdHTTPClient.Request.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; 
      FIdHTTPClient.Request.ContentType := 'application/xml'; 
      FIdHTTPClient.Request.ContentEncoding := 'utf-8'; 
      FIdHTTPClient.Request.CacheControl := 'no-cache'; 
      FIdHTTPClient.Request.UserAgent := 'RobotCommand'; 

      FIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive'); 
      FIdHTTPClient.Request.CustomHeaders.Add('Keep-Alive: timeout=30, max=3 header'); 

      // Create the correct authorization line for the commands stream server. 
      theSubstituteAuthLine := 
       basicAuthenticationHeaderLine(userName, password); 

      FIdHTTPClient.Request.CustomHeaders.Add(theSubstituteAuthLine); 

      Result := FIdHTTPClient.Post(URL, RBody); 

      // Let the owner component know the HTTP operation 
      // completed, whether the response code was 
      // successful or not. Return the response code in the long 
      // parameter. 
      PostMessageWithUserDataIntf(
           FOwner.winHandleStable, 
           WM_HTTP_OPERATION_FINISHED, 
           POSTMESSAGEUSERDATA_LPARAM_IS_INTF, 
           TRovioCommandIsFinished.Create(
             FIdHttpClient.responseCode, 
             commandName, 
             strPostData, 
             FIdHttpClient.ResponseText) 
           ); 
     finally 
      FreeAndNil(RBody); 
     end; // try/finally 
    except 
     { 
      Exceptions that occur during an HTTP operation should not 
      break the Execute() loop. That would render this thread 
      inactive. Instead, call the background Exception handler 
      and only raise an Exception if requested. 
     } 
     On E: Exception do 
     begin 
      // Default is to raise an Exception. The background 
      // Exception event handler, if one exists, can 
      // override this by setting bRaiseException to 
      // FALSE. 
      bRaiseException := true; 

      FOwner.doBgException(E, bRaiseException); 

      if bRaiseException then 
       // Ok, raise it as requested. 
       raise; 
     end; // On E: Exception do 
    end; // try/except 
end; 


// The background thread's Excecute() method and loop (excerpted). 
procedure TClientThread_roviosendcmd_.Execute; 
var 
    errMsg: string; 
    MsgRec : TMsg; 
    theHttpCliName: string; 
    intfCommandTodo, intfNewCommandTodo: IRovioSendCommandsTodo_indy; 
    bSendResultNotification: boolean; 
    responseBody, S: string; 
    dwPriority: DWORD; 
begin 
    // Clear the current command todo and the busy flag. 
    intfCommandTodo := nil; 
    FOwner.isBusy := false; 

    intfNewCommandTodo := nil; 

    // -------- BEGIN: THREAD PRIORITY SETTING ------------ 

    dwPriority := GetThreadPriority(GetCurrentThread); 

    {$IFDEF THREADDEBUG} 
    OutputDebugString(PChar(
     Format('Current thread priority for the the send-commands background thread: %d', [dwPriority]) 
    )); 
    {$ENDIF} 

    // On single CPU systems like our Dell laptop, the system appears 
    // to have trouble executing smooth motion. Guessing that 
    // the thread keeps getting interrupted. Raising the thread priority 
    // to time critical to see if that helps. 
    if not SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL) then 
     RaiseLastOSError; 

    dwPriority := GetThreadPriority(GetCurrentThread); 

    {$IFDEF THREADDEBUG} 
    OutputDebugString(PChar(
     Format('New thread priority for the the send-commands background thread after SetThreadPriority() call: %d', [dwPriority]) 
    )); 
    {$ENDIF} 

    // -------- END : THREAD PRIORITY SETTING ------------ 

    // try 

    // Create the client Indy HTTP component. 
    theHttpCliName := '(unassigned)'; 

    theHttpCliName := FOwner.Name + '_idhttpcli'; 

    // 1-24-2012: Added empty component name check. 
    if theHttpCliName = '' then 
     raise Exception.Create('(TClientThread_roviosendcmd_.Execute) The client HTTP object is nameless.'); 

    FIdHTTPClient := TIdHTTP.Create(nil); 

    { If GetMessage retrieves the WM_QUIT, the return value is FALSE and } 
    { the message loop is broken.           } 
    while not Application.Terminated do 
    begin 
     try 
      bSendResultNotification := false; 

      // Reset the variable that detects new commands to do. 
      intfNewCommandTodo := nil; 

      { 
       If we are repeating a command, use PeekMessage so that if 
       there is nothing in the queue, we do not block and go 
       on repeating the command. Note, intfCommandTodo 
       becomes NIL after we execute a single-shot command. 

       If we are not repeating a command, use GetMessage so 
       it will block until there is something to do or we 
       quit. 
      } 
      if Assigned(intfCommandTodo) then 
      begin 
       // Set the busy flag to let others know we have a command 
       // to execute (single-shot or looping). 
       // FOwner.isBusy := true; 

       { 
        Note: Might have to start draining the queue to 
        properly handle WM_QUIT if we have problems with this 
        code. 
       } 

       // See if we have a new command todo. 
       if Integer(PeekMessage(MsgRec, 0, 0, 0, PM_REMOVE)) > 0 then 
       begin 
        // WM_QUIT? 
        if MsgRec.message = WM_QUIT then 
         break // We're done. 
        else 
         // Recover the command todo if any. 
         intfNewCommandTodo := getCommandToDo(MsgRec); 
       end; // if Integer(PeekMessage(MsgRec, FWndProcHandle, 0, 0, PM_REMOVE)) > 0 then 
      end 
      else 
      begin 
       // Not repeating a command. Block until something new shows 
       // up or we quit. 
       if GetMessage(MsgRec, 0, 0, 0) then 
        // Recover the command todo if any. 
        intfNewCommandTodo := getCommandToDo(MsgRec) 
       else 
        // GetMessage() returned FALSE. We're done. 
        break; 
      end; // else - if Assigned(intfCommandTodo) then 

      // Did we get a new command todo? 
      if Assigned(intfNewCommandTodo) then 
      begin 
       // ----- COMMAND TODO REPLACED! 

       // Update/Replace the command todo variable. Set the 
       // busy flag too. 
       intfCommandTodo := intfNewCommandTodo; 
       FOwner.isBusy := true; 

       // Clear the recently received new command todo. 
       intfNewCommandTodo := nil; 

       // Need to send a result notification after this command 
       // executes because it is the first iteration for it. 
       // (repeating commands only report the first iteration). 
       bSendResultNotification := true; 
      end; // if Assigned(intfNewCommandTodo) then 

      // If we have a command to do, make the request. 
      if Assigned(intfCommandTodo) then 
      begin 
       // Check for the clear command. 
       if intfCommandTodo.commandName = 'CLEAR' then 
       begin 
        // Clear the current command todo and the busy flag. 
        intfCommandTodo := nil; 
        FOwner.isBusy := false; 

        // Return the response as a simple result. 
        // FOwner.sendSimpleResult(newSimpleResult_basic('CLEAR command was successful'), intfCommandToDo); 
       end 
       else 
       begin 
        // ------------- SEND THE COMMAND TO ROVIO -------------- 
        // This method makes the actual HTTP request via the TIdHTTP 
        // Post() method. 
        responseBody := doPost(
         intfCommandTodo.commandName, 
         intfCommandTodo.cgiScriptName, 
         intfCommandTodo.userName_auth, 
         intfCommandTodo.password_auth, 
         intfCommandTodo.commandString); 

        // If this is the first or only execution of a command, 
        // send a result notification back to the owner. 
        if bSendResultNotification then 
        begin 
         // Send back the fully reconstructed response since 
         // that is what is expected. 
         S := FIdHTTPClient.Response.ResponseText + CRLF + FIdHTTPClient.Response.RawHeaders.Text + CRLF + responseBody; 

         // Return the response as a simple result. 
         FOwner.sendSimpleResult(newSimpleResult_basic(S), intfCommandToDo); 
        end; // if bSendResultNotification then 

        // If it is not a repeating command, then clear the 
        // reference. We don't need it anymore and this lets 
        // us know we already executed it. 
        if not intfCommandTodo.isRepeating then 
        begin 
         // Clear the current command todo and the busy flag. 
         intfCommandTodo := nil; 
         FOwner.isBusy := false; 
        end; // if not intfCommandTodo.isRepeating then 
       end; // if intfCommandTodo.commandName = 'CLEAR' then 
      end 
      else 
       // Didn't do anything this iteration. Yield 
       // control of the thread for a moment. 
       Sleep(0); 

     except 
      // Do not let Exceptions break the loop. That would render the 
      // component inactive. 
      On E: Exception do 
      begin 
       // Post a message to the component log. 
       postComponentLogMessage_error('ERROR in client thread for socket(' + theHttpCliName +'). Details: ' + E.Message, Self.ClassName); 

       // Return the Exception to the current EZTSI if any. 
       if Assigned(intfCommandTodo) then 
       begin 
        if Assigned(intfCommandTodo.intfTinySocket_direct) then 
         intfCommandTodo.intfTinySocket_direct.sendErrorToRemoteClient(exceptionToErrorObjIntf(E, PERRTYPE_GENERAL_ERROR)); 
       end; // if Assigned(intfCommandTodo) then 

       // Clear the command todo interfaces to avoid looping an error. 
       intfNewCommandTodo  := nil; 

       // Clear the current command todo and the busy flag. 
       intfCommandTodo := nil; 
       FOwner.isBusy := false; 
      end; // On E: Exception do 
     end; // try 
    end; // while not Application.Terminated do 
+0

最新的印第安纳波利斯9是9.0.50,所以如果你留在印第安纳波利斯9印10,你应该甚至升级支持回D5一路,虽然。 – 2012-04-14 03:40:54

回答

3

要使用的HTTP保持alives正确,使用FIdHTTPClient.Request.Connection := 'keep-alive'代替FIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive'),或者设置FIdHTTPClient.ProtocolVersion := pv1_1。至少Indy 10是如何运作的。当我有机会的时候,我会仔细检查Indy 9。

无论您使用哪个版本,机器人都必须首先支持保持活动,否则TIdHTTP别无选择,只能为每个请求创建一个新的套接字连接。如果机器人发送不包含Connection: keep-alive标头的HTTP 1.0响应或包含Connection: close标头的HTTP 1.1响应,则不支持保持活动。

+0

你好雷米。机器人确实支持保持活着。我检查了响应头和这里就是它返回: HTTP/1.0 200 OK 日期:周四,1970年01一月0点25分03秒GMT 服务器:WYM/1.0 连接:保持活动 保持活动:超时= 3600,max = 108000 Content-Type:text/plain Content-Length:395 Last-Modified:Thu,01 Jan 1970 00:25:03 GMT Pragma:no-cache Cache-Control:no-cache 过期时间:1970年1月1日00:00:00 GMT – 2012-04-14 06:57:43

+0

'TIdHTTP'是否表示保持活动,为多个请求重复使用单个连接?还是每次都会断开连接并建立一个新连接?使用Wireshark等数据包嗅探器实时查看网络活动,以查看实际发生的情况。 – 2012-04-15 00:56:12

+0

我可以依靠流号码来告诉我连接是否被重新使用?也就是说,如果流号码更改每个请求,那么保持活动不被遵守?或者我应该寻找其他特定的Wireshark日志项?(s) – 2012-04-15 21:09:23

相关问题