Конференция "Начинающим" » Не могу нормально принять данные по UDP [D7, WinXP]
 
  • DSIoffe © (04.07.11 16:32) [0]
    Здравствуйте все!
    Я соорудил некое устройство, которое обменивается данными с компьютером по UDP.
    На компьютере работает моя самодельная программа, написанная в Delphi 7. Она работает с UDP через WinAPI так, как написано в книге А.Б. Григорьева "О чём не пишут в книгах по Delphi", стр. 204 и далее. Или я думаю, что она так работает.
    Пока надо было гонять туда-сюда по два-три пакета, всё было хорошо. Сейчас я отправляю из устройства подряд 256 пакетов размером поменьше MTU с интервалом 4 мс. Все эти пакеты появляются в компьютере, их видно в сниффере WireShark, и содержимое у них правильное.
    Но моя программа, приняв 5 первых пакетов, затем примерно полсекунды не видит приходящих пакетов. А потом она нормально принимает все оставшиеся, больше сотни. Содержимое каждого пакета - байты с его номером, так что легко понять, чего не хватает, а что пришло.
    То есть, как я понимаю, системе хватает быстродействия, чтобы принимать пакеты каждые 4 мс. Причём даже по 2 штуки за 4 мс: я пробовал слать пакеты больше MTU, они разбивались на пары внутри сетевого интерфейса моего устройства и нормально доходили, и тогда до пропадания программа нормально принимала 9 пакетов. Как бороться с пропаданием пакетов после начала передачи?
    Прошу не бить сильно, в программировании я любитель. Что мне ещё надо написать, чтобы на мой вопрос надо было ответить?
  • DVM © (04.07.11 19:27) [1]

    > Что мне ещё надо написать, чтобы на мой вопрос надо было
    > ответить?

    код приема наверное показать
  • DSIoffe © (05.07.11 02:45) [2]
    Объявлены переменные:
     Buffer: array[0..65506] of Byte;
     RecvAddr: TSockAddr;
     RecvLen, AddrLen: Integer;
     ErrorCount: integer;
     // Множество сокетов для функции select.
     // Будет содержать только один сокет FSocket.
     SocketSet: TFDSet;
     // Таймаут для функции select
     Timeout: TTimeVal;



    При запуске программы (onCreate формы):
    var
     WSData: TWSAData;
     err: integer;
     SocketVal,SocketLen: integer;


    .....
    //Инициализация библиотеки сокетов (стр. 204 у Григорьева):
     err := WSAStartup($0101,WSData);
     if err = 0 then MemoLog.Lines.Add('Инициализация библиотеки сокетов: OK')
       else
         begin
           err := WSAGetLastError;
           MemoLog.Lines.Add('Ошибка инициализации библиотеки сокетов с кодом'+IntToStr(err))
         end;
    //Открытие сокета (стр. 205 у Григорьева):
     MySocket := socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
     if MySocket = INVALID_SOCKET then MemoLog.Lines.Add(GetErrorString)
       else
         begin
           MemoLog.Lines.Add('Открытие сокета: OK');
           SocketLen:=SizeOf(Integer);
           GetSockOpt(MySocket,SOL_Socket,SO_SndBuf,@SocketVal,SocketLen);
           MemoLog.Lines.Add('   Размер передающего буфера '+IntToStr(SocketVal)+' байтов');
           GetSockOpt(MySocket,SOL_Socket,SO_RcvBuf,@SocketVal,SocketLen);
           MemoLog.Lines.Add('   Размер приёмного буфера '+IntToStr(SocketVal)+' байтов')
         end;
    //Привязка сокета к адресу (стр. 206 у Григорьева):
     SockAddr.sin_family := PF_INET;
     IPstring := editIP.text;
     SockAddr.sin_addr.S_addr := inet_addr(PAnsiChar(IPstring)); {нужен реальный адрес сетевой карты, иначе будет ошибка}
     // Для совместимости со старыми версиями Delphi приводим
     // константу INADDR_NONE к типу u_long
     if SockAddr.sin_addr.S_addr = u_long(INADDR_NONE) then
     begin
       MessageDlg('Неправильно задан IP адрес сокета программы', mtError, [mbOK], 0);
       Exit;
     end;
     SockAddr.sin_port := htons(3320);
     FillChar(SockAddr.sin_zero, SizeOf(SockAddr.sin_zero), 0);
     err := bind(MySocket, SockAddr, SizeOf(SockAddr));
     if err = SOCKET_ERROR then MemoLog.Lines.Add('Ошибка привязки сокета программы: '+GetErrorString)
     else
       begin
         MemoLog.Lines.Add('Сокет программы привязан. Адрес '+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b1))+'.'+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b2))+'.'+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b3))+'.'+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b4))+', порт '+IntToStr(ntohs(SockAddr.sin_port)))
       end;
    //Заполнение структуры, описывающей сокет нашего устройства
     DeviceSock.sin_family := PF_INET;
     DeviceSock.sin_addr.S_addr := inet_addr('192.168.1.150');
     if DeviceSock.sin_addr.S_addr = u_long(INADDR_NONE) then
     begin
       MessageDlg('Неправильно задан IP адрес сокета устройства', mtError, [mbOK], 0);
       Exit;
     end;
     DeviceSock.sin_port := htons(7);
     FillChar(DeviceSock.sin_zero, SizeOf(DeviceSock.sin_zero), 0);
     MemoLog.Lines.Add('Сокет устройства описан. Адрес '+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b1))+'.'+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b2))+'.'+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b3))+'.'+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b4))+', порт '+IntToStr(ntohs(DeviceSock.sin_port)));
     err := DataCalculation;
     if err <> 0 then
       memoLog.Lines.Add('Задан неправильный размер блока данных, код '+IntToStr(err));
     // Инициализируем множество сокетов,
     // т.е. очищаем его от случайного мусора
     FD_ZERO(SocketSet);
     // Добавляем в это множество сокет FSocket
     FD_SET(MySocket, SocketSet);
     // Устанавливаем таймаут равным нулю, чтобы
     // функция select ничего не ждала, а возвращала
     // готовность сокетов на момент вызова.
     Timeout.tv_sec := 0;
     Timeout.tv_usec := 0;


    И функция чтения сокета:
    function TForm1.DataFromDevice: boolean;
    begin
     // Проверяем готовность сокета для чтения
     if select(0, @SocketSet, nil, nil, @Timeout) = SOCKET_ERROR then
     begin
       MemoLog.Lines.Add('Ошибка при проверке готовности сокета: ' + GetErrorString);
       Result := false;
       Exit;
     end;
     // Проверяем, оставила ли функция select сокет в множестве.
     // Если оставила, значит, во входном буфере сокета есть данные.
     if FD_ISSET(MySocket, SocketSet) then
       begin
         AddrLen := SizeOf(RecvAddr);
         // Получаем дейтаграмму
         RecvLen := recvfrom(MySocket, Buffer, SizeOf(Buffer), 0, RecvAddr, AddrLen);
         // Так как UDP не поддерживает соединение, ошибку при вызове recvfrom мы
         // можем получить, только если случилось что-то совсем экстраординарное.
         if RecvLen < 0 then
         begin
           MemoLog.Lines.Add('Ошибка при получении сообщения: ' + GetErrorString);
           Result := false;
           Exit
         end;
         Result := true
       end  //копирования данных в приёмный буфер
     else
       begin
         Result := false
       end

  • MBo © (05.07.11 06:55) [3]
    как и когда вызывается функция DataFromDevice?
  • DSIoffe © (05.07.11 14:23) [4]
    Вызывается так:
    if DataFromDevice then - приняли данные
    else - повторяем вызов DataFromDevice, и так до истечения тайм-аута.
    По замыслу, так должны выгребаться все пришедшие пакеты.
    Вызывается она после того, как из компьютера уходят в устройство три пакета с управляющими данными, в которых, в частности, содержится команда для устройства передать данные в компьютер.
  • sniknik © (05.07.11 17:48) [5]
    > Вызывается так:
    а правиться значится так.
    садишься значит, читаешь код и если видишь написана ошибка заменяешь ее на правильный... ну т.е.
    if DataFromDevice then - это ошибка!
    то пишешь
    if DataFromDevice_2 then - это правильно.
    после исправлений "билдиш" проект, это частная команда компилируящая все файлы, был ли для нее dcu или нет.
  • MBo © (06.07.11 07:49) [6]
    Прием данных ведется в основном потоке?
    Чем занимается программа, приняв первую порцию - может, какими-то относительно долгими действиями?
  • DSIoffe © (06.07.11 08:49) [7]
    Да, в основном потоке. Программа пытается непрерывно выгребать приходящие пакеты, больше ничем не занимается.
  • MBo © (06.07.11 09:12) [8]
    т.е. организация такая - запустили, программа крутит цикл приема, в котором принимает пакет и записывает его номер, пока не придет весь набор пакетов?
    Ни перерисовкой формы, ни выводом контрольных данных в это время не занимается?
  • sniknik © (06.07.11 09:24) [9]
    допрос партизана продолжается второй день... но не выдал он тайны партизанской.
  • DSIoffe © (06.07.11 12:17) [10]
    to MBo:
    Да, всё почти так. Только после принятия пакета программа выводит в TMemo строчку с отчётом о приёме и номером вида: "Пакет № содержит N".
  • MBo © (06.07.11 12:30) [11]
    Попробуй не выводить сразу, а накопить всё в TStringList, например, и вывести по окончанию серии
  • DSIoffe © (11.07.11 11:00) [12]
    А не хватало всего двух строчек:
     RcvBufLen:= 82000000;  //больше не должно придти
     setsockopt(MySocket,SOL_SOCKET,SO_RCVBUF,@RcvBufLen,4);
    Профессиональный программист посидел пару часов с моим кодом. Эти строчки задают размер буфера, в который складываются поступающие данные.
    Итого, вот весь код для приёма данных по UDP с использованием только WinAPI. А то я уже с перепугу чуть не начал изучать WinPCAP.

    //Объявлено глобально:
    var
     MySocket: TSocket;
     SockAddr, DeviceSock: TSockAddr;
     // Буфер для получения сообщения. Размер равен максимальному размеру UDP-дейтаграммы:
     Buffer: array[0..65506] of Byte;
     // Адрес, с которого пришло сообщение
     RecvAddr: TSockAddr;
     RecvLen, AddrLen: Integer;

    //Выполняется один раз при создании главной формы приложения
    //Здесь и далее работа программы протоколируется в MemoLog типа TMemo.
    //Инициализация библиотеки сокетов (стр. 204 у Григорьева):
     err := WSAStartup($0101,WSData);
     if err = 0 then MemoLog.Lines.Add('Инициализация библиотеки сокетов: OK')
       else
         begin
           err := WSAGetLastError;
           MemoLog.Lines.Add('Ошибка инициализации библиотеки сокетов с кодом'+IntToStr(err))
         end;
    //Открытие сокета (стр. 205 у Григорьева):
     MySocket := socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
     if MySocket = INVALID_SOCKET then MemoLog.Lines.Add(GetErrorString)
       else
         begin
           MemoLog.Lines.Add('Открытие сокета: OK');
           SocketLen:=SizeOf(Integer);
           GetSockOpt(MySocket,SOL_Socket,SO_SndBuf,@SocketVal,SocketLen);
           MemoLog.Lines.Add('   Размер передающего буфера '+IntToStr(SocketVal)+' байтов');
           GetSockOpt(MySocket,SOL_Socket,SO_RcvBuf,@SocketVal,SocketLen);
           MemoLog.Lines.Add('   Размер приёмного буфера '+IntToStr(SocketVal)+' байтов')
         end;
    //Привязка сокета к адресу (стр. 206 у Григорьева):
     SockAddr.sin_family := PF_INET;
     IPstring := editIP.text;
     SockAddr.sin_addr.S_addr := inet_addr(PAnsiChar(IPstring)); {нужен реальный адрес сетевой карты, иначе будет ошибка}

    //Строчки от Валеры:
     RcvBufLen:= 82000000;  //больше не должно придти
     setsockopt(MySocket,SOL_SOCKET,SO_RCVBUF,@RcvBufLen,4);
     
     // Для совместимости со старыми версиями Delphi приводим
     // константу INADDR_NONE к типу u_long
     if SockAddr.sin_addr.S_addr = u_long(INADDR_NONE) then
     begin
       MessageDlg('Неправильно задан IP адрес сокета программы', mtError, [mbOK], 0);
       Exit;
     end;
     SockAddr.sin_port := htons(3320);
     FillChar(SockAddr.sin_zero, SizeOf(SockAddr.sin_zero), 0);
     err := bind(MySocket, SockAddr, SizeOf(SockAddr));
     if err = SOCKET_ERROR then MemoLog.Lines.Add('Ошибка привязки сокета программы: '+GetErrorString)
     else
       begin
         MemoLog.Lines.Add('Сокет программы привязан. Адрес '+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b1))+'.'+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b2))+'.'+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b3))+'.'+
           IntToStr(integer(SockAddr.sin_addr.S_un_b.s_b4))+', порт '+IntToStr(ntohs(SockAddr.sin_port)))
       end;
    //Заполнение структуры, описывающей сокет нашего устройства
     DeviceSock.sin_family := PF_INET;
     DeviceSock.sin_addr.S_addr := inet_addr('192.168.1.150');
     if DeviceSock.sin_addr.S_addr = u_long(INADDR_NONE) then
     begin
       MessageDlg('Неправильно задан IP адрес сокета устройства', mtError, [mbOK], 0);
       Exit;
     end;
     DeviceSock.sin_port := htons(7);
     FillChar(DeviceSock.sin_zero, SizeOf(DeviceSock.sin_zero), 0);
     MemoLog.Lines.Add('Сокет устройства описан. Адрес '+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b1))+'.'+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b2))+'.'+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b3))+'.'+
       IntToStr(integer(DeviceSock.sin_addr.S_un_b.s_b4))+', порт '+IntToStr(ntohs(DeviceSock.sin_port)));

    //И функция приёма пакета:
    function TForm1.DataFromDevice: boolean;
     var
     // Множество сокетов для функции select.
     // Будет содержать только один сокет FSocket.
     SocketSet: TFDSet;
     // Таймаут для функции select
     Timeout: TTimeVal;
    begin
     // Инициализируем множество сокетов,
     // т.е. очищаем его от случайного мусора
     FD_ZERO(SocketSet);
     // Добавляем в это множество сокет FSocket
     FD_SET(MySocket, SocketSet);
     // Устанавливаем таймаут равным нулю, чтобы
     // функция select ничего не ждала, а возвращала
     // готовность сокетов на момент вызова.
     Timeout.tv_sec := 0;
     Timeout.tv_usec := 0;
     // Проверяем готовность сокета для чтения
     if select(0, @SocketSet, nil, nil, @Timeout) = SOCKET_ERROR then
     begin
       MemoLog.Lines.Add('Ошибка при проверке готовности сокета: ' + GetErrorString);
       Result := false;
       Exit;
     end;
     // Проверяем, оставила ли функция select сокет в множестве.
     // Если оставила, значит, во входном буфере сокета есть данные.
     if FD_ISSET(MySocket, SocketSet) then
       begin
         AddrLen := SizeOf(RecvAddr);
         // Получаем дейтаграмму
         RecvLen := recvfrom(MySocket, Buffer, SizeOf(Buffer), 0, RecvAddr, AddrLen);
         // Так как UDP не поддерживает соединение, ошибку при вызове recvfrom мы
         // можем получить, только если случилось что-то совсем экстраординарное.
         if RecvLen < 0 then
         begin
           MemoLog.Lines.Add('Ошибка при получении сообщения: ' + GetErrorString);
           Result := false;
           Exit
         end;
         Result := true
       end  //копирования данных в приёмный буфер
     else
       begin
         Result := false
       end
    end;  //TForm1.DataFromDevice

  • RWolf © (11.07.11 11:16) [13]

    >  [12]

    а как связано отсутствие этих строчек с пропаданием пакетов?
  • Slym © (11.07.11 14:33) [14]
    как пользуешься DataFromDevice?
    кто ее выполняет? мож она на кнопку прицеплена :)
 
Конференция "Начинающим" » Не могу нормально принять данные по UDP [D7, WinXP]
Есть новые Нет новых   [134431   +15][b:0][p:0.01]