2012-02-17 2 views
4

TCP 서버를 만들고 클라이언트에게 메시지를 보내고 싶습니다. 필요에 따라 TCP 서버의 OnExecute 이벤트가 아닙니다.OnExecute 이벤트가없는 TCPserver

메시지 보내기/받기에 문제가 없습니다. 나는 그것을 좋아한다 :

procedure TFormMain.SendMessage(IP, Msg: string); 
var 
    I: Integer; 
begin 
    with TCPServer.Contexts.LockList do 
    try 
    for I := 0 to Count-1 do 
     if TIdContext(Items[I]).Connection.Socket.Binding.PeerIP = IP then 
     begin 
     TIdContext(Items[I]).Connection.IOHandler.WriteBuffer(Msg[1], Length(Msg)); 
     // and/or Read 
     Break; 
     end; 
    finally 
    TCPServer.Contexts.UnlockList; 
    end; 
end; 

참고 1 : OnExecute를 사용하지 않으면 클라이언트가 연결할 때 프로그램에서 예외가 발생한다.
참고 2 : 아무 것도하지 않고 OnExecute를 사용하면 CPU 사용량이 % 100으로 이동합니다.
참고 3 : TCP 클라이언트를 변경할 기회가 없습니다.

그럼 어떻게해야합니까?

+0

'OnExecute'는 클라이언트 요청에서만 호출되지 않습니까? – jpfollenius

+1

아니요. 'OnExecute'는 연결 수명 동안 연속 루프에서 호출됩니다. –

답변

4

OnExecute를 사용하고 아무 것도하지 않으면 잠시 동안 (예 : 10 밀리 초) 절전()하십시오. 각 연결에는 자체 OnExecute 처리기가 있으므로 개별 연결에만 영향을 미칩니다.

+0

감사 Misha, 나는 왜 전에 그것을 시도하지 않은지 모르겠다 :) – SimaWB

1

구성 요소 집합은 네트워크 연결에서 차단 을 에뮬레이트하도록 설계되었습니다. OnExecute 이벤트 핸들러에 모든 코드를 캡슐화해야합니다. 은 대부분의 프로토콜이 어떤 방식 으로든 차단 (명령 보내기, 응답 대기 중 등)하기 때문에 쉽게 이루어집니다.

분명히 작동 모드가 마음에 들지 않으므로 차단하지 않고 작동하는 것이 좋습니다. 사용하려는 방법에 맞게 설계된 구성 요소 제품군을 사용하는 것이 좋습니다. ICS 제품군을 사용해보십시오! ICS는 스레드를 사용하지 않고 모든 작업은 이벤트 핸들러에서 수행됩니다.

+1

Downvoter, 정교한 신경? –

+1

당신은 동기식/비동기식 통신과 블로킹/블로킹 소켓 작동을 혼란스럽게 생각합니다. 비동기 통신은 Indy와 완벽하게 잘 작동합니다. 서버는 클라이언트에 데이터를 게시 한 다음 클라이언트가 응답 할 때까지 기다릴 수도 있고하지 않을 수도 있습니다. 이는 예를 들어 메시지 브로커 시스템의 정상 작동 모드입니다. – mjn

1

OnExecute 처리기에서 TEvent 및 TMonitor와 같은 스레드 통신 방법을 사용하여 클라이언트에 대한 데이터가있을 때까지 대기 할 수 있습니다.

TMonitor는 Delphi 2009부터 사용할 수 있으며 최소 CPU 사용으로 알림을 보내고 받기위한 방법 (대기, 펄스 및 펄스)을 제공합니다.

+0

'TMonitor'가 심각하게 손상되었습니다. 처음 도입 된 이래로 끊임없는 문제가 있었으며, 엠바 카데로 (Ensarcadero)는 여전히 신뢰할 수 있도록 노력하고 있습니다. –

7

TIdTCPServer에는 기본적으로 할당 된 OnExecute 이벤트 처리기가 필요합니다. 이 문제를 해결하려면 TIdTCPServer에서 새 클래스를 파생시키고 가상 CheckOkToBeActive() 메서드를 재정의해야하며 가상 클래스를 재정 의하여 Sleep()으로 호출해야합니다. 그렇지 않으면 이벤트 처리기를 할당하고 Sleep()을 호출하게합니다.

