Вот друг попросил помочь с функций отправки файла на сервер. По-быстрому навоял функцию. Думаю что она будет полезна и другим людям. Работает всё на WinAPI + WinSock. Можно отправлять файлы до 2 гигов (не напряжно на память). После отправки файла функция возвращает страницу которую выдал сервак (с учетом HTTP заголовка) Гейт представляет собой скрипт PHP: <? $myfile = $_FILES['myfile']['tmp_name']; $name = basename($_FILES['myfile']['name']); if (!file_exists($myfile)) { echo "error"; } else { move_uploaded_file($myfile, $name); echo "ok"; } ?> Код функции: Code: uses winsock; function SendFile(host, script, filename:string):string; var sock : dword; ca : sockaddr_in; HTTPHeader : string; boundary : string; fs:dword; hFile : DWORD; buf : array [0..4095] of char; p : PHostEnt; rb : cardinal; len : integer; SubHeader1 : string; SubHeader2 : string; begin result := ''; // открываем файл на чтение hFile := CreateFile(PChar(filename), GENERIC_READ, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if hFile <> INVALID_HANDLE_VALUE then // если всё норм begin fs := GetFileSize(hFile, nil); // получим размер файла // создаем сокет sock := socket(AF_INET, SOCK_STREAM, IPPROTO_IP); // если создался сокет if sock <> INVALID_SOCKET then // если норм begin ca.sin_family := AF_INET; ca.sin_port := htons(80); // порт p := GetHostByName(PChar(host)); // получим ip по домену if p = nil then // если нету begin // значит это IP ca.sin_addr.s_addr := inet_addr(pchar(host)); end else begin // выдерим ip ca.sin_addr := PInAddr(p.h_addr_list^)^; end; // коннектимся if connect(sock, ca, sizeof(ca)) <> -1 then begin // если всё норм // генерим разделитель boundary := inttohex(random(65535), 4)+inttohex(random(65535), 4)+inttohex(random(65535), 4); // создаем части HTTP заголовка SubHeader1 := '--'+boundary+#13#10+ 'Content-Disposition: form-data; name="myfile"; filename="'+filename+'"'#13#10+ 'Content-Type: application/octet-stream'#13#10#13#10; SubHeader2 := #13#10+'--'+boundary+'--'#13#10; HTTPHeader := 'POST '+script+' HTTP/1.1'#13#10+ 'Host: '+host+#13#10+ 'Connection: close'#13#10+ 'Content-Type: multipart/form-data; boundary='+boundary+#13#10+ 'Content-Length: '+inttostr(fs + length(SubHeader1) + length(SubHeader2))+#13#10#13#10+SubHeader1; // посылаем заголовок send(sock, HTTPHeader[1], length(HTTPHeader), 0); while true do begin rb := 0; // читаем 4 кила из файла ReadFile(hFile, buf, 4096, rb, nil); if rb = 0 then break; // если не считалось то выход из цикла send(sock, buf, rb, 0); // пошлем считанные данные end; // пошел последний разделитель send(sock, SubHeader2[1], length(SubHeader2), 0); while true do // к цикле ждем ответа от сервера begin len := recv(sock, buf, 4096, 0); // считали данные if len > 0 then // если есть чтото begin result := result + copy(buf, 0, len); end else begin // если нет больше данных то выходим из цикла break; end; end; end; closesocket(sock); // закрываем сокет end; CloseHandle(hFile); // закрываем файл end; end; Юзать это можно так: Code: var ws : TWSAData; s : string; begin WSAStartup($101, ws); s := SendFile('localhost', '/1.php', 'c:\test.txt'); ShowMessage(s); end;
s := SendFile('localhost', '/1.php', 'c:\test.txt'); возвращет ответ. первый параметр - адрес сервера - host / ip второй - путь и имя скрипта на который посылается файл. третий - локальный файл который будет посылаться. Юзать как? - вставляй куда угодно и юзай. Всё расписано.
здорово. скомпилилось и получилась пустая форма. так и должно быть? может хоть окошко какое куда файл вставлять и кнопка send? я уже не говорю о прогрессбаре...
>>> учи Delphi или хотябы смотри как это она сама делает. <<< Этим всё сказано. Учить людей умножению я не собираюсь ) Зачем тебе лезть в это, если ты не знаеш самый элементарных вещей.
по-хорошему вот так делать не айс Code: while true do begin rb := 0; // читаем 4 кила из файла ReadFile(hFile, buf, 4096, rb, nil); if rb = 0 then break; // если не считалось то выход из цикла send(sock, buf, rb, 0); // пошлем считанные данные end; далеко не факт что удасться отправить за раз 4096
господа помогите теперь сделать что бы не руками путь к загружаемому файлу писать а через опендиалог загружать и отправлять. сделал так- button2 вызывает опендиалог. имя выбранного файла отображается в листбоксе. button3 очищает листбокс если был выбран не тот файл. а вот как сделать что бы s := SendFile('localhost', '/1.php', 'c:\test.txt'); тут менялось ума не приложу
пришла идея- s := SendFile('localhost', '/1.php', 'тут переменная какаято'); procedure TForm1.Button2Click(Sender: TObject); begin if OpenDialog1.Execute then begin Listbox1.Items.Add(ExtractFileName(opendialog1.Fil eName)); тоже чтото надо дописать end; end; господа не стесняемся предлагаем решение может поможет кто?
2 Hellsp@wn если бы да кабы. По хорошему нужно обрабатывать что разорвалась связь с серваком, а также что не удалось считать файл. итд итп. А друг юзверь вообще комп ребутнул. 4 кила полюбому уйдут в ядро, и send вернет это кол-во. другое дело, что не факт что они по сети передадутся. хотя по хорошему достаточно просто подправить: Но всё равно это лишнее. Code: wb := 0; error_flag := false; while wb < rb do begin len := send(sock, pointer(dword(@buf[0]) + wb)^, rb - wb, 0); if len < 1 then begin error_flag := true; break; end; wb := wb + len; end; if error_flag then break;
уважаемые. этот код предполагает наличие у того, кто его пытается понять хотя бы основ знаний языка - на уровне "Delphi для самых начинающих". Если вы вообще никогда не видели этого языка в глаза - не задавайте идиотских вопросов.
Вы задаете какие то нюбийские вопросы. Если вы изучали Delphi, то должны понимать, как вызвать эту функцию. К тому же автор все детально описал. Почему не факт? Все будет нормально, если делать, скажем, так: Code: while true do begin rb := 0; // читаем 4 кила из файла ReadFile(hFile, buf, 4096, rb, nil); if rb = 0 then break; // если не считалось то выход из цикла if send(sock, buf, rb, 0) <> rb then break; // пошлем считанные данные end; Спасибо за код, slesh. (+ не могу поставить пока что) Уже давно спросить хотел, так как сам занимаюсь этим... По моему самым правильным вариантом было бы начинать делать отправку файла вместе с заголовком, так как делают браузеры. К примеру: составил заголовок, который занимает 200 байт. Если мы решили отправлять по 4096 байт, то, соответственно, 4096-200=3896 байт считываем из файла, и заносим в массив. Это честно говоря не особо легко реализовать, особенно когда используем формат Content-Type: multipart/form-data, так как после отправки файла, мы должны отправить еще boundary.
Никак, наверное. Хотя, может быть есть способ. Нет, это не очень удобно, лучше файл отправлять одним POST запросом (как и делал slesh). Но если нужно учитывать значения upload_max_filesize и post_max_size, тогда можно было бы: 1) Получить от сервака сессию, которою потом передавать снова назад на сервер в виде либо кукиса, либо как параметр POST запроса. 2) Отправлять файл как ты уже говорил, кусками по 1 Мб., при этом указывать номер части и свою сессию. Сессия нужна для того, чтобы сервак знал, в какой файл будем записывать полученные данные. То есть, когда сервак только генерирует сессию, он должен создать уникальный префикс для файла, (скажем file_name_777_). Потом передаем клиенту сессию. Когда клиент уже будет отправлять нам данные на сервер, он должен будет указывать свою сессию, а сервак в свою очередь будет создавать файлы вида file_name_777_номер_части_файла с соответствующем содержанием. Когда отослали все части, я бы сделал следующее: отослал бы дополнительный POST запрос на скрипт, в котором указывал бы сессию, и количество частей, которое я отправил. Сервер должен проверить, есть ли все части файла (то есть, существуют ли файлы file_name_777_1...file_name_777_N). Если существуют, - объединить всех в один файл, сами части удалить. Но это мой вариант, хочу выслушать ваши, если есть ограничения в upload_max_filesize и post_max_size.
Если на сервере стоит наш скрипт... что нам стоит попдправиьт его вот так. PHP: <?php if(empty($_POST)){ echo ini_get("upload_max_filesize")."\n".ini_get("post_max_size")."\n\0"; } ... и в программе сперва наперво отправлять GET запрос... так же на стороне сервера можно эти значения в байты перевести... в своих клиентах я испольую вместо send и recv такие функции Code: // отослать ВСЕ ... - правильная функция int sendall(SOCKET s, char *buf, int len, int flags) { int total = 0; int n; while(total < len) { n = send(s, buf+total, len-total, flags); if(n == -1) { break; } total += n; } return (n==-1 ? -1 : total); } // принять ВСЕ ... - правильная функция int recvall(SOCKET s, char *buf, int len, int flags) { int total = 0; int n; while(total < len) { n = recv(s, buf+total, len-total, flags); if(n == -1) { break; } total += n; } return (n==-1 ? -1 : total); } ну и + к этому можно сокету задать время жизни... при обрыве связи на строне сервака через некоторое время он сам откинется.
мдауж Code: procedure TForm1.Button2Click(Sender: TObject); begin if OpenDialog1.Execute then begin SendFile('localhost', '/1.php', opendialog1.FileName); end; end;
вот господа кое как с помощью вышеотписавшегося человека придал ей божеский вид. сделал кнопку загрузки файла и даже полосу (edit) ввода хоста. осталось прикрутить прогрессбар но это не в моих силах. кто может это сделать вот вам исходники http://webfile.ru/4012376