Текущий архив: 2002.09.16;
Скачать: CL | DM;
Вниз
Ошибка 10022 при попытке послать данные Найти похожие ветки
← →
Sapersky_ (2002-07-02 21:47) [0]Привет всем!
Ситуация следующая: при подсоединении клиент посылает серверу информацию о том, что он клиент данного приложения и свою версию; сервер, если его это устраивает, посылает информацию о себе... и вот здесь лезет глюк (см. заголовок). Сообщение посылается следующим образом: перебираются ActiveConnections, находится нужный клиент (я их различаю по SocketHandle - полагаю, он у всех должен быть разный) и ему посылается информация. Мне знакомый посоветовал посылать ответ тому сокету, который был в параметрах OnRead - как ни странно, помогает (по идее это тот же самый сокет, что находится через ActiveConnections - SocketHandle у них одинаковые!) -
но, во-первых, это неудобно, во-вторых, иногда нужно слать информацию не в ответ на запрос клиента, а просто так (оповещение всем клиентам о присоединении нового, например) - в этом случае без ActiveConnections не обойтись...
Самое интересное, что на этой же библиотеке сделан чат, и он работает - хотя там немного другая система, не "запрос - ответ", а "пользователь ввёл данные - отправили, данные пришли - отобразили", т.е. получение и отправка никак не связаны.
Вообще, что такое 10022? В Help"е написано:
Socket not bound, invalid address or listen is not invoked prior to accept.
Тут скорее первое - но что это (not bound) значит?
← →
Digitman © (2002-07-03 08:03) [1]Приведи фрагмент кода.
← →
Sapersky_ (2002-07-03 10:53) [2]Функция отправки сообщения:
function TClientServer.SendDataTo(ClientID : Integer; AllExceptThis : Boolean; data : PByte; bCount : DWord): Boolean;
Var n, cID : Integer;
begin
Result:=True;
If bCount=0 then Exit;
If IsServer then With Server do
For n:=0 to ActiveConnections-1 do begin
cID:=Connections[n].SocketHandle;
If ((not AllExceptThis) and (cID=ClientID)) or
((AllExceptThis) and (cID<>ClientID)) then
If Connections[n].SendBuf(data^,bCount)<=0 // ошибка!
then Result:=False;
end;
end;
ClientID - это SocketHandle клиента, на запрос которого мы отвечаем; при получении сообщения (OnRead сервера) к нему присоединяется этот SocketHandle и оно добавляется в буфер, из которого его затем вынимает основная программа (кода здесь много, так что приводить не буду) и посылает ответ клиенту.
А вот такое работает:
function TClientServer.SendDataToSocket(Socket: TCustomWinSocket;
data : PByte; bCount : DWord): Boolean;
begin
Result:=True;
If bCount=0 then Exit;
If Socket.SendBuf(data^,bCount)<=0 then Result:=False;
end;
В данном случае в OnRead приходится запоминать весь Socket.
Но иногда, как я уже писал, требуется послать сообщение 1-м методом; а хранить для этой цели ещё один массив сокетов (в дополнение к стандартному Connections) не хочется.
← →
Digitman © (2002-07-03 12:41) [3]Приведи обработчик (или хотя бы - фрагмент) OnClientRead(), где ты читаешь у некоего объекта и запоминаешь (для дальнейшей идентификации) св-во SocketHandle.
Склонен предпологать, что читаешь ты это св-во не у наследника TServerClientWinSocket, а у наследника TServerWinSocket. Совершенно разные вещи !
Кр.того, концептуально неверно (или как минимум - некорректно) фиксировать SocketHandle в обработчике OnClientRead() - делать это нужно в OnClientConnect(). И фиксировать-таки гораздо корректнее не SocketHandle, а ссылку на экз-р TServerClientWinSocket (какая разница, что фиксировть в списке целочисленных идентификаторов - целое число, содержащее SocketHandle или то же целое число, содержащее ссылку на экз-р некоего TCustomWinSocket). При фиксации ссылок и цикл поиска в списке Connections[] упрощается :
function TClientServer.SendDataTo(Client : TCustomWinSocket; AllExceptThis : Boolean; data : PByte; bCount : DWord): Boolean;
Var n: Integer;
begin
Result:=True;
If bCount=0 then Exit;
If IsServer then With Server do
For n:=0 to ActiveConnections-1 do begin
If ((not AllExceptThis) and (Client = Connections[n])) or
((AllExceptThis) and (Client <> Connections[n])) then
If Connections[n].SendBuf(data^,bCount)<=0 // вот и никаких ошибок нет !
then Result:=False;
end;
← →
Sapersky_ (2002-07-04 01:21) [4]Переделал на указатели... результат тот же (глюк).
Вот новая версия обработчика приёма... TClientServer - это мой класс, содержащий объекты TServerWinSocket и TClientWinSocket.
Обработчик OnRead сервера:
procedure TClientServer.OnClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin
ReceiveData(Socket);
end;
procedure TClientServer.ReceiveData(Socket: TCustomWinSocket);
// используется и клиентом тоже
Var bc : Integer;
pb,pb1 : PByte;
header : TMsgHeaderLong;
begin
bc:=Socket.ReceiveLength;
If bc>0 then begin
GetMem(pb,bc);
Socket.ReceiveBuf(pb^,bc);
If bc>0 then begin
header.MsgHead:=PMsgHeader(pb)^;
header.SenderID:=Integer(Socket);
// ну хорошо, используем указатель на объект
pb1:=pb; Inc(pb1,SizeOf(TMsgHeader));
AddLocalMsg(header,pb1); // добавляем в буфер
end;
FreeMem(pb);
end;
end;
А фиксируется (в смысле, запоминается) Socket (ранее SocketHandle) действительно в OnClientConnect:
procedure TClientServer.OnClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
AddLocalMsgSimple(nmConnect,Integer(Socket));
//добавляем в буфер сообщение о присоединении клиента
end;
Но поскольку работаем теперь с указателями (объектами), можно воспользоваться моей функцией посылки N2 (SendDataToSocket). Она работает, так что в какой-то степени проблема решена... хотя всё равно интересно, почему глючат ActiveConnections и что такое socket not bound (вспоминается HW:Cataclysm: "We will not been bound!!!" :)).
← →
Digitman © (2002-07-04 08:30) [5]1.
procedure TClientServer.ReceiveData(Socket: TCustomWinSocket);
// используется и клиентом тоже
Var bc : Integer;
pb,pb1 : PByte;
header : TMsgHeaderLong;
begin
bc:=Socket.ReceiveLength; // это - ожидаемый размер принятых данных
If bc>0 then begin
GetMem(pb,bc);
Socket.ReceiveBuf(pb^,bc); // а сколько ты реально принял-то ??
If bc>0 then begin
header.MsgHead:=PMsgHeader(pb)^;
header.SenderID:=Integer(Socket);
// только вот какой смысл приводить TCustomWinSocket к Integer ?
// сделай тип поля SenderId : TCustomWinSocket;
pb1:=pb;
// а вот здесь - неверно :
// ты смещаешь указатель на размер заголовка,
// а с чего ты решил , что размер реально принятых данных
// больше чем SizeOf(TMsgHeader) ??
Inc(pb1,SizeOf(TMsgHeader));
//????????????????????????????????????????????????
AddLocalMsg(header,pb1); // добавляем в буфер
// что добавляешь ?? Указатель ?? На что и какого размера ?
// а что потом с ним делать-то будешь, с этим указателем ?
// после завершения этого обработчика ты теряешь
// информацию о размере принятых данных !
end;
// а здесь вообще нонсенс !
// блок памяти, выделенный под буфер приема ты освобождаешь !
// вместе с принятыми данными !
FreeMem(pb);
// как же ты собираешься работать с принятым заголовком и следующими за ним данными, если их уже НЕТ в этой точке ???
end;
end;
2. Почему при SendDataToSocket() "Она работает ... в какой-то степени" ? Поясни.
← →
Sapersky_ (2002-07-04 13:57) [6]Насчёт того, что получить можно не всё сообщение, а его кусок или несколько сообщений, склееных вместе - это да, проблема, и я пока не знаю, как её решать. Ну разделить несколько ещё можно (в заголовке идёт информация о размере данных), а вот собирать сообщение по кусочкам - муторное дело, ведь последующие части придут без заголовков, и как определить, что есть что?
Так что пока считаем - сколько отправили, столько и приняли. И вроде работает, проблем с приёмом не наблюдалось (видимо, оттого, что общаюсь с LocalHost). Информацию от клиента сервер получает нормально, я проверял.
Размер заголовка - 4 байта - может ли в принципе придти меньше?
AddLocalMsg копирует данные в буфер (размер в заголовке), так что ничего страшного в освобождении pb нет.
В общем, добрая половина замечаний - оттого, что я выложил не весь исходник. Ну извини, большой он. Разве что на е-мэйл...
SendDataToSocket() работает в общем нормально, есть некоторые глюки при присоединении более чем одного клиента, но это, я полагаю, вина основной программы (надо копаться...).
← →
Digitman © (2002-07-04 16:41) [7]>>"сколько отправили, столько и приняли"
на это надеяться нельзя : если ты ожидаешь, что каждый вызов SendBuffer() на передатчике в точности соответствует отдельному событию OnRead() на приемнике, в котором ты ожидаешь получить в точности то, что было отправлено непосредственно перед этим передатчиком - это не соответствует действительности.
>>Размер заголовка - 4 байта - может ли в принципе придти меньше?
Конечно !
И больше, и меньше, и вообще ничего может не прийти в событии OnRead !
Пойми - протокол-то поточный, а не message-ориентированный !
И к тому же в силу особенностей TCP/IP посылаемый передатчиком поток разбивается на пакеты, который "гуляют" по глоб.сети совершенно различными путями и приходят к точке приема далеко не одновременно и - более того - даже не в порядке их следования в исх.потоке. И задача приемника (на низком уровне) - собирать эти пакеты в оригинальный поток и сообщать тебе событием OnRead() о том, что очер. фрагмент потока достоверен и доступен к чтению.
Пусть передачтчик, например, выполнил ( не ожидая никаких подтверждений от приемника) M последовательных вызовов SendBuffer(), послав тем самым приемнику поток , скажем, в 20 байт. Из них первые 4 есть некий заголовок, оставшиеся 16 - собственно данные.
Приемник может получить все эти 20 байт (в той же последовательности, чтои они отправлены были) как за одно событие OnRead() (сразу все 20 байт) , так и за N событий, в каждом из которых могут быть доступны для чтения очередные 1 или 2 или 7 или иное кол-во байт из ожидаемого приемником вх.потока.
Ты же ожидаешь, что в очередном событии OnRead() пришло ТОЛЬКО ОДНО ОТДЕЛЬНОЕ И ЦЕЛИКОМ ДОСТУПНОЕ В ЭТОТ МОМЕНТ очер.сообщение в 20 байт, из которых первые 4 - заголовок. Но это может быть и не так ! Вдруг передатчик уже послал 4 сообщения (4 х 20 = 80 байт) на момент, когда у тебя впервые возникло событие OnRead() ? А ты отрезал (если успешно, то тебе еще повезло) первые 4 + 16 = 20 байт, сохранил их, а остальное попросту выкинул ! В то время как это может быть, например, заголовок 2-го сообщения и часть из 16-ти байт, сопровождающих его ....
Вот-с ...такие "сложности" ...
Поэтому грамотно реализованная логика приема/обработки поточных гнездовых данных должна предусматривать некий механизм, именуемый часто "машиной состояния". Достаточно грамотно машина состояния реализована в демо-проекте ScktSrvr.dpr. Загляни туда и разберись, что Борланд делает всякий раз вызывая метод ReceiveBuf()
← →
Sapersky_ (2002-07-06 10:06) [8]Ну ладно, ладно, я согласен, зачем так горячиться...
Посмотрел в ScktSrvr.dpr... жуть. Я-то представлял себе сетевое программирование по примеру Chat :).
Может, всё-таки перейду на DirectPlay (с первого взгляда он мне резко не понравился - фирменный стиль Microsoft и всё такое). Там вроде фрагментация/дефрагментация автоматически делается... ну, так написано в Help"е по крайней мере. Хотя сам по себе DPlay - тоже мутное дело.
Ещё - вопрос вроде был уже, но без толкового ответа: что писать в Host при соединении через Интернет (например, кто-нибудь хочет соединиться со мной...). IP не предлагать. Хотя бы ссылку на ответ.
← →
Digitman © (2002-07-08 08:16) [9]открывай св-ва TCP/IP-протокола, ассоциированного с интерфейсом связи с провайдером.
Имя_компютера[.доменный_суффикс]
← →
Sapersky_ (2002-07-09 13:42) [10]"Ассоциированного с интерфейсом связи..." - не понял...
Имя компьютера - это имя, под которым я вхожу в сеть?
Т.е., например, Host = "sapersky.atnet.ru". Так?
← →
Digitman © (2002-07-09 14:08) [11]Да, имя, под которым ты входишь в сеть провайдера и под которым провайдер регистрирует тебя в своей DNS-базе
Страницы: 1 вся ветка
Текущий архив: 2002.09.16;
Скачать: CL | DM;
Память: 0.52 MB
Время: 0.017 c