Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2011.10.30;
Скачать: [xml.tar.bz2];

Вниз

Не могу нормально принять данные по UDP   Найти похожие ветки 

 
DSIoffe ©   (2011-07-04 16:32) [0]

Здравствуйте все!
Я соорудил некое устройство, которое обменивается данными с компьютером по UDP.
На компьютере работает моя самодельная программа, написанная в Delphi 7. Она работает с UDP через WinAPI так, как написано в книге А.Б. Григорьева "О чём не пишут в книгах по Delphi", стр. 204 и далее. Или я думаю, что она так работает.
Пока надо было гонять туда-сюда по два-три пакета, всё было хорошо. Сейчас я отправляю из устройства подряд 256 пакетов размером поменьше MTU с интервалом 4 мс. Все эти пакеты появляются в компьютере, их видно в сниффере WireShark, и содержимое у них правильное.
Но моя программа, приняв 5 первых пакетов, затем примерно полсекунды не видит приходящих пакетов. А потом она нормально принимает все оставшиеся, больше сотни. Содержимое каждого пакета - байты с его номером, так что легко понять, чего не хватает, а что пришло.
То есть, как я понимаю, системе хватает быстродействия, чтобы принимать пакеты каждые 4 мс. Причём даже по 2 штуки за 4 мс: я пробовал слать пакеты больше MTU, они разбивались на пары внутри сетевого интерфейса моего устройства и нормально доходили, и тогда до пропадания программа нормально принимала 9 пакетов. Как бороться с пропаданием пакетов после начала передачи?
Прошу не бить сильно, в программировании я любитель. Что мне ещё надо написать, чтобы на мой вопрос надо было ответить?


 
DVM ©   (2011-07-04 19:27) [1]


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

код приема наверное показать


 
DSIoffe ©   (2011-07-05 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 ©   (2011-07-05 06:55) [3]

как и когда вызывается функция DataFromDevice?


 
DSIoffe ©   (2011-07-05 14:23) [4]

Вызывается так:
if DataFromDevice then - приняли данные
else - повторяем вызов DataFromDevice, и так до истечения тайм-аута.
По замыслу, так должны выгребаться все пришедшие пакеты.
Вызывается она после того, как из компьютера уходят в устройство три пакета с управляющими данными, в которых, в частности, содержится команда для устройства передать данные в компьютер.


 
sniknik ©   (2011-07-05 17:48) [5]

> Вызывается так:
а правиться значится так.
садишься значит, читаешь код и если видишь написана ошибка заменяешь ее на правильный... ну т.е.
if DataFromDevice then - это ошибка!
то пишешь
if DataFromDevice_2 then - это правильно.
после исправлений "билдиш" проект, это частная команда компилируящая все файлы, был ли для нее dcu или нет.


 
MBo ©   (2011-07-06 07:49) [6]

Прием данных ведется в основном потоке?
Чем занимается программа, приняв первую порцию - может, какими-то относительно долгими действиями?


 
DSIoffe ©   (2011-07-06 08:49) [7]

Да, в основном потоке. Программа пытается непрерывно выгребать приходящие пакеты, больше ничем не занимается.


 
MBo ©   (2011-07-06 09:12) [8]

т.е. организация такая - запустили, программа крутит цикл приема, в котором принимает пакет и записывает его номер, пока не придет весь набор пакетов?
Ни перерисовкой формы, ни выводом контрольных данных в это время не занимается?


 
sniknik ©   (2011-07-06 09:24) [9]

допрос партизана продолжается второй день... но не выдал он тайны партизанской.


 
DSIoffe ©   (2011-07-06 12:17) [10]

to MBo:
Да, всё почти так. Только после принятия пакета программа выводит в TMemo строчку с отчётом о приёме и номером вида: "Пакет № содержит N".


 
MBo ©   (2011-07-06 12:30) [11]

Попробуй не выводить сразу, а накопить всё в TStringList, например, и вывести по окончанию серии


 
DSIoffe ©   (2011-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 ©   (2011-07-11 11:16) [13]


>  [12]

а как связано отсутствие этих строчек с пропаданием пакетов?


 
Slym ©   (2011-07-11 14:33) [14]

как пользуешься DataFromDevice?
кто ее выполняет? мож она на кнопку прицеплена :)



Страницы: 1 вся ветка

Форум: "Начинающим";
Текущий архив: 2011.10.30;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.52 MB
Время: 0.005 c
15-1309465804
Юрий
2011-07-01 00:30
2011.10.30
С днем рождения ! 1 июля 2011 пятница


15-1309840625
dxc
2011-07-05 08:37
2011.10.30
биллиардный шар


15-1309633372
eXAAAXe
2011-07-02 23:02
2011.10.30
Инвайт для ВКонтакта


2-1310420987
set666
2011-07-12 01:49
2011.10.30
Компонент tEdit


15-1309527475
Chatnick
2011-07-01 17:37
2011.10.30
Ассоциация ICO-файла с программой.





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский