Конференция "Сети" » Сдвиг данных TcpClient при задержках чтения буфера [WinXP]
 
  • Alik (23.09.11 12:19) [0]
    Добрый день,

    С помощью компонент TTcpClient / TClientSocket соединюсь с  удаленным сервером на котором стоит 100 мегабитная сетевая карточка и получаю поток данных (примерно 15 мегабайт в минуту). Все бы ничего, да только когда мое приложение начинает заниматься другими вещами (графика, винчестер) происходит какой то сдвиг во входящих данных, проявляющийся в том что сегодняшние данные поступают завтра. После закрытия и повторного открытия порта сдвиг прекращается и все идет корректно.    

    Чтение буфера для ClientSocket идет по событию

    procedure TForm_Main.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
    var
     S: AnsiString;
    begin
     S := Socket.ReceiveText();
     if S = '' then Exit;
     Buf := Buf + S;
     Inc(BytesRead, Length(S));
     RedrawStatusBar;
    end;

    Чтение буфера для TcpClient происходит в потоке c
    Priority := tpTimeCritical;

    procedure TTcpPort.Execute;
    begin
     while not Terminated and TcpClient.Connected do begin
           BufData := ReadPort;
           Synchronize(SynchroMethod);
           end;
    end;

    function TTcpPort.ReadPort: String;
    const
     BufLen = 1024 * 30;
    var
     d: array[1..BufLen] of Char;
     i, BytesRead: Integer;
    begin
     try
       //Result := TcpClient.Receiveln;
       BytesRead := TcpClient.ReceiveBuf(d, SizeOf(d), 0);
       except
         on E: Exception do Exit;
       end;
       Result := '';
       for i := 1 to BytesRead do
           Result := Result + d[i];
    end;

    procedure TTcpPort.SynchroMethod;
    begin
     with F_Radar do begin
          if BufData <> '' then RawDataProcessing.TreatBuffer(BufData);
          BufData := '';
          end;
    end;

    Однако в обоих случаях проблема одинаковая.
    Если кто то сталкивался с таким, прошу помочь!
  • Alik (23.09.11 12:22) [1]
    Да, после того как повтыкал Apllication.ProcessMessages в процедуры работы с графикой и винтом, ситуация улучшилась, но не разрешилась!
  • FireMan_Alexey © (23.09.11 13:49) [2]
    Может у тебя работа со строками не правильная?
    Посмотри как твоя прога загружает проц... Если примерно 100% при отсутствующих Apllication.ProcessMessages, то добавь после Synchronize Sleep(1);
    посмотришь разницу... :)
    Еще за один раз (за ИНДИ не знаю), ReceiveBuf/ReceiveText больше 8192 не читает, если ты сам не конфигурировал размеры буферов!

    function TTcpPort.ReadPort: String;
    const
    BufLen = 1024 * 30;
    var
    d: array[1..BufLen] of Char;
    i, BytesRead: Integer;
    begin
    try
      //Result := TcpClient.Receiveln;
      BytesRead := TcpClient.ReceiveBuf(d, SizeOf(d), 0);
      except
        on E: Exception do Exit;
      end;
      Result := '';
      for i := 1 to BytesRead do
          Result := Result + d[i];

    end;

    отмеченное жирным полная хрень!!!!

    Var
     S:String;
     Size,Size1:Integer;
    Begin
      Size:=TcpClient.Receiveln; {Socket.ReceiveLength}
      Try
        SetLength(S,Size);
        Size1:=Tcp.ReceiveBuf(S[1],Size,0);
        If (Size1>0)and(Size1<>Size) Then
           {делай чего нибудь}
          Begin
             SetLength(S,Size1);
          End;
        Result:=S;
      finally
        SetLength(S,0);
      end;
    End;

    Обрати внимание на жирным выделенное!!!!!!
  • FireMan_Alexey © (23.09.11 13:55) [3]
    Priority := tpTimeCritical;
    Полная фигня!!!

    Зачем тебе это? А если к диску обратиться из этой нитки, то вообще вешаться?
    Пока не видел ни одной задачи, чтобы для принятия данных по сети нужен был приоритет реального времени...
  • Alik (23.09.11 15:25) [4]

    > Size:=TcpClient.Receiveln; {Socket.ReceiveLength}


    Метод не работает, Size - Integer, TcpClient.Receiveln - String.
  • FireMan_Alexey © (23.09.11 16:31) [5]
    >Alik

    А что по комментариям трудно догадаться?

    Size:=Socket.ReceiveLength;
    Ищи подобную ф-цию в своем компоненте или напиши свою!
  • FireMan_Alexey © (23.09.11 16:48) [6]
    Если не найдешь в компоненте...

    Function  ReceiveLength:Integer;
    Begin
     IOCTLSocket(FSock,FIONREAD,Result);
    End;
  • FireMan_Alexey © (23.09.11 16:54) [7]
    Я еще раз говорю, что скорее всего у тебя не правильная обработка данных приходящих от сервера!
    Сервер твой?
  • Alik (23.09.11 17:10) [8]

    > Я еще раз говорю, что скорее всего у тебя не правильная
    > обработка данных приходящих от сервера!
    > Сервер твой?


    Сервер это наше железо, работающее с сетевой картой WIZ830MJ, есть подозрение, что электронщик еще не полностью разобрался со всеми регистрами и прерываниями от карточки и проблемы начинаются с его стороны когда сервер не может протолкнуть данные клиенту по причине занятости последнего. Старые данные все таки досылаются завтрашним днем, но сдвинутые и перемешанные. Стоит клиенту закрыть и открыть свой порт, все нормализуется.
  • Alik (23.09.11 18:33) [9]

    > Var
    >  S:String;
    >  Size,Size1:Integer;
    > Begin
    >   Size:=TcpClient.Receiveln; {Socket.ReceiveLength}
    >   Try
    >     SetLength(S,Size);
    >     Size1:=Tcp.ReceiveBuf(S[1],Size,0);
    >     If (Size1>0)and(Size1<>Size) Then
    >        {делай чего нибудь}
    >       Begin
    >          SetLength(S,Size1);
    >       End;
    >     Result:=S;
    >   finally
    >     SetLength(S,0);
    >   end;
    > End;


    Сделал следующее:

    function TTcpPort.ReadPort: String;
    const
     BufLen = 1024 * 10;
    var
     BytesRead: Integer;
    begin
     try
       SetLength(Result, BufLen);
       BytesRead := TcpClient.ReceiveBuf(Result[1], BufLen, 0);
       SetLength(Result, BytesRead);
     except
       on E: Exception do Exit;
     end;
    end;

    Выглядет более правильно, но проблема остается
  • FireMan_Alexey © (23.09.11 19:01) [10]
    Т.е. вы сами пишете стек TCPIP?
    Я чего-то не пойму... :\
    Если у Вас ТСР реализовано правильно, то какие это данные вчерашние?
    ТСР гарантирует доставку последовательно пакетов, иначе разрывается связь с соответствующей ошибкой(таймаута или д.р.)!
    Вопрос в чем, как ты инициализируешь передачу данных от сервера или как/откуда сервер знает, что тебе надо передавать данные?
    Может сам сервер не правильно посылает данные? А открытие и закрытие порта лишь "лечение гланд через @опу..."!
    Софт на сервере Ваш или сторонний?
    Как работает алгоритм на сервере?
  • Alik (23.09.11 20:54) [11]
    Электрон
    > FireMan_Alexey ©   (23.09.11 19:01) [10]


    Электронщик на низком уровне на программируемой логической матрице (ПЛМ) дает команды записи, чтения и т.д. сетевой карте, там же следит за ее прерываниями, т.е полностью управляет сетевухой, но видимо что то не доделывает.

    Данные формируются постоянно один раз в 4 миллисекунды по 808 байт и поступают на вход нашей железки. Задача сервера (железки с сетевой картой) их передавать.

    Сервер в принипе честно все укладывает в исходящий буфер и думает, что данные уходят и буфер очищается. В действительности же если клиент не может получать данные по каким либо причинам, сервер продолжает класть в буфер по 808 байт, а сетевая карта не протолкнула еще предыдущие данные и происходит сдвижка.

    Сейчас я стал обрабатывать событие TcpClient.OnError, ошибок нет.

    По сему я делаю предварительный вывод, что проблема возникает на сервере. Очевидно, что он должен рулить затыки связи и задержки передачи, иначе ни одна сеть не смогла бы работать.
  • FireMan_Alexey © (24.09.11 02:13) [12]
    В принципе ты прав, а не возможность доставить пакет клиенту за время Т яв-ся ошибкой... :) или при посылке данных при не пустом буфере должна возникать ситуация с отказом передачи данных, которые твой сервер должен обрабатывать!
    Вот еще вопрос, на который ты мне не ответил:
    Где реализован стек ТСР?
    Как я понял у тебя сетевая с Ethernet (Канальный уровень) протоколом, а ТСР (транспортный) который правильно формирует последовательность пакетов!
    Еще, так называемая Железка(Сервер) поддерживает подключения клиентов?
    Если да, то  сколько и как реализован алгоритм рассылки твоих сообщений?
    Я думаю у Вас на сервере просто не правильно построена логика обмена с клиентом...
    Т.е., возможно, необходимо чтобы клиент сам посылал запрос на данные с сервера... (как ответ на пинг)
  • Alik (24.09.11 16:08) [13]
    В железе реализована прямая стыковка ПЛИС и сетевого модуля. По мере формирования железякой данных они бросаются ПЛИСом в регистр TX сетевого модуля, как только накопилась пачка в 808 байт (раз в 4 миллисекунды) ПЛИС отдает модульку команду SEND и данные улетают к клиенту. Сетевой модулек имеет свои шины, буфера TX и RX, адресное пространство аж на 8 сокетов. При включении питания ПЛИС записывает в модулек таблицу настроек (IP адрес, Port, и т.д.)  
    При запросе подключения от клиента сетевой модулек формирует прерывание, на которое ПЛИС дает команду OPEN_Socket.
    При поступлении данных от клиента, модулек формирует прерывание RECIEVE, потом ПЛИС читает регистр входящего буфера и т.д....

    В принципе все работает красиво, но сервер сейчас не обрабатывает прерывания SEND_OK и SEND_FAILED, которое возникает вслед за командой отправки данных (после заданного таймаута и заданного кол-ва попыток передачи). То есть ПЛИС игнорирует эти сообщения, а сетевой модулек оказывается что нет. В исходящем регистре модулька остается след от непереданных данных.

    Здесь как бы все понятно, но не понятно мне другое - с помощью компоненты TTcpClient получается осуществить коннект только в режиме BlockMode := bmBlocking;
    С помощью компоненты ClientSocket1 можно работать в режиме ClientType := ctNonBlocking.


    Сам пока не смог понять причину такого поведения !?
  • FireMan_Alexey © (25.09.11 01:18) [14]

    > Здесь как бы все понятно, но не понятно мне другое - с помощью
    > компоненты TTcpClient получается осуществить коннект только
    > в режиме BlockMode := bmBlocking;
    > С помощью компоненты ClientSocket1 можно работать в режиме
    > ClientType := ctNonBlocking.

    Мой совет, почитай про модуль winsock/winsock2 не обязательно все переписывать на winsock АПИ, просто для понимания работы блок/не блок режимов надо ручками попробовать, а в компонентах могут быть нюансы...
    С TTcpClient даже не разбирался, когда-то перелопатил (Client/Server)Socket сделал для себя выводы, написал для себя несколько классов, для различных задач.
    Сейчас открыл реализацию TTcpClient, дело в том, что при не блок режиме при коннекте куда либо возвращается ошибка типа WSAEWOULDBLOCK:


    > With a nonblocking socket, the connection attempt cannot
    > be completed immediately. In this case, connect will return
    > SOCKET_ERROR, and WSAGetLastError will return WSAEWOULDBLOCK.
    >  In this case, the application can:
    >
    > 1. Use select to determine the completion of the connection
    > request by checking if the socket is writeable, or
    >  2. If your application is using WSAAsyncSelect to indicate
    > interest in connection events, then your application will
    > receive an FD_CONNECT notification when the connect operation
    > is complete, or
    >  3. If your application is using WSAEventSelect to indicate
    > interest in connection events, then the associated event
    > object will be signaled when the connect operation is complete.
    >


    А в реализации самого компонента это не учтено, во всяком случае в моем Делфи 6 :) И он выдает ошибку!
    А в (Client/Server)Socket коннект реализован чуть-чуть по другому :)
  • Alik (25.09.11 19:02) [15]

    > FireMan_Alexey ©   (25.09.11 01:18) [14]


    В итоге, судя по сообщению коннект происходит, но легче от этого не становится )

    WSAEISCONN
    10056
    Socket is already connected.
    A connect request was made on an already-connected socket. Some implementations also return this error if sendto is called on a connected
    SOCK_DGRAM socket (for SOCK_STREAM sockets, the to parameter in sendto is ignored) although other implementations treat this as a legal occurrence.

    Вообщем буду изучать винсокеты и попробую реализвать на инди клиенте.

    Большое спасибо FireMan_Alexey за помощь и дельные советы!!!
  • FireMan_Alexey © (26.09.11 01:06) [16]
    http://book.itep.ru - там много интересного :)
  • FireMan_Alexey © (26.09.11 01:17) [17]
    Я понял, ты сразу после коннета в не блок режиме пытаешься что-то прочитать/записать, но факт коннекта в не блок режиме является событие FD_CONNECT, которое приходит ввиде сообщения при использовании WSAAsyncSelect или Event-а при использовании WSAEventSelect. А до момента прихода события сокет не считается с кем-либо соединенным. Я для себя сделал модуль когда-то, для блок режима, который очень удобно использовать(для меня, конечно) в отдельном потоке. Если хочешь, могу поделиться... ;)
  • Alik (27.09.11 14:41) [18]

    > FireMan_Alexey ©   (26.09.11 01:17) [17]
    > Я понял, ты сразу после коннета в не блок режиме пытаешься
    > что-то прочитать/записать, но факт коннекта в не блок режиме
    > является событие FD_CONNECT, которое приходит ввиде сообщения
    > при использовании WSAAsyncSelect или Event-а при использовании
    > WSAEventSelect. А до момента прихода события сокет не считается
    > с кем-либо соединенным. Я для себя сделал модуль когда-то,
    >  для блок режима, который очень удобно использовать(для
    > меня, конечно) в отдельном потоке. Если хочешь, могу поделиться.
    > .. ;)


    Да, если можно, дай ссылочку откуда скачать или можно слить сюда: asumrl@mail.ru
  • Slym © (28.09.11 11:24) [19]
    если сообщение 808 байт зачем читать BufLen = 1024 * 30; столько?

    unit Unit1;

    interface

    uses
     Windows, Messages, SysUtils, Variants, Classes, Controls, Forms, ScktComp, StdCtrls;

    type
     TForm1 = class(TForm)
       Memo1: TMemo;
     private
       { Private declarations }
     public
       { Public declarations }
     end;

    var
     Form1: TForm1;

    implementation

    {$R *.dfm}

    type
     TClientSocketThread=class(TThread)
     private
       Buf:AnsiString;
     protected
       procedure Execute; override;
       procedure SynchroMethod;
     end;

    { TClientSocketThread }

    procedure TClientSocketThread.Execute;
     function RecvBufFully(Peer:TCustomWinSocket;var Buf; Count: Integer):boolean;
     var pBuf:PByte;
       s:integer;
     begin
       result:=false;
       pBuf:=@Buf;
       while count>0 do
       begin
         s:=Peer.ReceiveBuf(pBuf^,Count);
         if s=0 then exit;
         inc(pBuf,s);
         dec(Count,s);
       end;
       result:=true;
     end;

    const SizeOfMsg=808;
    var Sock:TClientSocket;
    begin
     SetLength(Buf,SizeOfMsg);
     Sock:=TClientSocket.Create(nil);
     try
       Sock.ClientType:=ctBlocking;
       Sock.Host:='127.0.0.1';
       Sock.Port:=11111;
       while not Terminated do
       begin
         if not Sock.Active then
           Sock.Active:=true;
         if not RecvBufFully(Sock.Socket,PChar(Buf)^,Length(Buf)) then
         begin
           Sock.Active:=false;
           continue;
         end;
         Synchronize(SynchroMethod);
       end;
     finally
       Sock.Free;
     end;
    end;

    procedure TClientSocketThread.SynchroMethod;
    begin
     Form1.memo1.text:=Buf;
    end;

    end.

  • FireMan_Alexey © (28.09.11 12:48) [20]
    Пример использования.

    Sock.Host:='127.0.0.1';
    Sock.Port:=8080;
    If Not Sock.Connect Then
     Begin
          ShowMessage(IntToStr(Sock.Error));
          Exit;
     End;
    While (not Terminated)and(Not Sock.Disconnected) do
     Begin
       Sock.GetStatus;
       If Sock.ErrorPresent Then {obrabativaem oshibku}
       If Sock.CanRead Then  Size:=Sock.ReceiveLength; //Potom chitaem -> Sock.Read
       If Sock.CanWrite Then //Pichem
     End;
    Sock.Close;
  • FireMan_Alexey © (28.09.11 12:49) [21]
    Сам модуль.

    unit NewSock;

    interface
    Uses WinSock;

    CONST
     SOCKET_ERROR=WINSOCK.SOCKET_ERROR;
       
    Type
     TNewSock=Class
     Private
       FSock    :Integer;
       FClose   :Boolean;
       FError   :Integer;
       FCanRead :Boolean;
       FCanWrite:Boolean;
       FOnError :Boolean;
       FAddr    :TSockAddr;
       FHost    :String;
       FPort    :Word;
     Public
       Procedure Close;
       Procedure GetStatus(Const TimeOut:Integer=50);
       Function  GetRemoteAddress:Integer;
       Function  GetRemoteHost:String;
       Function  Connect:Boolean;
       Function  Write(Var Buff; Size:Integer):Integer;
       Function  Read (Var Buff; Size:Integer):Integer;
       Function  ReceiveLength:Integer;
       Constructor Create(Const Sock:Integer=Invalid_Socket);
       Destructor  Destroy;Override;
       Property Host:String Read FHost Write FHost;
       Property Port:Word Read FPort Write FPort;
       Property CanRead:Boolean Read FCanRead;
       Property CanWrite:Boolean Read FCanWrite;
       Property Disconnected:Boolean Read FClose;
       Property ErrorPresent:Boolean Read FOnError;
       Property Error:Integer Read FError;
     End;

    Function InetAddr(Addr:Integer):String;

    implementation

    Constructor TNewSock.Create;
    Begin
     Inherited Create;
     FClose:=Sock=Invalid_Socket;
     FSock:=Sock;
     {IF Not FClose Then
       Begin
         FCanWrite:=True;
       End
     Else}
     FCanWrite:=False;
     FCanRead:=False;
     FOnError:=False;
     FError:=0;    
    End;

    Destructor  TNewSock.Destroy;
    Begin
    //
     Close;
     Inherited Destroy;
    End;

    Procedure TNewSock.Close;
    Begin
     If FSock<>Invalid_Socket Then
       Begin
         If CloseSocket(FSock)<>0 Then
           Begin
             FError:=WSAGetLastError;
             FOnError:=True;
           End;
         FSock:=Invalid_Socket;
         FCanRead:=False;
         FCanWrite:=False;
       End;
     FClose:=True;
    End;

    Procedure TNewSock.GetStatus;
    Var
     FD_READS,FD_WRITES,FD_ERROR:TFDSET;
     T:TTimeVal;
     R:Integer;
    Begin
    //
     If FClose Then Exit;
     T.tv_usec:=TimeOut Mod 1000;
     T.tv_sec:=TimeOut Div 1000;
     FD_ZERO(FD_READS);
     FD_SET(FSock,FD_READS);
     FD_WRITES:=FD_READS;
     FD_ERROR:=FD_READS;

     //FD_SET(FSock,FD_WRITES);

     R:=Select(1,@FD_READS,@FD_WRITES,@FD_ERROR,@T);
     If R=Socket_Error Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
         Exit;
       End
     Else FError:=0;
     If R>0 Then
       Begin
         If FD_ISSET(FSock,FD_READS) Then FCanRead:=True
           Else FCanRead:=False;
         If FD_ISSET(FSock,FD_WRITES) Then FCanWrite:=True;
         If FD_ISSET(FSock,FD_ERROR) Then
           Begin
             FOnError:=True;
             R:=SizeOf(FError);
             R:=GetSockOpt(FSock,SOL_SOCKET,SO_ERROR,@FError,R);
             {If R=SOCKET_ERROR Then
               FError:=WSAGetLastError;}
           End
         Else
           FOnError:=False;  
       End;
    End;

    Function  TNewSock.GetRemoteAddress;
    Var
     _Addr:TSockAddr;
     _Size:Integer;
    Begin
     Result:=-1;
     If Not FClose Then
       Begin
         _Size:=SizeOf(_Addr);
         FError:=GetPeerName(FSock,_Addr,_Size);
         If FError=SOCKET_ERROR Then
           Begin
             FError:=WSAGetLastError;
             FOnError:=True;
             Exit;
           End;
         Result:=_Addr.sin_addr.S_addr;  
       End;
    End;

    Function  TNewSock.GetRemoteHost;
    Var
     _Addr:Integer;
     Host:PHostEnt;
    Begin
     _Addr:=GetRemoteAddress;
     Host:=GetHostByAddr(@_Addr,SizeOf(_Addr),AF_INET);
     Result:='';
     If Host<>Nil Then Result:=Host.h_name;
    End;

    Function  TNewSock.Write;
    Var
     R:Integer;
    Begin
     R:=Send(FSock,Buff,Size,0);
     If R=SOCKET_ERROR Then
       Begin
         FError:=WSAGetLastError;
         If FError=WSAEWOULDBLOCK Then
           Begin
             FCanWrite:=False;
             R:=0;
             FError:=0;
           End
         Else FOnError:=True;
       End;
     Result:=R;  
    End;

    Function  TNewSock.Read;
    Var
     R:Integer;
    Begin
     R:=Recv(FSock,Buff,Size,0);
     If R=0 Then
       Close;
     If R=SOCKET_ERROR Then
       Begin
         FError:=WSAGetLastError;
         If FError=WSAEWOULDBLOCK Then
           Begin
             FCanRead:=False;
             FError:=0;
           End
         Else FOnError:=True;  
         R:=0;
       End;
     Result:=R;  
    End;

    Function  TNewSock.ReceiveLength;
    Begin
     If IOCTLSocket(FSock,FIONREAD,Result)=SOCKET_ERROR Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
       End;
    End;

    Function  TNewSock.Connect;
    Var
     PHost:PHostEnt;
     R:Integer;
    Begin
     Result:=False;
     If Not FClose Then Exit;
     If FHost='' Then Exit;
     If FPort=0 Then Exit;
     FSock:=Socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
     If FSock=Invalid_Socket Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
         Close;
         Exit;
       End;
     FAddr.sin_family:=AF_INET;
     FAddr.sin_port:=HToNS(FPort);
     FAddr.sin_addr.S_addr:=Inet_addr(PChar(FHost));
     If FAddr.sin_addr.S_addr=INADDR_NONE Then
       Begin
         PHost:=GetHostByName(PChar(FHost));
         If PHost=Nil Then
           Begin
             FError:=WSAGetLastError;
             FOnError:=True;
             Exit;
           End;
         Move(PHost^.h_addr_list^[0],
              FAddr.sin_addr,
              PHost^.h_length);
       End;
     R:=WinSock.Connect(FSock,FAddr,SizeOF(FAddr));
     If R=SOCKET_ERROR Then
       Begin
         FError:=WSAGetLastError;
         FOnError:=True;
         Exit;
       End;
     FClose:=False;
     Result:=True;
    End;

    Function InetAddr;
    Var
     _Addr:TSockAddr;
    Begin
     _Addr.sin_addr.S_addr:=Addr;
     Result:=inet_ntoa(_Addr.sin_addr);
    End;

    VAR
     WSAD:TWSAData;

    Initialization
    Begin
     WSAStartup($202,WSAD);
    End;

    Finalization
    Begin
     WSACleanup;
    End;

    end.
  • Alik (28.09.11 20:38) [22]
    Спасибо всем, буду пробовать!
 
Конференция "Сети" » Сдвиг данных TcpClient при задержках чтения буфера [WinXP]
Есть новые Нет новых   [134435   +19][b:0][p:0.002]