При работе с TServerSocket и TClientSocket использую модуль для принятия и отправления длинных блоков данных. Как в этом случае разумно осуществить передачу файлов?
Тот модуль писал неадекват. Делаешь стандартно : сначала отправляешь заголовок пакета - X - 1 байт целое без знака(тип пакета, если нужно больше 256 типов - используй WORD) XXXX - 4 байт целое без знака(размер пакета без заголовка) а дельше данные размером скок указал в заголовке. Вот написал(не проверял, но работать должно точно) : Code: uses ScktComp type TOnPacketReceived = procedure(Const pData : Pointer; wType : Byte; dwSize : Cardinal); procedure SendData(socket : TCustomWinSocket; Const pData : Pointer; wType : Byte; dwSize : Cardinal); var Header : Pointer; procedure _sendBuf(_data : Pointer; _size : Cardinal); var ret : Integer; pos : Cardinal; Begin pos := 0; while (pos < _size) do Begin ret := socket.SendBuf(PByteArray(_data)^[pos], _size - pos); Inc(pos, ret); end; end; Begin GetMem(Header, 5); try Move(PByte(@wType)^, PByte(Header)^, 1); Move(PByte(@dwSize)^, PByteArray(Header)^[1], 4); _sendBuf(Header, 5); _sendBuf(pData, dwSize); finally FreeMem(Header, 5); end; end; procedure SendString(socket : TCustomWinSocket; Const str : String; wType : Byte); Begin SendData(socket, @str[1], wType, Cardinal(Length(str))); end; procedure SendFile(socket : TCustomWinSocket; Const fileName : String; wType : Byte); var fs : TFileStream; buf : Pointer; Begin fs := TFileStream.Create(fileName, 0); try GetMem(buf, fs.Size); fs.ReadBuffer(buf^, fs.Size); try SendData(socket, buf, wType, fs.Size); finally FreeMem(buf, fs.Size); end; finally fs.Free; end; end; procedure LoopReceivePackets(socket : TCustomWinSocket; handler : TOnPacketReceived); Var headBuffer : Pointer; curSize : Cardinal; curType : Byte; curData : Pointer; procedure _rcvbuf(_data : Pointer; _size : Cardinal); Var pos : Cardinal; ret : Integer; Begin pos := 0; while (pos < _size) do Begin ret := socket.ReceiveBuf(PByteArray(_data)^[pos], _size - pos); Inc(pos, ret); end; end; Begin GetMem(headBuffer, 5); try repeat _rcvbuf(headBuffer, 5); Move(PByte(headBuffer)^, curType, 1); Move(PByteArray(headBuffer)^[1], curSize, 4); GetMem(curData, curSize); try _rcvbuf(curData, curSize); handler(curData, curType, curSize); finally FreeMem(curData, curSize); end; until false; finally FreeMem(headBuffer, 5); end; end;
Jingo Bo, спасибо! Вы очень помогли В общем идея мне ясна, но все же приведите пожалуйста пример отправки и получения данных (строки и файла) с помощью этого кода.
с удовольствием. прошу примеры кода или ссылки на статьи касательно вопроса организации пересылки текста и файлов посредством winsock.
Отправляем так : sendString(socket, 'Привет', 2); //Тип пакета 2(типа для строки) sendFile(socket, 'C:\test.txt', 1); //Тип пакета к примеру 1 На строне получаетля : когда-то когда сокет подключеается вызываем : LoopReceivePackets(socket, myhandler); обработчик myhandler : procedure myhandler(Const pData : Pointer; wType : Byte; dwSize : Cardinal); var fs : TFileStream; tmp : String; Begin case wType of 1 : Begin fs := TFileStream.Create('C:\test123.txt', fmCreate); fs.WriteBuffer(pData^, dwSize); fs.Free; end; 2 : Begin SetLength(tmp, dwSize); Move(pData^, tmp[1], dwSize); ShowMessage(tmp); end; end;
Если хочешь что бы пути не были статическими - передавай так : После заголовка уже в данных : XXXX - длянна строки пути файла XX...X - сама строка XX...X - файл Думаю идея понятна
Спасибо. Очень нравится такой подход к решению задачи. Но возникла следующая проблема: при отправке строки, после того как мы получили данные, вывели их через ShowMessage(tmp); и нажали ОК, снова появляется это же окно с нашим сообщением, и этот цикл не прекращается. Как это можно исправить? (отправку файлов пока не пробовал)
Я с VCL'овским TClientSocket мало знаком, привычнее на WinApi, но мне кажется трабла в нем, т.к. он буфер после чтения почему то не отчищает. Щас поищю свою старенькию либу на сокетах которая реализцет такю блочную передачу.
Вот архив, но в ней будет по сложнее сделать передачу файла, т.к. максимальный размер пакета может быть 65535 байт или 64 кб. Что бы сделать полноценную передачу файла - нужно сначал принять пакет о информации о файле(размер, имя и т.д.) потом перевести мой сокет в обычный режим и в другой процедуре принемать уже файл. Я завтра напишу пример как это сделать если нужно. Вот архив, там с HTTP тестом http://slil.ru/29517338
Хм... придется весь проект переводить с VCL сокетов на WinApi, но хотя наверное это и к лучшему сейчас разбираюсь с кодом. На первый взгляд всего очень много и наверное сразу всего понять не смогу, так что буду ждать примера. Спасибо за помощь))
Я программирую в Delphi 2010 поэтому пришлось в юнитах XSocket.pas и NetPacket.pas переделать PChar в PAnsiChar. Надеюсь это допустимо... во всяком случае после замены компилируется без проблем.
Благодарю. На Delphi 7 все прекрасно работает, а на Delphi 2010 возник ряд ошибок из за PChar'ов, но с этой проблемой справлюсь.
а зачем нам таймер SendTimer с интервалом 1 мс?... нельзя ли осуществлять передачу без него, например в каком нибудь цикле... я конечно не против таймера, но это как то странно мне кажется...)
Проблема в том что при отправки кусочка файла следующий кусочек отправляется когда срабатыват событие CliDataSend, от туда нужно по теории вызвать процедуру отправки след. кусочка. Проблема возникает в том что при очень быстрой(локальной) отправки происходит такое что процедуры вызывают друг друга, за счет чего получается переполнение стека. Я понимаю что коряво работает в этом месте. Можно сделать через события (объекты event винды) и отдельаный поток, но это на самом деле лишь в данном случае не нужно, т.к. скорость передачи не увеличится и нагрузка на проц тодже не уменьшится.