Конференция "Сети" » Проблема с компонентами TidTCPServer и TidTCPClient [D7, WinXP]
 
  • maxistent © (30.11.09 06:57) [0]
    Доброго времени суток. Подскажите как правильно использовать компоненты TidTCPServer и TidTCPClient для передачи данных? На сервере в OnExecute можно просто сделать что-то вроде AThread.Connection.ReadBuffer(Data,DataSize)? или надо как-то по-особому обрабатывать этот эвент? Собственно проблема вот в чем: пишу софт для передачи картинки с экрана в реалтайме (ну или почти в реалтайме). Получилось что-то вроде этого:

    с клиента передаю данные idTcpClient1.WriteBuffer(...);

    на сервере принимаю так:


    procedure TForm1.ss1Execute(AThread: TIdPeerThread);
    var
    buf1,buf2:pointer;
    DataSize:integer;
    st:TStream;
    jpg:TJPEGImage;
    r:TRect;
    begin
    AThread.Connection.ReadBuffer(DataSize,sizeof(DataSize));
    AThread.Connection.ReadBuffer(r,sizeof(r));
    GetMem(buf1,DataSize);
    AThread.Connection.ReadBuffer(buf1^,DataSize);
    st:=TMemoryStream.Create;
    st.Write(buf1^,DataSize);
    FreeMem(buf1,DataSize);
    st.Position:=0;
    jpg:=TJPEGImage.Create;
    jpg.LoadFromStream(st);
    st.Free;
    image1.Picture.Bitmap.Canvas.draw(r.Left,r.Top,jpg);
    jpg.Free;
    end;



    При подключении начинается передача изображения, но через несколько секунд (~5 сек) изображение перестает обновляться, а еще через несколько секунд прога "вылетает" с сообщением <Canvas does not allow drawing>. что здесь не так? не могу понять...
  • maxistent © (30.11.09 07:21) [1]
    Блин, протупил =)) решил проблему двумя дополнительными строчками кода:


    image1.Picture.Bitmap.Canvas.Lock;
    image1.Picture.Bitmap.Canvas.draw(r.Left,r.Top,jpg);
    image1.Picture.Bitmap.Canvas.UnLock;



    Вроде работает...

    Остался только вопрос по поводу правильности обработки эвента OnExecute - правильный он у меня или все-таки нет?
  • maxistent © (30.11.09 08:14) [2]
    Вообще не могу понять, как организовать двухстороннюю связь с помощью этих компонентов? На сервере нужно всем клиентам рассылать команды, а как это сделать - не знаю  :(  пробывал вызвать
    idTCPServer.bindings.items[i].Send(buf,x,0)


    но как тогда на клиентах принять эти данные - не пойму..
    пробывал на клиенте создать отдельный поток и в нем вызывать
    idTCPClient.ReadBuffer(buf,x)


    но данные туда не приходють :(
    что делать - не знаю.. нигде толком не объясняют как организовать непрерывную двухстороннюю связи сервера с несколькими клиентами.
    Помогите, хто чем может, плииииз!
  • brother © (30.11.09 08:39) [3]
    > На сервере нужно всем клиентам рассылать команды

    и

    > как организовать двухстороннюю связь

    О_о
    для клиента связь: клиент- сервер (сеанс),
    для сервера: сервер (сеансы) - много клиентов
    ?
  • CrytoGen (30.11.09 08:41) [4]
      l:=TCPTranslation.Threads.LockList;
      for i:=0 to l.Count-1 do
      begin
        TIdPeerThread(l.Items[i]).Connection.WriteBuffer(...);
    //     TIdPeerThread(l.Items[i]).Connection.Disconnect;
      end;
      TCPTranslation.Threads.UnlockList;



    а на клиенте необходимо создать поток и читать в нём.
  • brother © (30.11.09 08:43) [5]
    > На сервере нужно всем клиентам рассылать команды

    кстати, какие? тыж вроде хочешь слать картинки?
  • Сергей М. © (30.11.09 09:06) [6]

    > maxistent ©   (30.11.09 08:14) [2]



    > двухстороннюю связь


    Ерунду ты задумал.
    Либо клиент командует серверу и тот исполняет команды, либо наоборот.
  • maxistent © (30.11.09 09:32) [7]

    > CrytoGen [4]


    А что за TCPTranslation? это к чему относится?


    > brother ©   (30.11.09 08:43) [5]
    > > На сервере нужно всем клиентам рассылать команды
    > кстати, какие? тыж вроде хочешь слать картинки?


    я посылаю комманду, а за ней уже идут данные (например, изображение)


    > Сергей М. ©   (30.11.09 09:06) [6]
    > Ерунду ты задумал. Либо клиент командует серверу и тот исполняет команды, либо наоборот.


    Почему же ерунду? клиенты выполняют команды сервера, а сервер в свою очередь выполняет команды клиентов. поэтому мне и нужно реализовать двухсторонний обмен данными.
  • maxistent © (30.11.09 09:37) [8]
    ну на стороне клиентов проще - там просто в потоке я читаю вот так:
    idTCPClient.ReadBuffer(buf,x)

    и мне этого в принципе достаточно, а с отправкой вообще никаких проблем. А вот как на стороне сервера отправить всем или выборочным клиентам данные - не знаю. допустим, на сервере выполнили действие ОТПРАВИТЬ СООБЩЕНИЕ (образно), выбрали ВСЕХ или НЕСКОЛЬКИХ клиентов и он должен этим клиентам разослать это сообщение. вот как это сделать я не знаю. подскажите?
  • maxistent © (30.11.09 09:47) [9]

    > CrytoGen   (30.11.09 08:41) [4]

    сделал по твоему примеру, вроде работает. спасибо.
  • Сергей М. © (30.11.09 10:04) [10]

    > Почему же ерунду?


    Потому что.

    Читать сюда

    http://pda.delphimaster.net/?n=18&id=1251710046

    до полного просветления.

    В особенности пост [61] - это как раз твой печальный случай.

    И не надо ходить по лесу из граблей.
  • maxistent © (30.11.09 13:53) [11]
    вроде разобрался, но появилась такая проблема:
    я в потоке TIdPeerThread получаю изображение, потом пытаюсь его вывести на экран и "вылетает ошибка". я так понимаю мне нужно из этого потока как-то "временно выйти" (илипоставить на паузу), произвести отрисовку, и пото вернуться обратно? или я что-то не так понимаю? :(
  • Сергей М. © (30.11.09 14:32) [12]
    Рисование на канве VCL-контролов в доп.потоках недопустимо.
  • maxistent © (30.11.09 15:04) [13]
    и как быть?
  • Сергей М. © (30.11.09 15:10) [14]
    Известно как - извещать (любым удобным способом, синхронно или асинхронно - выбор за тобой) осн.поток о необходимости отрисовки там-то таких-то граф.данных.
  • maxistent © (30.11.09 17:38) [15]
    ну например как? допустим у меня на форме динамически создаются объекты TImage. в них нужно выводить в определенных местах небольшие графические фрагменты (что-то вроде
    Image1.picture.bitmap.canvas.draw(x,y,jpg)

    )
    но делать это надо после того, как будет получен от клиента очередной фрагмент, в событии OnExecute
  • CrytoGen (30.11.09 17:58) [16]
    например использовать Synchronize - что не очень удобно
    или сделать примерно так

    var
     GlobalImage    : TBitmap;
     newImageFlag : boolean;
    ...
    //получение данных
    GlobalImage.Assign(TCPImage);
    newImageFlag:=True;
    ...
    //таймер
    if newImageFlag then
    begin
     Image1.Canvas.Draw(0,0,GlobalImage);
     newImageFlag:=False;
    end;
    ...


  • maxistent © (30.11.09 19:43) [17]
    блииин... тогда задержки большие будут :( а другие, более "гуманные" варианты (в плане быстродействия) существуют?
  • CrytoGen (30.11.09 20:20) [18]
    20 мс большая задержка?
  • brother © (01.12.09 05:00) [19]
    > 20 мс большая задержка?

    а откуда такая точность?
  • Сергей М. © (01.12.09 08:23) [20]

    > maxistent ©   (30.11.09 17:38) [15]


    Например, так:

    var
     ptrDataToRender: Pointer;
    ..
     ptrDataToRender := SysGetMem(dwBytesReadFromClient);
     try
       CopyMemory(ptrDataReadFromClient, ptrDataToRender, dwBytesReadFromClient);
       PostMessage(MyRenderForm.Handle, WM_DATA_TO_RENDER_AVAIL, Pointer(dwBytesReadFromClient), Self);
     except
        FreeMem(ptrDataToRender);
        raise;
     end;
  • maxistent © (01.12.09 09:15) [21]
    в общем, пробывал сделать по таймеру, пипец получается. в смысле, задержка большая. пробывал в отдельном потоке - тут одно из двух, либо ошибка (типа не успевает прорисовываться, JPEG Error #43,#52 и т.д.), либо, если поставить что-то вроде sleep(1/5/10) то опять-таки тормозит :( что делать даже не знаю..
  • maxistent © (01.12.09 09:27) [22]

    > Сергей М. ©   (01.12.09 08:23) [20]

    Не пойму как это применить? можно пояснить что к чему? например что есть ptrDataReadFromClient? указатель на TJPEGImage? и что за сообщение такое WM_DATA_TO_RENDER_AVAIL? что в этом блоке кода происходит, объясни если не сложно?
  • Сергей М. © (01.12.09 09:28) [23]
    > что делать даже не знаю

    Ему про фому, он всё про ерему)

    Ты в [14],[20] вник ?
  • maxistent © (01.12.09 09:31) [24]
    в [14] да, а вот в [20] не совсем. поэтому и прошу объяснить
  • maxistent © (01.12.09 09:45) [25]
    вопросы:
    1) WM_DATA_TO_RENDER_AVAIL - это, как я понимаю, МОЕ СОБСТВЕННОЕ сообщение, так? тогда как его обработать потом?
    2) в строке
    PostMessage(MyRenderForm.Handle, WM_DATA_TO_RENDER_AVAIL, Pointer(dwBytesReadFromClient), Self);

    вообще не пойму что происходит.. отправляем окну сообщение - это понятно, а что в параметрах?..
  • Сергей М. © (01.12.09 09:46) [26]

    > что есть ptrDataReadFromClient?


    Указатель на очередной блок данных, полученный сервером от клиента в теле OnExecute.


    > что за сообщение такое WM_DATA_TO_RENDER_AVAIL?


    Константа-идентификатор предопределенного тобой пользовательского сообщения, например, WM_USER + 1000


    > что в этом блоке кода происходит


    Очередной блок данных, который твой сервер получил в OnExecute от клиента и который должен быть отрисован формой в осн.потоке, копируется в подготовленный (выделением памяти) буфер, указатель на этот буфер передается параметром wParam асинхронного сообщения окну твоей формы.

    В классе твоей формы, соотв-но, должен быть предусмотрен обработчик этого сообщения, в теле которого данные по переданному в сообщении адресу можно безопасно рисовать куда тебе вздумается

    В OnExecute:

    var
    ptrDataToRender: Pointer;
    ..
    ptrDataToRender := SysGetMem(dwBytesReadFromClient);
    try
      CopyMemory(ptrDataReadFromClient, ptrDataToRender, dwBytesReadFromClient);
      PostMessage(MyRenderForm.Handle, WM_DATA_TO_RENDER_AVAIL, WPARAM(ptrDataToRender), LPARAM(dwBytesReadFromClient));
    except
       FreeMem(ptrDataToRender);
       raise;
    end;

    В юните формы:

    const
     WM_DATA_TO_RENDER_AVAIL = WM_USER + 1000;
    ..
    TMyForm = class(TForm)
    ...
    private
    ..
     procedure WMMyMessage(var Message: TMessage); message WM_DATA_TO_RENDER_AVAIL;
    ..
    end;
    ..
    procedure TMyForm.WMMyMessage(var Message: TMessage);
    var
     ptrDataToRender: Pointer;
     dwSizeOgDataToRender: DWord;
    begin
     ptrDataToRender := Pointer(Message.wParam); // вот тебе адрес буфера
     dwSizeOgDataToRender := DWord(Message.lParam); //вот тебе размер данных в этом буфере
     try
    .. тут обрабатывай его как угодно - хоть рисуй его где хочешь, хоть еще что-то ..
     finally
       FreeMem(ptrDataToRender); // и обязательно освободи память, выделенную в OnExecute под этот буфер !!
     end;
    end;
  • maxistent © (01.12.09 11:50) [27]
    теперь понятно, спасибо. только данные приходят "левые" какие-то :( может что-то с указателями не так?

    > ptrDataToRender := Pointer(Message.wParam); // вот тебе
    > адрес буфера

    вот тут явно не тот буфер (я проверял), который был изначально в OnExecute, хотя dwSizeOfDataToRende имеет правильное значение. что-то тут не так..
  • Сергей М. © (01.12.09 11:59) [28]

    > что-то тут не так


    Угу.
    Ошибка у тебя в программе, в 17-й строке)
  • maxistent © (01.12.09 12:20) [29]
    нет, врядли =) в 17-й строке написано
    TForm1 = class(TForm)


    ну а если серьезно? почему данные, полученные обработчиком сообщения (procedure WMMyMessage(var Message: TMessage)) отличаются от тех, которые были изначально ему переданы? отличается именно буфер!
  • Сергей М. © (01.12.09 12:24) [30]
    У тебя с головой и логикой вообще все в порядке ?)

    Ты где-то там накосячил, код ты показывать не желаешь, а я должен догадаться с 3-х раз, где и что у тебя там не так ?)
  • brother © (01.12.09 12:37) [31]
    ну, партизан... код показывай уже а? что за ё маё?
  • maxistent © (01.12.09 12:39) [32]
    ну вот такой у меня код:

    procedure TForm1.ss1Execute(AThread: TIdPeerThread);
    var
    buf:pointer;
    DataSize:integer;
    st:TStream;
    r0:TRect;
    p0:TPoint;
    ptrDataToRender:Pointer;
    begin
    AThread.Connection.ReadBuffer(p0,sizeof(p0));//"габариты" всей картинки
    AThread.Connection.ReadBuffer(r0,sizeof(r0));//позиция текущего фрагмента
    AThread.Connection.ReadBuffer(DataSize,sizeof(DataSize));//размер изображения (jpg)
    GetMem(buf,DataSize);
    AThread.Connection.ReadBuffer(buf^,DataSize);

    //тут я все это складываю в стрим...
    st:=TMemoryStream.Create;
    st.Write(p0,sizeof(p0));
    st.Write(r0,sizeof(r0));
    st.Write(buf^,DataSize);
    FreeMem(buf,DataSize);
    st.Position:=0;
    //потом читаю это все в память...
    DataSize:=st.Size;
    GetMem(buf,DataSize);
    st.Read(buf^,DataSize);
    st.Free;

    //и передаю дальше...
    ptrDataToRender := SysGetMem(DataSize);
    try
      CopyMemory(buf, ptrDataToRender, DataSize);
      PostMessage(Form1.Handle, WM_DATA_TO_RENDER_AVAIL, WPARAM(ptrDataToRender), LPARAM(DataSize)); except
      FreeMem(ptrDataToRender);
      raise;
    end;
    FreeMem(buf1,DataSize);
    end;

    procedure TForm1.WMMyMessage(var Message: TMessage);
    var
    ptrDataToRender: Pointer;
    dwSizeOfDataToRender: DWord;
    st:TStream;
    jpg:TJPEGImage;
    p:TPoint;
    r:TRect;
    begin
    ptrDataToRender := Pointer(Message.wParam);
    dwSizeOfDataToRender := DWord(Message.lParam);
    try
    //дальше я уже не могу ничего сделать - буфер не тот, но в переменной
    //dwSizeOfDataToRender значение верное
    { st:=TMemoryStream.Create;
    st.Write(ptrDataToRender^,dwSizeOfDataToRender);
    st.Position:=0;
    st.Read(p,sizeof(p));
    st.Read(r,sizeof(r));
    jpg:=TJPEGImage.Create;
    jpg.LoadFromStream(st);
    st.Free;
    Image1.Picture.Bitmap.Canvas.Draw(r.Left,r.Top,jpg);
    jpg.Free;}

    finally
      FreeMem(ptrDataToRender);
    end;
    end;

  • CrytoGen (01.12.09 12:45) [33]
    молодец
     FreeMem(ptrDataToRender);
  • Сергей М. © (01.12.09 12:53) [34]

    > CopyMemory(buf, ptrDataToRender, DataSize);


    Это что за ерунда ?!
    Да еще и с учетом [33] ?
  • maxistent © (01.12.09 12:55) [35]
    что? где? в чем моя ошибка? я понимаю (во всяком случае подозреваю), что с указателями что-то напутал, но где именно - не могу понять
  • Сергей М. © (01.12.09 12:59) [36]
    > в чем моя ошибка?

    Их минимум две:

    1. Собственноручно уничтожил только что принятые данные, см. [33]
    2. Поменял местами источник и приемник в операции копирования (CopyMemory), указав к тому же в кач-ве приемника адрес уже не существующего буфера, который ты см. п.1
  • maxistent © (01.12.09 13:06) [37]
    сорри, по поводу п.2 это я протупил. да, действительно перепутал. сейчас поменял местами - то же самое. в каком месте нужно выполнять
    FreeMem(ptrDataToRender);

    ?
  • CrytoGen (01.12.09 13:20) [38]
    вообще странный код:
    ptrDataToRender := SysGetMem(DataSize);
    try
     CopyMemory(buf, ptrDataToRender, DataSize);
     PostMessage(Form1.Handle, WM_DATA_TO_RENDER_AVAIL, WPARAM(ptrDataToRender), LPARAM(DataSize)); except
     FreeMem(ptrDataToRender);
     raise;
    end;
    FreeMem(buf1,DataSize);



    что за buf1?
    почему raise?

    FreeMem правильно написан
    finally
     FreeMem(ptrDataToRender);
    end;
  • Сергей М. © (01.12.09 13:33) [39]
    > в каком месте нужно выполнять FreeMem(ptrDataToRender);?

    Минимум в 2-х:

    1. на стороне отправителя - при исключении.
    2. На стороне получателя - безусловно.
  • maxistent © (01.12.09 13:42) [40]
    в общем, я сделал так:

    procedure TForm1.ss1Execute(AThread: TIdPeerThread);
    var
    buf:pointer;
    DataSize:integer;
    st:TStream;
    r0:TRect;
    p0:TPoint;
    begin
    AThread.Connection.ReadBuffer(p0,sizeof(p0));//"габариты" всей картинки
    AThread.Connection.ReadBuffer(r0,sizeof(r0));//позиция текущего фрагмента
    AThread.Connection.ReadBuffer(DataSize,sizeof(DataSize));//размер изображения (jpg)
    GetMem(buf,DataSize);
    AThread.Connection.ReadBuffer(buf^,DataSize);

    //тут я все это складываю в стрим...
    st:=TMemoryStream.Create;
    st.Write(p0,sizeof(p0));
    st.Write(r0,sizeof(r0));
    st.Write(buf^,DataSize);
    FreeMem(buf,DataSize);
    st.Position:=0;
    //потом читаю это все в память...
    DataSize:=st.Size;
    GetMem(buf,DataSize);
    st.Read(buf^,DataSize);
    st.Free;

    //и передаю дальше...
    try
     PostMessage(Form1.Handle, WM_DATA_TO_RENDER_AVAIL, WPARAM(buf), LPARAM(DataSize)); except
     FreeMem(ptrDataToRender);
     raise;
    end;
    end;

    procedure TForm1.WMMyMessage(var Message: TMessage);
    var
    ptrDataToRender: Pointer;
    dwSizeOfDataToRender: DWord;
    st:TStream;
    jpg:TJPEGImage;
    p:TPoint;
    r:TRect;
    begin
    ptrDataToRender := Pointer(Message.wParam);
    dwSizeOfDataToRender := DWord(Message.lParam);
    try
    st:=TMemoryStream.Create;
    st.Write(ptrDataToRender^,dwSizeOfDataToRender);
    st.Position:=0;
    st.Read(p,sizeof(p));
    st.Read(r,sizeof(r));
    jpg:=TJPEGImage.Create;
    jpg.LoadFromStream(st);
    st.Free;:=TJPEGImage.Create;
    jpg.LoadFromStream(st);
    st.Free;
    Image1.Picture.Bitmap.Canvas.Draw(r.Left,r.Top,jpg);
    jpg.Free;
    finally
     FreeMem(ptrDataToRender);
    end;
    end;
    Image1.Picture.Bitmap.Canvas.Draw(r.Left,r.Top,jpg);
    jpg.Free;
    finally
     FreeMem(ptrDataToRender);
    end;
    end;



    Теперь все безупречно работает. И память распределяется и расходуется вроде правильно. Большое спасибо вам всем! теперь буду внимательней "смотреть" на указатели.. =)
  • maxistent © (01.12.09 13:46) [41]
    p.s. в [40] последние 6 строк кода ошибочно вставлены...
  • CrytoGen (01.12.09 13:49) [42]
    не походу ты всё равно не внимательно к указателям относишься:
    try
    PostMessage(Form1.Handle, WM_DATA_TO_RENDER_AVAIL, WPARAM(buf), LPARAM(DataSize)); except
    FreeMem(ptrDataToRender);
    raise;
    end;


    где ты тут работал с ptrDataToRender?
  • maxistent © (01.12.09 14:35) [43]
    ну это я уже по памяти списывал из кода и "на лету" редактировал, чтоб вам было проще читать... там не "ptrDataToRender", а "buf"  =)
  • Slym © (02.12.09 11:33) [44]
 
Конференция "Сети" » Проблема с компонентами TidTCPServer и TidTCPClient [D7, WinXP]
Есть новые Нет новых   [134438   +31][b:0][p:0.003]