Форум: "Начинающим";
Текущий архив: 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.004 c