Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Не могу нормально принять данные по UDP
Форум разработчиков электроники ELECTRONIX.ru > Интерфейсы > Форумы по интерфейсам > Fast Ethernet/Gigabit Ethernet/FibreChannel
DSIoffe
Здравствуйте все!
Я соорудил некое устройство на W5100 в связке с ПЛИС, которое обменивается с компьютером по UDP.
На компьютере работает моя самодельная программа, написанная в Delphi 7. Она работает с UDP через WinAPI так, как написано в книге А.Б. Григорьева "О чём не пишут в книгах по Delphi", стр. 204 и далее. Или я думаю, что она так работает.
Пока надо было гонять туда-сюда по два-три пакета, всё было хорошо. Сейчас я отправляю через W5100 подряд 256 пакетов размером поменьше MTU с интервалом 4 мс. Все эти пакеты появляются в компьютере, их видно в сниффере WireShark, и содержимое у них правильное.
Но моя программа, приняв 5 первых пакетов, затем примерно полсекунды не видит приходящих пакетов. А потом она нормально принимает все оставшиеся, больше сотни. Содержимое каждого пакета - байты с его номером, так что легко понять, чего не хватает, а что пришло.
То есть, как я понимаю, системе хватает быстродействия, чтобы принимать пакеты каждые 4 мс. Причём даже по 2 штуки за 4 мс: я пробовал слать пакеты больше MTU, они разбивались на пары внутри W5100 и нормально доходили, и тогда до пропадания программа нормально принимала 9 пакетов. Как бороться с пропаданием пакетов после начала передачи?
Очень прошу объяснить как можно проще, потому что я не разбираюсь в сетевых вопросах.
zltigo
QUOTE (DSIoffe @ Jul 3 2011, 21:29) *
На компьютере работает моя самодельная программа, написанная в Delphi 7. Она работает с UDP через WinAPI так, как написано в книге А.Б. Григорьева "О чём не пишут в книгах по Delphi", стр. 204 и далее.

Нет не малейшего желания читать, то, что мог кто-то на Дельфи написать, но сокетами у Win проблем,естественно нет. Просто нужно воспользоваться возможностями операционной системы, например, тем-же select() для отслеживания состояний сокета.

DSIoffe
Цитата("zltigo")
но сокетами у Win проблем,естественно нет

Да, конечно, это у меня проблемы, а не у Windows, я так и написал. Select я использую, если бы сокет был не готов, я бы получил заранее заготовленное сообщение, а его ни разу не было.
zltigo
Что такое "не готов", не совсем понимаю sad.gif. Данные из сокета по какому событию читать начинаете?
DSIoffe
Я не по событию. Я проверяю готовность сокета:
if select(0, @SocketSet, nil, nil, @Timeout) = SOCKET_ERROR
а потом проверяю, оставила ли функция select мой сокет в множестве сокетов:
if FD_ISSET(MySocket, SocketSet)
Если оставила, значит, в сокете есть данные, если нет, то проверяю ещё раз, до победного конца или до выхода по тайм-ауту.
zltigo
Когда-то много лет назад писал писал для одного из заказчиков учебный пример на чистых Win/lin сокетах. Попробую завтра найти и выложить.
vat
Цитата(DSIoffe @ Jul 3 2011, 23:29) *
Все эти пакеты появляются в компьютере, их видно в сниффере WireShark, и содержимое у них правильное.
Но моя программа, приняв 5 первых пакетов, затем примерно полсекунды не видит приходящих пакетов.

Пакеты UDP у вас корректные? Контрольные суммы правильные? Длины TCP и UDP частей соответствуют реальным? MAC, IP, Port верно указаны?Если что не так, то сокет прибъет некорректный пакет, а снифер съест любой - даже самый кривой...
И еще вероятность небольшая, но может мешает фаервол. Снифер ловит до фаервола, поэтому там все пакеты. Конечно обычно фаер должен либо блокировать все, либо все пропускать, но ... может какая-то странная защита от flood или быстродействие все-таки. Поправьте скорострельность в настройках - пусть пакеты летят раз в секунду. Что-то изменится?
DSIoffe
Пакеты правильные, потому что их формирую не я, а умная штуковина W5100. Они же, в основном, приходили нормально.
А не хватало всего двух строчек:
CODE
RcvBufLen:= 82000000; //больше не должно придти
setsockopt(MySocket,SOL_SOCKET,SO_RCVBUF,@RcvBufLen,4);

Профессиональный программист посидел пару часов с моим кодом. Эти строчки задают размер буфера, в который складываются поступающие данные.
Итого, вот весь код для приёма данных по UDP с использованием только WinAPI. А то я уже с перепугу чуть не начал изучать WinPCAP.
CODE

//Объявлено глобально:
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
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.