그러나 이것은 TIdTCPServer의 효과적인 사용은 아닙니다. 더 나은 디자인은 고객의 SendMessage() 메소드 내부에서 직접 아웃 바운드 데이터를 클라이언트에 작성하지 않는 것입니다. 오류가 발생하기 쉽고 (WriteBuffer()의 예외를 catch하지 않음) 쓰기 도중 SendMessage()을 차단할뿐만 아니라 통신을 직렬화합니다 (클라이언트 2는 클라이언트 1이 먼저 데이터를 수신 할 때까지 데이터를 수신 할 수 없음). 훨씬 더 효과적인 디자인은 각 클라이언트에게 자체 스레드 안전 아웃 바운드 큐를 제공 한 다음 필요에 따라 SendMessage() 데이터를 각 클라이언트의 큐에 넣는 것입니다. 그런 다음 OnExecute 이벤트를 사용하여 각 클라이언트의 대기열을 확인하고 실제 작성을 수행 할 수 있습니다. 이 방법으로 SendMessage()이 더 이상 차단되지 않으며 오류가 발생하기 쉽고 클라이언트를 병렬로 작성할 수 있습니다.이 같은

시도 뭔가 :

uses 
    ..., IdThreadSafe; 

type 
    TMyContext = class(TIdServerContext) 
    private 
    FQueue: TIdThreadSafeStringList; 
    FEvent: TEvent; 
    public 
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override; 
    destructor Destroy; override; 
    procedure AddMsgToQueue(const Msg: String); 
    function GetQueuedMsgs: TStrings; 
    end; 

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); 
begin 
    inherited; 
    FQueue := TIdThreadSafeStringList.Create; 
    FEvent := TEvent.Create(nil, True, False, ''); 
end; 

destructor TMyContext.Destroy; 
begin 
    FQueue.Free; 
    FEvent.Free; 
    inherited; 
end; 

procedure TMyContext.AddMsgToQueue(const Msg: String); 
begin 
    with FQueue.Lock do 
    try 
    Add(Msg); 
    FEvent.SetEvent; 
    finally 
    FQueue.Unlock; 
    end; 
end; 

function TMyContext.GetQueuedMsgs: TStrings; 
var 
    List: TStringList; 
begin 
    Result := nil; 
    if FEvent.WaitFor(1000) <> wrSignaled then Exit; 
    List := FQueue.Lock; 
    try 
    if List.Count > 0 then 
    begin 
     Result := TStringList.Create; 
     try 
     Result.Assign(List); 
     List.Clear; 
     except 
     Result.Free; 
     raise; 
     end; 
    end; 
    FEvent.ResetEvent; 
    finally 
    FQueue.Unlock; 
    end; 
end; 

procedure TFormMain.FormCreate(Sender: TObject); 
begin 
    TCPServer.ContextClass := TMyContext; 
end; 

procedure TFormMain.TCPServerExecute(AContext: TIdContext); 
var 
    List: TStrings; 
    I: Integer; 
begin 
    List := TMyContext(AContext).GetQueuedMsgs; 
    if List = nil then Exit; 
    try 
    for I := 0 to List.Count-1 do 
     AContext.Connection.IOHandler.Write(List[I]); 
    finally 
    List.Free; 
    end; 
end; 

procedure TFormMain.SendMessage(const IP, Msg: string); 
var 
    I: Integer; 
begin 
    with TCPServer.Contexts.LockList do 
    try 
    for I := 0 to Count-1 do 
    begin 
     with TMyContext(Items[I]) do 
     begin 
     if Binding.PeerIP = IP then 
     begin 
      AddMsgToQueue(Msg); 
      Break; 
     end; 
     end; 
    end; 
    finally 
    TCPServer.Contexts.UnlockList; 
    end; 
end; 
+1

@ JerryDodge : 고정 –

1

나는 비슷한 상황 복용 100 %의 CPU를 가지고 있으며 IdThreadComponent과를 추가하여 해결 :

void __fastcall TForm3::IdThreadComponent1Run(TIdThreadComponent *Sender) 
{ 
    Sleep(10); 
} 

가 권리인가? 나는 잘 모르겠다.

관련 문제