我有一个用Delphi 6编写的应用程序,它使用Indy9.0.18 HTTP客户端组件来驱动机器人。如果我还记得当我安装Indy时,版本10和更新版本不能与Delphi 6一起工作,所以我使用的是9.0.18。在较新的桌面上,程序运行完全正常。但我在旧笔记本电脑上遇到了一些问题。注意:在所有情况下,我绝对没有错误或异常。
机器人是一个HTTP服务器,它响应HTTP请求来驱动它。要获得连续运动,您必须在一个连续循环中发送一个驱动命令(例如-前进)。驱动器命令是对机器人响应的数字IP地址的HTTP请求,请求中使用的URL不涉及域名,因此排除了域名解析的问题(我相信)。从上一个HTTP请求获得响应后,立即转身并发送下一个请求。你可以知道当一个系统跟不上回路的时候,因为机器人做小的抖动运动,永远不能达到平稳运动所需要的持续动量,因为电机有时间停下来。
有问题的两个系统是膝上型计算机,具有以下CPU和内存,并且正在运行Windows SP3:
这两种系统都不能获得平滑的运动,除非如下面所示的瞬变。
我之所以说这是笔记本电脑的问题,是因为上面两个系统都是笔记本电脑。相比之下,我有一个旧的奔腾4单核超线程(2.8GHz,2.5GB内存)。它可以获得平滑的运动。然而,尽管机器人的移动速度明显较慢,这表明HTTP请求之间仍有轻微的延迟,但不足以完全停止马达,因此移动仍然是连续的,尽管明显慢于我的四核或双核桌面上。
我知道,数据点中的另一个区别是,旧的奔腾4桌面拥有2.5倍于笔记本电脑的内存,尽管它是一台近乎过时的PC。也许真正的罪魁祸首是一些记忆的打击?机器人不时地平稳地运行,但很快又会恢复口吃,这表明没有任何东西干扰了插座上的交互,平滑的运动偶尔也是可能的。请注意,机器人也是从PC和视频传输到PC (但不是其他方向)的音频流,因此在驱动机器人的同时也会进行大量的处理。
Indy客户端是在后台线程上创建和运行的,而不是在主Delphi线程上运行,在一个没有睡眠状态的紧循环中运行。它确实会在循环中执行PeekMessage()调用,以查看是否有任何新的命令被循环,而不是当前循环的命令。循环中GetMessage()调用的原因是,当机器人应该处于空闲状态时,线程会阻塞,也就是说,在用户决定再次驱动它之前,不应该向它发送任何HTTP请求。在这种情况下,将一个新命令提交给线程,解除对GetMessage()调用的阻塞,并将新命令循环起来。
我尝试将线程优先级提高到THREAD_PRIORITY_TIME_CRITICAL,但这完全没有效果。注意,我确实使用了GetThreadPriority()来确保优先级确实被引发,并且在最初返回SetThreadPriority()调用之前返回0之后,它返回了一个15的值。
那么,我能做些什么来改善这些老旧的低功耗系统的性能呢?因为我的几个最好的用户都有它们?
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发布于 2012-04-14 03:59:46
若要正确使用HTTP,请使用FIdHTTPClient.Request.Connection := 'keep-alive'而不是FIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive'),或设置FIdHTTPClient.ProtocolVersion := pv1_1。至少这是印第10的工作方式。当我有机会的时候,我会再次检查印第9。
无论您使用哪个版本,机器人都必须首先支持“保持生命”,否则TIdHTTP别无选择,只能为每个请求建立一个新的套接字连接。如果机器人发送不包含Connection: keep-alive头的HTTP1.0响应,或发送包含Connection: close头的HTTP1.1响应,则不支持“保持生命”。
https://stackoverflow.com/questions/10150620
复制相似问题