delphi - 改善旧系统(尤其是笔记本电脑(Delphi 6))上的Indy HTTP客户端性能?

标签 delphi http sockets indy robot

我有一个用Delphi 6编写的应用程序,该应用程序使用Indy 9.0.18 HTTP客户端组件来驱动机器人。如果我在安装Indy时正确地记得,则版本10和更高版本不适用于Delphi 6,因此我使用的是9.0.18。在较新的桌面上,该程序可以完全正常运行。但是我在较旧的笔记本电脑上遇到了一些问题。请注意,在所有情况下,我绝对不会收到任何错误或异常。

机械手是一个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是双核,但仅启用了一个核。

  • 这两个系统都不能获得平稳的运动,除非如下所示短暂。

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

    我知道,数据方面的另一个区别是,旧的奔腾4台式机虽然是近乎古老的PC,但其内存却是笔记本电脑的2.5倍。也许真正的罪魁祸首是一些内存颠簸?机器人时不时地会平稳运行,但很快又会再次陷入停顿状态,这表明在没有任何事情破坏 socket 上的交互作用的情况下,偶尔可能会有平稳的运动。请注意,机器人还会同时在PC和PC之间传输音频,并在PC和PC之间传输视频(但不能双向传输),因此在驱动机器人时需要进行大量的处理。

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

    我尝试将线程优先级提高到THREAD_PRIORITY_TIME_CRITICAL,但效果绝对为零。注意,我确实使用GetThreadPriority()来确保确实提高了优先级,并且在调用SetThreadPriority()之前最初返回0之后,它返回了15的值。

    1)既然我的几个最佳用户都拥有它们,那么该怎么做才能提高这些老式低功耗系统的性能呢?

    2)我的另一个问题是有人知道Indy是否必须重建每个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
    

    最佳答案

    要正确使用HTTP保持 Activity ,请使用FIdHTTPClient.Request.Connection := 'keep-alive'而不是FIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive')或设置FIdHTTPClient.ProtocolVersion := pv1_1。至少这就是在Indy 10中的工作方式。当我有机会的时候,我将仔细检查Indy 9。

    无论使用哪种版本,机器人都必须首先支持保持 Activity 状态,否则TIdHTTP别无选择,只能为每个请求建立新的套接字连接。如果漫游器发送不包含Connection: keep-alive header 的HTTP 1.0响应,或包含Connection: close header 的HTTP 1.1响应,则不支持保持 Activity 状态。

    关于delphi - 改善旧系统(尤其是笔记本电脑(Delphi 6))上的Indy HTTP客户端性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10150620/

    相关文章:

    c# - 使用 C# 从 Delphi DLL 中检索记录数组

    delphi - 转换 TStringList 中的特殊字符

    android - android可以使用flash list 文件(.f4m)直接播放动态HTTP流吗

    c - 绑定(bind)和套接字编程

    c# - NetworkStream,是否有类似于 SerialPort 的 DataReceived 的东西? (C#)

    delphi - 检测已安装的 lazarus IDE

    delphi - 元类默认参数值 (Delphi 2009)

    http - HTTP 上传有很多开销吗?

    性能:绝对 URL 与相对 URL

    java - 客户端应该具有与服务器相同的 keystore 吗?