4

Indy 9.0.18 HTTP クライアント コンポーネントを使用してロボットを駆動する Delphi 6 で作成されたアプリケーションがあります。Indy インストールを行ったときの記憶が正しければ、バージョン 10 以降は Delphi 6 で動作しないため、9.0.18 を使用しています。新しいデスクトップでは、プログラムは完全に正常に動作します。しかし、私は古いラップトップでいくつかの問題を抱えています。いずれの場合も、エラーや例外がまったく発生していないことに注意してください。

ロボットは、HTTP 要求に応答してロボットを駆動する HTTP サーバーです。連続モーションを取得するには、連続ループでドライブコマンド (前進など) を送信する必要があります。ドライブ コマンドは、ロボットが応答する数値 IP アドレスへの HTTP 要求です。要求で使用される URL にはドメイン名が含まれていないため、ドメイン名の解決が問題になることはありません (私は信じています)。最後の HTTP 要求からの応答を取得したら、すぐに向きを変えて次のものを送信します。ロボットが小さなぎくしゃくした動きをし、モーターが落ち着いて停止する時間があるため、スムーズな動きに必要な継続的な運動量に達することができないため、システムがループに追いつくのに苦労していることがわかります。

問題が発生している 2 つのシステムはラップトップ コンピュータで、次の 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 はデュアル コアですが、有効なコアは 1 つだけです。

これらのシステムはどちらも、以下に示すように過渡的な場合を除き、滑らかな動きを得ることができません。

ラップトップの問題だと私が言う理由は、上記の 2 つのシステムがラップトップであるためです。対照的に、ハイパースレッディングを備えた古い Pentium 4 シングル コア (2.8 GHz、2.5 GB のメモリ) を使用しています。滑らかな動き得られます。ただし、連続してロボットの動きが著しく遅くなることは、HTTP 要求の間にまだわずかな遅延があることを示していますが、モーターを完全に停止するのに十分ではないため、クアッドコアまたはデュアルコアデスクトップよりも著しく遅いとはいえ、動きはまだ連続しています。 .

データ ポイントのもう 1 つの判別要因は、ほとんど古風な PC であるにもかかわらず、古い Pentium 4 デスクトップがラップトップの 2.5 倍のメモリを搭載していることです。おそらく本当の原因は、メモリのスラッシングでしょうか? 時々、ロボットはスムーズに動作しますが、すぐに再び吃音に戻ります。これは、ソケットを介した相互作用を台無しにするものがない場合、スムーズな動作が可能な場合があることを示しています。ロボットはまた、PC との間で音声をストリーミングし、ビデオを PC にストリーミングします (ただし、逆方向ではありません)。そのため、ロボットの駆動に伴ってかなりの量の処理が行われます。

Indy HTTP クライアントは、Delphi のメイン スレッドではなく、バックグラウンド スレッドで作成され、スリープ状態のないタイトなループで実行されます。ループ内で PeekMessage() 呼び出しを実行して、現在ループしているコマンドの代わりにループする必要がある新しいコマンドが入ってきたかどうかを確認します。ループ内で GetMessage() を呼び出す理由は、ロボットがアイドル状態であると想定されているときにスレッドがブロックされるようにするためです。つまり、ユーザーが再度駆動することを決定するまで、HTTP 要求がロボットに送信されないようにするためです。その場合、新しいコマンドをスレッドにポストすると、GetMessage() 呼び出しのブロックが解除され、新しいコマンドがループされます。

スレッドの優先度を THREAD_PRIORITY_TIME_CRITICAL に上げてみましたが、まったく効果がありませんでした。GetThreadPriority() を使用して優先度が実際に上げられ、SetThreadPriority() 呼び出しの前に最初に 0 が返された後、値 15 が返されたことを確認したことに注意してください。

1) では、これらの古い低電力システムのパフォーマンスを向上させるにはどうすればよいでしょうか?

2)私が持っているもう1つの質問は、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
4

1 に答える 1

4

HTTPキープアライブを正しく使用するには、FIdHTTPClient.Request.Connection := 'keep-alive'の代わりにを使用するFIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive')か、を設定しFIdHTTPClient.ProtocolVersion := pv1_1ます。少なくとも、それがIndy 10でどのように機能するかです。機会があれば、Indy9を再確認します。

使用するバージョンに関係なく、ロボットはそもそもキープアライブをサポートする必要があります。そうでない場合TIdHTTPは、要求ごとに新しいソケット接続を確立するしかありません。ロボットがヘッダーを含まないHTTP1.0Connection: keep-alive応答、またはヘッダーを含むHTTP 1.1応答を送信する場合Connection: close、keep-alivesはサポートされません。

于 2012-04-14T03:59:46.707 に答える