![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Передача данных при помощи сокетов. [D6, Win95/98, WinME, NT4, Win2k, WinXP]
V_Pavel © (2002-11-01 12:27:00)Мне нужно организовать обмен данными между клиентом и сервером при помощи сокетов. Как и с помощью чего это лучше сделать чтоб работало надежно? Предполагаемый размер пакетов колеблется от 10 байт до 500 килобайт. Данные нужно передавать как с клиента на сервер так и обратно. Если у кого есть примеры или ссылки на примеры поделитесь буду рад. Я побывал это делать при помощи стандартных компанент работает но не устойчива, а именно если после большого пакета размером примерно 50 кб. передаю пакет размером 10 байт он кудато теряется.
V_Pavel (2002-11-12 12:15:00) [21]Вроде все так но на самом деле чтото не так. У меня вот какое предположение. Вызвали мы метод SendStream он вернул True но на самом деле физически часть пакета еще находится на сервере. И скорее всего пока они не передадутся сокет заблокирован. Если это так то отсюда вопрос как узнать передан пакет или нет.
Digitman (2002-11-12 08:57:00) [22]давай-ка еще раз вникни и посмотри, что не так, как ожидается
//тело обработчика OnClientWrite()
var Stream: TMemoryStream; //лок.переменная
...
Stream := TMemoryStream(Socket.Data);
// если ссылка на экземпляр исх.потока ассоциирована с гнездом
if Assigned(Stream)
// и в момент нижеследующего вызова ВСЕ содержимое потока
// успешно поставлено в очередь на передачу
and Socket.SendStream(Stream) then
// то экз-р исх.потока будет автоматически уничтожен
// в private-методе TCustomWinSocket.SendStreamPiece (DropStream)
// и ассоциация с экз-ром разорвана, чтобы известить внешний код
// о том, что экз-р больше не существует
Socket.Data := nil;
//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
// первым делом даем возможность системе обработать тек.события, в т.ч. - OnWrite(), если оно возникнет в этот момент
//а если возникнет, то в его обработчике в случае успеха
// поток будет уничтожен, а ассоциация разорвана
Application.ProcessMessages;
// считываем состояние ассоциации
// изначально в OnConnect() она сброшена в nil
Stream := Socket.Data;
// если на этот момент еще ничто не передавалось
// или ранее осуществленные попытки передачи завершились успешно
// и предудущий экз-р потока уже не существует (уничтожен в DropStream)
if not Assigned(Stream) then
// создадим новый экз-р потока
Stream := TMemoryStream.Create;
// фиксируем тек.позицию считывания из потока
// ибо не делаем никаких предположений о том,
// новый это поток или уже существующий и ассоциированный с гнездом
CurStreamReadPos := Stream.Position;
//смещаем тек.позицию в конец потока
Stream.Position := Stream.Size;
//в хвост потока записываем то, что необходимо передать
Stream.Write(...то что нужно передать...);
//восстанавливаем тек.позицию в потоке
// для того, чтобы метод SendStreamPiece() мог начать продолжить
// отложенную передачу именно с того места, с которого при его предыд.вызове была неуспешной попытка передачи очер.блока
Stream.Position := CurStreamReadPos;
//если ассоциации на дан.момент нет, т.е. экз-р потока вновь создан
if not Assigned(Socket.Data) then
begin
// фиксируем ассоциацию гнезда с новым экз-ром потока
Socket.Data := Stream;
// стартуем потытку передачи потока
Процедура_обработчика_OnClientWrite(ServerSocket, Socket.Connections[index]);
end
// иначе ничего делать не надо -
// ассоциация есть, значит, ранее уже тем или иным образом
// OnWrite() вызывалась и на сей момент ассоциированный поток
// находится в стадии отложенной передачи;
// для продолжения/завершения отложенной передачи
// полагаемся на событие OnWrite(), которое будет вызвано
// автоматически, как только буфер гнезда освободится;
// здесь, в принципе, можно еще раз вызвать Application.ProcessMessages;
V_Pavel (2002-11-11 19:02:00) [23]На LocalHost-е работает если разношу клиент и сервер по разным машинам ситуация повторяется.
Digitman (2002-11-11 12:35:00) [24]
//в момент необходимости передачи данных клиенту
var Stream: TMemoryStream; //лок.переменная
var CurStreamReadPos: Integer; //лок.переменная
...
Application.ProcessMessages// !!!!!!!!!!!!!!!!
Stream := Socket.Data;
if not Assigned(Stream) then
Stream := TMemoryStream.Create;
CurStreamReadPos := Stream.Position; //тек.позиция считывания из потока
Stream.Position := Stream.Size; //в конец потока
Stream.Write(...то что нужно передать...); //в хвост потока - новые данные
Stream.Position := CurStreamReadPos; //восстановление тек.позиции
if not Assigned(Socket.Data) and not Socket.Connections[index].SendStream(Stream) then
begin
Socket.Data := Stream;
Процедура_обработчика_OnClientWrite(ServerSocket, Socket);
end
V_Pavel (2002-11-11 11:36:00) [25]На счет OnWrite()ты прав. Приемник получает 100 кил. А вот если передавать маленький пакет после того как большой был принят приемником то все работает.
Digitman (2002-11-11 08:26:00) [26]
> первый раз когда передаетя большой пакет
Быть того не может ! Самое первое событие OnWrite(), ДАЖЕ ЕСЛИ НИЧЕГО НЕ ПЕРЕДАЕТСЯ, должно возникать как минимум однократно. Это - извещение Winsock о готовности буфера передачи к записи в него блока потенциально передаваемых данных.
Каков полный объем переданных и принятых данных ? Я не спрашиваю, сколько раз возникало событие OnRead() у приемника - именно СКОЛЬКО БАЙТ всего получил приемник в результате передачи в общей сложности, например, 100 + 7 = 107кб ?
V_Pavel (2002-11-11 06:28:00) [27]Событие OnClientWrite() возникает в данном случае 2 раза первый раз когда передаетя большой пакет второй раз когда маленький но прередача маленького не осушествляется.
Digitman (2002-11-10 18:02:00) [28]при чем здесь SendBuf() и иже с ними ?
Я тебе для чего привел фрагмент кода от Борланда ? Чтобы ты ВНИК в него и не делал "отфонарных" умозаключений ! Данные потока передаются именно поблочно ! Или , как ты там говоришь, - "кусками")...
Лучше скажи, сколько раз возникает событие OnWrite() при посылке, скажем, фрагмента в 100кб и следом за ним - 7б
V_Pavel (2002-11-10 17:17:00) [29]Я это все прекрасно понимаю но из за того что SendStream выдает False данные копятся в буфере и не передаются. Я передаю пакет длиной 100 кил. Он передается но если следом передавать к примеру 2 пакета по 7 байт. Вот они остаются в очереди. Я так понимаю что при передаче большого пакета Socket блокируется пока его не передаст. Ток вот не удобнее ли здесь использовать SendBuff и передавать это все кусками.
Digitman (2002-11-10 13:33:00) [30]Вот что при этом происходит :
function TCustomWinSocket.SendStreamPiece: Boolean;
var
Buffer: array[0..4095] of Byte;
StartPos: Integer;
AmountInBuf: Integer;
AmountSent: Integer;
ErrorCode: Integer;
procedure DropStream;
begin
if FDropAfterSend then Disconnect(FSocket);
FDropAfterSend := False;
FSendStream.Free;
FSendStream := nil;
end;
begin
Lock;
try
Result := False;
if FSendStream <> nil then
begin
if (FSocket = INVALID_SOCKET) or (not FConnected) then exit;
while True do
begin
StartPos := FSendStream.Position;
AmountInBuf := FSendStream.Read(Buffer, SizeOf(Buffer));
if AmountInBuf > 0 then
begin
AmountSent := send(FSocket, Buffer, AmountInBuf, 0);
if AmountSent = SOCKET_ERROR then
begin
ErrorCode := WSAGetLastError;
if ErrorCode <> WSAEWOULDBLOCK then
begin
Error(Self, eeSend, ErrorCode);
Disconnect(FSocket);
DropStream;
if FAsyncStyles <> [] then Abort;
Break;
end else
begin
FSendStream.Position := StartPos;
Break;
end;
end else if AmountInBuf > AmountSent then
FSendStream.Position := StartPos + AmountSent
else if FSendStream.Position = FSendStream.Size then
begin
DropStream;
Break;
end;
end else
begin
DropStream;
Break;
end;
end;
Result := True;
end;
finally
Unlock;
end;
end;
function TCustomWinSocket.SendStream(AStream: TStream): Boolean;
begin
Result := False;
if FSendStream = nil then
begin
FSendStream := AStream;
Result := SendStreamPiece;
end;
end;
Digitman (2002-11-10 13:06:00) [31]>>при посылке после большого пакета маленького выдает false
Ну и что ? False - всего лишь признак того, что полное содержимое потока не может в данный момент поставлено в очередь гнезда на передачу. Как только очередь освободится, возникнет очер.событие OnWrite(), очер.порция из потока будет считана из потока и поставлена в очередь, и , если поток не полностью вычерпан, снова результатом будет False ... и так до полного исчерпания потока... как только поток "кусками" будет полностью вычерпан, результатом будет True, а объект-поток автоматически уничтожен
V_Pavel (2002-11-10 12:54:00) [32]да первое это я конечно глупость сморозил не до конца разобрался
а вот на счет Socket.SendStream(Stream) при посылке после большого пакета маленького выдает false.
Digitman (2002-11-10 08:13:00) [33]
> мне кажется должно быть так
> if ActualBytesRead <> - 1 then
> Inc(TotalDataSizeBytesRead, dword(buf^));
Это почему же ? Прокомментируй ход свей мысли.
> перестает реботать вот этот кусочек(выделенный жирным) кода
> в обработчике OnClientWrite()
> if Assigned(Stream) and Socket.SendStream(Stream)then
> Socket.Data := nil;
Что значит "перестает работать" ?
V_Pavel (2002-11-10 06:25:00) [34]После передачи большого пакета перестает реботать вот этот кусочек(выделенный жирным) кода в обработчике OnClientWrite()
if Assigned(Stream) and Socket.SendStream(Stream)then
Socket.Data := nil;
Вчем тут дело?
V_Pavel (2002-11-09 07:53:00) [35]Дело тут не в приемнике а в передатчике который описал Digitman ©. Он после передачи длинного пакета подвисает последующие пакеты становятся в очередь но не передаются. Немогу понять в чем тут дело.
V_Pavel (2002-11-08 17:55:00) [36]Digitman © Твой пример приемника тоже не примнмает маленькие пакеты после большого.
V_Pavel (2002-11-08 16:08:00) [37]В финкции DoReadDataSize мне непонятно вот это место
if ActualBytesRead <> - 1 then
Inc(TotalDataSizeBytesRead, ActualBytesRead);
мне кажется должно быть так
if ActualBytesRead <> - 1 then
Inc(TotalDataSizeBytesRead, dword(buf^));
Digitman (2002-11-06 11:56:00) [38]> 1) Данные разбиваются на пакеты;
На уровне IP-протокола - да
> 2) Каждый пакет весом 8 кило
Не пакет, а буфер очереди передачи. И - необязательно. Это значение в большинстве реализайий WinSock - по-умолчанию, но делать на это безусловную ставку некорректно.
> 3) Вместе с пакетами идут флаги
Все это на уровне IP-транспорта.
> 4) Разработчики Delphi
> не сочли нужным анализировать эти флаги
Это не их забота. Это - забота разработчиков кода, реализующего TDI и OSI
> (5) НО на API уровне анализ возможен.
На уровне WinsockAPI - нет. Это - высокоуровневая надстройка над низкоуровневыми транспортными сетевыми протоколами
daan_m (2002-11-06 11:39:00) [39]На сколько я знаю:1) Данные разбиваются на пакеты;2) Каждый пакет весом 8 кило3) Вместе с пакетами идут флаги один из них говорит, что пакет последний.4) Разработчики Delphi не сочли нужным анализировать эти флаги:(5) НО на API уровне анализ возможен.
Digitman (2002-11-05 14:02:00) [40]Куда тебе столько - аж 10 байт ?)
10-ю байтами можно описать число до 1208925819614629174706175 включительно ) ... Астрономическое почти число !)
Практически же для любых передаваемых объектов вполне достаточно и 4-х байт
type
TTransportState = (tsDataSizeReceiving, tsDataBlockReceiving);
var
TransportState: TTransportState = tsDataSizeReceiving;
TotalDataSizeBytesRead: DWord = 0;
TotalDataBlockBytesRead: DWord = 0;
TotalBlockSize: DWord = 0;
DataBlock: Pointer;
function DoReadDataSize(Socket: TCustomWinSocket): Boolean;
var
BytesRemains: DWord,
ActualBytesRead : Integer;
buf: Pointer;
begin
BytesRemains := SizeOf(DWord) - TotalDataSizeBytesRead;
if BytesRemains > 0 then
begin
buf := Pointer(DWord(@TotalBlockSize) + TotalDataSizeBytesRead);
ActualBytesRead := Socket.ReceiveBuf(buf^, BytesRemains);
if ActualBytesRead <> - 1 then
Inc(TotalDataSizeBytesRead, ActualBytesRead);
end;
Result := TotalDataSizeBytesRead = SizeOf(DWord);
end;
function DoReadDataBlock(Socket: TCustomWinSocket): Boolean;
var
BytesRemains: DWord,
ActualBytesRead : Integer;
buf: Pointer;
begin
if not Assigned(DataBlock) then
GetMem(DataBlock, TotalBlockSize);
try
BytesRemains := TotalBlockSize - TotalDataBlockBytesRead;
if BytesRemains > 0 then
begin
buf := Pointer(DWord(DataBlock) + TotalDataBlockBytesRead);
ActualBytesRead := Socket.ReceiveBuf(buf^, Min(BytesRemains, 4096));
if ActualBytesRead <> - 1 then
Inc(TotalDataBlockBytesRead, ActualBytesRead);
end;
Result := TotalDataBlockBytesRead = TotalBlockSize;
except
FreeMem(DataBlock);
DataBlock := nil;
raise;
end;
end;
procedure DoProcessDataBlock(DataBlock, TotalBlockSize);
begin
try
// ... обработка блока ...
finally
FreeMem(DataBlock);
DataBlock := nil;
end;
end;
procedure TFmain.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
var
case TransportState of
tsDataSizeReceiving:
if DoReadDataSize(Socket) then
begin
TotalDataBlockBytesRead := 0;
TransportState := tsDataBlockReceiving;
end;
tsDataBlockReceiving:
if DoReadDataBlock(Socket) then
try
DoProcessDataBlock(DataBlock, TotalBlockSize);
TotalDataSizeBytesRead := 0;
TransportState := tsDataSizeReceiving;
finally
DataBlock := nil;
end;
end;
begin
end;