-
Клиент отправляет сразу после подключения:
type TPrefix=record CMD: Integer; Size: Integer; end;
procedure TfMain.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket); var Prefix: TPrefix; ClientNo: String[6]; begin if Socket.Connected then begin Prefix.CMD:=CMD_LOGIN; Prefix.Size:=6; ClientSocket1.Socket.SendBuf(Prefix,SizeOf(Prefix)); ClientNo:=C_CLIENTNO; ClientSocket1.Socket.SendBuf(ClientNo,Prefix.Size); end; end;
сервер принимает:
procedure TForm1.ServerClientRead(Sender: TObject; Socket: TCustomWinSocket); var ClientNo: String[6]; SocketData: TclAssoc; Prefix: TPrefix; begin Socket.ReceiveBuf(Prefix,Socket.ReceiveLength); case Prefix.CMD of {CMD_LOGIN} 0:begin Socket.ReceiveBuf(ClientNo,Prefix.Size+1); ShowMessage('CMD_LOGIN'); ShowMessage(ClientNo); ShowMessage(IntTosTr(Length(ClientNo))); end; 2:; end; end;
При первовызове SendBuf(Prefix...) доставляется все как задумано, при втором вызове SendBuf(ClientNo...) принимается 0 байт и ClientNo=''. А если указать что принимать нужно 8 байт, то длина ClientNo в итоге получится 48. Заметил такую штуку. Если установить брекпоинт в том месте где принимается ClientNo то все принимается нормально... Помогите разобраться почему? Я в ступоре...
-
> Socket.ReceiveBuf(Prefix,Socket.ReceiveLength);
- тогда уж Socket.ReceiveBuf(Prefix,sizeof(Prefix)); т.к. в Socket.ReceiveLength - может быть уже весь пакет(и только чудом у тебя не запоролся стек, при переполнении буфера Prefix - размещенного на стеке)... > Socket.ReceiveBuf(ClientNo,Prefix.Size+1);
- отправляешь Prefix.Size, а принимаешь почему то Prefix.Size+1... при этом принимаешь и отправляешь ShortString в которой первый(и похоже лишний) символ - длинна строки...
-
> т.к. в Socket.ReceiveLength - может быть уже весь пакет(и > только чудом у тебя не запоролся стек, при переполнении > буфера Prefix - размещенного на стеке)...
Видимых результатов не дало... Я не пойму почему при брекпоинте я вижу то что отправил??
-
If Socket.ReceiveLength>=SizeOf(Prefix) Then Socket.ReceiveBuf(Prefix,SizeOf(Prefix)); If Socket.ReceiveLength>=Prefix.Size Then {... Считываем что надо!!!! Хотя если размер буфера приема меньше передаваемых данных (по умолчанию 4КБ), то надо просто сохранять все что пришло в свой буфер!!!! Иначе ничего не получиться.... ...}
-
Не кидайте тапками... Обшибся :) Не 4КБ, а 8 КБ... Давно к сетям не обращался...
>molinero TCP - потоковый протокол, это не значит, что если пришло сообщение/событие о приходе данных, то в буфере 100% будет все что ты послал!!!! Т.е., гипотетически, даже твой префикс в 8 байт может приходить по 1 байту! И твой код должен быть готовым к принятию фрагментированных/частичных данных!!! В том числе и то, что конец одного ТВОЕГО пакета может быть склеен с началом другого пакета!!!
-
Как вариант, если собираешься посылать/принимать пакеты свыше 4 КБ (не рекомендуется посылать больше чем MaxSize(8192) div 2 пакеты с данными, может постоянно выдавать ошибку переполнения буфера...), то стоит сделать примерно так... Type
PClientData=^TClientData;
TClientData=Record
Prefix:TPrefix;
ReadBytes:Integer;
prefixReady:Boolean;
<Какой-нибудь буфер на твое усмотрение (Stream/Динамический массив)>
End;
В процедуре коннекта: var
CData:PClientData;
Begin
...
New(CData);
CData^.PrefixReady:=False;
CData^.ReadBytes:=0;
Socket.Data:=CData;
...
End;
В процедуре приема данных: Begin ... If (not Socket.Data^.PrefixReady)and(Socket.ReceiveLength>=SizeOf(TPrefix)) Then <читаем префикс и устанавливаем PrefixReady=True> Else If Socket.Data^.ReadBytes<Socket.Data^.prefix.Size then <Вычисляем кол-во байт, которые необходимо считать, и если считали все необходимое, сбрасываем PrefixReady=False и ReadBytes=0!!!! В принципе УСЕ :), дальше делай с данными все что хочешь ;)> ... End;
-
Да забыл в процедуре дисконнекта подчищаем созданную динам. запись: ...
Dispose(Socket.Data);
...
-
Еще раз оговорюсь, сам заметил ошибку... :) If (Socket.Data^.PrefixReady)and(Socket.Data^.ReadBytes<Socket.Data^.prefix.Size) then
-
FireMan_Alexey благодарю за участие. Я сделал немного подругому. Принимаю в буфер, а потом накладываю на буфер указатель (PTPrefix).
type PTPrefix=^TPrefix; TPrefix=Packed record CMD: Integer; Size: Integer; end;
var Buf: Pointer; Prefix: TPrefix; ReceiveLength: Integer;
begin ReceiveLength:=0;
if Socket.ReceiveLength>=SizeOf(TPrefix)then Socket.ReceiveBuf(Buf^,SizeOf(TPrefix))
else begin while ReceiveLength<=SizeOf(Tprefix)do begin Socket.ReceiveBuf(Buf^,Socket.ReceiveLength); ReceiveLength:=+Socket.ReceiveLength; end; end;
Prefix:=PTPrefix(Buf)^;
В Prefix записывается то что я отправил, но в конце процедуры ошибка доступа к памяти. Нужно освободить памаять (Buf)?
И еще, Если длать так, то Buf будет перезаписваться и указывать на адрес новой порции данных? Как мне добавитьв буфер?
-
> molinero (01.06.2011 09:07:08) [8]
В конце ReceiveLength становится равным 0
-
С ошибкой доступа к памяти разобрался. Сначала GetMem(Buf,SizeOf(TPrefix)) Когда буфер больше ненужен FreeMem(Buf)
Дальше опять непонятно... На форуме прочитал, что если данные приходят не все сразу, нужно принимать в цикле.
Отправляю
procedure TfMain.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket); var Prefix: PTPrefix; ClientNo: String[6]; begin if Socket.Connected then begin New(Prefix); Prefix^.CMD:=CMD_LOGIN; Prefix^.Size:=6; ClientNo:=C_CLIENTNO; ClientSocket1.Socket.SendBuf(Prefix^,SizeOf(TPrefix));//Отправляем размер и команду ClientSocket1.Socket.SendBuf(ClientNo,Length(ClientNo));//Отправляем номер клиента Dispose(Prefix); end; end;
//принимаю
Prefix:=PTPrefix(Buf)^; FreeMem(Buf); case Prefix.CMD of {CMD_LOGIN} 0:begin //здесь я уже принял Prefix и теперь хочу принять номер клиента GetMem(Buf,Prefix.Size); ReceiveLength:=0; if Socket.ReceiveLength>=Prefix.Size then//здесь ReceiveLength=0 Socket.ReceiveBuf(Buf^,Prefix.Size) else while ReceiveLength<=Prefix.Size do//и вот здесь зацикливается begin Socket.ReceiveBuf(Buf^,Socket.ReceiveLength); ReceiveLength:=+Socket.ReceiveLength; end; s:=PClientNo(Buf)^; end; 2:; end;
Собственно вопрос. Как принимать в цикле, если на клиенте был вызван два раза подряд метод отправки данных?
-
> molinero (01.06.2011 10:09:10) [10]
А если один раз нажали, уже умеешь и правильно?
-
>molinero Чего то я ничего не понял...!!! :-\ Если у тебя в параметре Socket.ReceiveLength не хватает цифры до длины твоего префикса, то забей и не читай ничего до следующего события прихода данных!!! Я для чего тебе написал вот это и как заполнять это поле ??????: Type
PClientData=^TClientData;
TClientData=Record
Prefix:TPrefix;
ReadBytes:Integer;
prefixReady:Boolean;
<Какой-нибудь буфер на твое усмотрение (Stream/Динамический массив)>
End; Еще если ты собираешься в цикле писать данные из сокета в буфер, то какого лешего ты пишешь в одно и тоже место: while ReceiveLength<=SizeOf(Tprefix)do
begin
Socket.ReceiveBuf(Buf^,Socket.ReceiveLength);
ReceiveLength:=+Socket.ReceiveLength;
end; ReceiveBuf и SendBuf - это функции!!!!! Они возвращают кол-во посланных байт!!!!!!Даже если ты посылаешь/принимаешь 1 байт это не значит, что эти функции отработают на 100%!!! А как показано в твоем коде, ты не разу даже не поинтересовался ушли ли все данные у тебя или нет!!!!
-
Вот правильное принятие данных! RecSize:=Socket.ReceiveLength;
Size:= Socket.ReceiveBuf(Buf^,RecSize); В нормальном случае Size=RecSize, а может быть и не равны!!!!
-
И зачем тебе динамический префикс?????!!!!!!
Я тебе вот это показал:
PClientData=^TClientData;
Для того, чтобы ты смог использовать свойство Socket.Data: pointer!
В твоем случае, когда на сервере у тебя будет не 1 клиент, то возникнет проблема как привязать данные к каждому клиенту!!!!!!!! По сути расписал тебе всю схему доставки! Только осталось вписать в обработчики событий!!!
-
>molinero Не обижайся, но это полная охинея!!!! procedure TfMain.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var Prefix: PTPrefix;
ClientNo: String[6];
begin
if Socket.Connected then - Он точно законнектился, проверять не надо!!!!
begin
New(Prefix); - Зачем это, сделай свой TPrefix, он все равно умрет в стеке при выходе из процедуры!!!!
Prefix^.CMD:=CMD_LOGIN;
Prefix^.Size:=6;
ClientNo:=C_CLIENTNO;
ClientSocket1.Socket.SendBuf(Prefix^,SizeOf(TPrefix)); - Где проверка, что данные ушли и сколько их ушло???
ClientSocket1.Socket.SendBuf(ClientNo,Length(ClientNo));- здесь аналогично!!!
Dispose(Prefix); - Убрать!!!!! Как и New выше!!!!
end;
end;
-
Да! Работай вот так!!!! Socket.SendBuf(Prefix^,SizeOf(TPrefix)); а не ClientSocket1.Socket.SendBuf(Prefix^,SizeOf(TPrefix));
-
Далее на сервере: procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
CData:PClientData;
Begin
...
New(CData);
CData^.PrefixReady:=False;
CData^.ReadBytes:=0;
Socket.Data:=CData;
...
End;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
Begin
...
If (not Socket.Data^.PrefixReady)and(Socket.ReceiveLength>=SizeOf(TPrefix)) Then
<читаем префикс и устанавливаем PrefixReady=True>
Else
If Socket.Data^.ReadBytes<Socket.Data^.prefix.Size then
<Вычисляем кол-во байт, которые необходимо считать, и если считали все необходимое, сбрасываем PrefixReady=False и ReadBytes=0!!!! В принципе УСЕ :), дальше делай с данными все что хочешь ;)>
...
End;
Повторился, но это только для понимания!!!
-
procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Dispose(Socket.Data);
end;
-
Совет! Почитай про динамические массивы! Функцию SetLength! Потому что, судя из выше написанного тобою, ты слабо разбираешься/понимаешь в работе указателей и адресации!
Без обид! ;)
-
FireMan_Alexey © (02.06.11 01:32) [19]
Да какие обиды? Пинок в нужном направлении самое то!
-
В принципе принятие и отправку данных можно расписать по следующему алгоритму(во всяком случае я так делал):
1. Создаешь буферы на прием и отправку!!! 2. Ждешь события OnWrite потом посылаешь данные до тех пор, пока не получишь SOCKET_ERROR или данные не закончатся!!! 3. Ждешь событие OnRead и складываешь все в буфер приема, а когда необходимое кол-во данных оказывается в буфере, то проверяешь на корректность и т.д. 4. При дисконнекте освобождаешь буферы. 5. ВСЕ!!! :)
Сейчас поищу пример, только с моим компонентом! Но там должно быть понятно и так...
-
Type
BuffType=Array of Byte;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
Var
BuffTemp:BuffType;
Size,ReadSize:Integer;
begin
Size:=Socket.ReceiveLength;
SetLength(BuffTemp,Size);
ReadSize:=Socket.ReceiveBuf(BuffTemp,Size);
If ReadSize>0 Then BuffRead.Write(BuffTemp,ReadSize);
SetLength(BuffTemp,0);
end;
Const
MaxWrite=4096;
procedure TForm1.ClientSocket1Write(Sender: TObject;
Socket: TCustomWinSocket);
Var
BuffTemp:BuffType;
Size,WriteSize:Integer;
begin
While BuffWrite.Size>0 do
Begin
SetLength(BuffTemp,MaxWrite);
Size:=BuffWrite.Read(BuffTemp,MaxWrite);
WriteSize:=Socket.SendBuf(BuffTemp,Size);
SetLength(BuffTemp,0);
If WriteSize>0 Then
Begin
BuffWrite.Position:=0;
BuffWrite.Delete(WriteSize);
End
Else
Break;
End;
end; Напиши, если нужен исходный код моего класса по типу MemoryStream! Он на дин, массивах! :)
-
Это то что иероглифами!!!
{BuffRead - мой класс, но можно использовать и TMemoryStream, но мой удобней ;) }
-
Здравствуйте! Тоже бьюсь с сокетами, транспорт впринципе работает, но есть проблеммы. Подскажите пож-та кто знает. Проблема такая: В событиях по приему данных (ClinetRead или ServerClientRead) идет определение размера пакета данных и следом прием в цикле, пока весь пакет не будет принят. При этом находясь в режиме приема, это событие возникает несколько раз. Если процесс приема уже ведется и еще не закончен, то я выхожу из всех последующих срабатываний ClinetRead .... После приема пакета данных выхожу из события ... Проблема в том что при поступлении новых данных, событие ClinetRead или ServerClientRead в какой то произвольный момент может не сработать, несмотря на то что в сокете есть данные, оно перестает срабатывать. Если при этом принять данные в какой либо функции - событие продолжает работать.
Можно ли какой либо командой выйдя из обработчика ClinetRead заставить сработать этот обработчик снова, если в сокете есть данные?
-
На то есть три метода - ReceiveLength, ReceiveBuf и ReceiveText.
Любому из этих методов методу глубоко фиолетово откуда их вызывают - из обработчика OnXXXRead или не из обработчика, на их работоспособность это никак не влияет.
-
> Сергей М. © (11.01.12 13:41) [25]
Я использую методы ReceiveLength и ReceiveBuf. Смысл в том, что я не выхожу из обработчика OnXXXRead пока не будет принят весь известный мне оъбем данных. При этом событие OnXXXRead срабатывает неоднократно, а потом перестает срабатывать вообще. Прога при этом не висит.
Ну то есть примерно так: 1.Например сервер послал клиенту 100кб 2.на клиенте возникает событие OnXXXRead так как не все 100 кб придут клиенту сразу событие OnXXXRead срабатывает у меня несколько раз ... но я принимаю данные (все 100кб) в первом событии, пока не приму все 100кб, а из последующих срабатываний событий выхожу ... после этого покидаю первое событие OnXXXRead, в котором принимал данные .... все хорошо ... работает, принимает, данные не теряет .... Когда сервер еще что нибудь пошлет, опять событие срабатывает, принимает ... НО НЕ ВСЕГДА, в какой то момент что то втыкается ...и не смотря на то что на клиент отосланы данные, событие OnXXXRead не срабатывает.
Что то можно сделать вне обработчика OnXXXRead чтоб он опять сработал если в сокете есть данные? Или обязательно менять структуру приема данных?
-
> я принимаю данные (все 100кб) в первом событии
В режиме nonBlocking это неразумное решение.
Возникло OnRead - обрабатывай его и читай в обработчике не более чем столько сколько в этот момент кажет ReceiveLength. Остальные данные никуда не пропадут - возникнут очередные OnRead и в них ты продолжишь получение очередных принятых от партнера и доступных данных.
-
Замечено, что при срабатывании события OnXXXRead, если не принять данные и выйти из него, то последующих срабатываний события OnXXXRead возникать не будет ...
Так вот, можно ли сделать что нибудь ("переинициализировать" что ли ), чтоб при такой ситуации, оно сработало?
-
> при срабатывании события OnXXXRead, если не принять данные > и выйти из него, то последующих срабатываний события OnXXXRead > возникать не будет
Да, не будет, если из очереди поступивших от партнера данных тобой выбраны ВСЕ данные. Если же выбраны не все, то событие обязано возникнуть минимум еще однократно. И если оно у тебя не возникает, значит ты что-то накосячил в коде.
-
> Сергей М. © (11.01.12 15:09) [27]
Ясно ... да, у меня nonBlocking ....
ну тогда, мне предется переделывать схему приема и обработки данных ... типа писать все в TFileStream в OnRead, а в другом потоке производить разбор данных ... пойдет по такой схеме? только сложности на серверной стороне ... там ведь много клиеннтов может быть, разбор данных усложняется ...
-
> мне предется переделывать схему приема и обработки данных
А это уже твоя личная драма что ты сразу не пошел по верному пути.
> писать все в TFileStream в OnRead, а в другом потоке производить > разбор данных ... пойдет по такой схеме?
Такая схема еще дерьмовей. TFileStream тут как корове седло : наверняка вполне сойдет и TMemoryStream. Да и зачем другой поток, если ты даже при текущем решении без него обходишься ?
-
> Сергей М. © (11.01.12 15:26) [31]
Большое спасибо за совет и разъяснеия!
Ну как мне кажется ... в OnRead читать данные, сохранять, и быстро выходить ... А обрабатывать и геагировать на принятые данные уже в другом месте, в потоке, дабы не грузить остальную часть приложения ... а?
А FileStream вмесо MemoryStream чтоб память не занимать ... может напрасно конечно? :)
-
> в OnRead читать данные, сохранять, и быстро выходить
Именно так и правильно - на то и существует неблокирующий режим.
-
> Сергей М. © (11.01.12 16:00) [33]
ОК - Спасибо!
А как считаешь, лучше так реализовывать, как сказано выше, в неблокирующем режиме, или попробовать с блокирующим разобраться?
Работать в блокирующем режиме я не пробовал?
-
-
Хорошо! Спасибо!
|