Delphi. Отправка файла на гейт.

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by slesh, 16 Oct 2009.

  1. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    Вот друг попросил помочь с функций отправки файла на сервер. По-быстрому навоял функцию. Думаю что она будет полезна и другим людям.


    Работает всё на 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;
    
     
    #1 slesh, 16 Oct 2009
    Last edited: 16 Oct 2009
    7 people like this.
  2. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    можно пояснить как запихнуть код в делфи и последний код совсем не понятен. куда его пихать?
     
  3. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    s := SendFile('localhost', '/1.php', 'c:\test.txt');
    возвращет ответ.
    первый параметр - адрес сервера - host / ip
    второй - путь и имя скрипта на который посылается файл.
    третий - локальный файл который будет посылаться.

    Юзать как? - вставляй куда угодно и юзай. Всё расписано.
     
  4. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    вставил всю эту беду а делфи вот так-
    ошибку при компиляции дает -
     
  5. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    код вставляется после implementation
    учи Delphi или хотябы смотри как это она сама делает.
     
    1 person likes this.
  6. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    здорово. скомпилилось и получилась пустая форма. так и должно быть?
    может хоть окошко какое куда файл вставлять и кнопка send?
    я уже не говорю о прогрессбаре...
     
  7. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    автор ну чего молчишь?
     
  8. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    >>> учи Delphi или хотябы смотри как это она сама делает. <<<
    Этим всё сказано. Учить людей умножению я не собираюсь )
    Зачем тебе лезть в это, если ты не знаеш самый элементарных вещей.
     
    1 person likes this.
  9. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    вообщем спасибо Nightmarе помог наладить
     
  10. Hellsp@wn

    Hellsp@wn Elder - Старейшина

    Joined:
    29 Apr 2007
    Messages:
    401
    Likes Received:
    153
    Reputations:
    48
    по-хорошему вот так делать не айс
    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 :)
     
  11. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    господа помогите теперь сделать что бы не руками путь к загружаемому файлу писать а через опендиалог загружать и отправлять. сделал так-
    button2 вызывает опендиалог. имя выбранного файла отображается в листбоксе. button3 очищает листбокс если был выбран не тот файл. а вот как сделать что бы s := SendFile('localhost', '/1.php', 'c:\test.txt'); тут менялось ума не приложу
     
  12. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    пришла идея-
    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;
    господа не стесняемся предлагаем решение
    может поможет кто?
     
    #12 mobilka, 16 Oct 2009
    Last edited: 16 Oct 2009
  13. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    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;
    
     
    #13 slesh, 16 Oct 2009
    Last edited: 16 Oct 2009
  14. ErrorNeo

    ErrorNeo Elder - Старейшина

    Joined:
    2 May 2009
    Messages:
    923
    Likes Received:
    838
    Reputations:
    402
    уважаемые.
    этот код предполагает наличие у того, кто его пытается понять хотя бы основ знаний языка - на уровне "Delphi для самых начинающих".

    Если вы вообще никогда не видели этого языка в глаза - не задавайте идиотских вопросов.
     
  15. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    936
    Likes Received:
    162
    Reputations:
    27
    Вы задаете какие то нюбийские вопросы. Если вы изучали 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.
     
    1 person likes this.
  16. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    936
    Likes Received:
    162
    Reputations:
    27
    Никак, наверное. Хотя, может быть есть способ.
    Нет, это не очень удобно, лучше файл отправлять одним 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.
     
    1 person likes this.
  17. Gar|k

    Gar|k Moderator

    Joined:
    20 Mar 2009
    Messages:
    1,166
    Likes Received:
    266
    Reputations:
    82
    Если на сервере стоит наш скрипт... что нам стоит попдправиьт его вот так.

    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);
    }
    
    
    ну и + к этому можно сокету задать время жизни... при обрыве связи на строне сервака через некоторое время он сам откинется.
     
    _________________________
    1 person likes this.
  18. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    может поможет кто?
     
  19. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    мдауж
    Code:
    procedure TForm1.Button2Click(Sender: TObject);
    begin
       if OpenDialog1.Execute then
       begin
          SendFile('localhost', '/1.php', opendialog1.FileName);        
       end;
    end;
    
     
    1 person likes this.
  20. mobilka

    mobilka Member

    Joined:
    13 Oct 2009
    Messages:
    39
    Likes Received:
    5
    Reputations:
    0
    вот господа кое как с помощью вышеотписавшегося человека придал ей божеский вид. сделал кнопку загрузки файла и даже полосу (edit) ввода хоста. осталось прикрутить прогрессбар но это не в моих силах. кто может это сделать вот вам исходники
    http://webfile.ru/4012376