Сокеты C, изображение в формате jpeg отображается неправильно

#c #sockets

#c #сокеты

Вопрос:

Я изучаю сокеты и Im в процессе создания сервера сокетов, который может обслуживать файлы html, а также изображения в jpeg формате. Основываясь на других подобных сообщениях, которые я нашел, я не могу понять, что я делаю неправильно. Я использую fseek и ftell , чтобы получить размер изображения, а затем я использую fread для чтения этих байтов в malloc массив символов ‘ed, который я затем отправляю обратно. Что я делаю не так?

код для работы с jpeg файлами:

 else if (!strcmp(getFileExtension(fname), "jpg") || !strcmp(getFileExtension(fname), "jpeg"))
      {
        printf("got a jpeg requestn");
        FILE *fp = fopen(fname, "rb");

        if (fp == NULL)
        {
          perror("Error opening .jpg/.jpeg file");
          status = 500;
          ret = toStr(status);
          statusMessage = " Internal Server Errorn";
          body = "<h1>Server encountered error trying to open jpeg file</h1>";
          mode = html;
          goto shutdown;
        }

        // seek to end of file and get
        // length of the file to read
        fseek(fp, 0, SEEK_END);
        bodySize = ftell(fp);
        printf("file is %d bytesn", bodySize);

        // seek back to beginning of file
        fseek(fp, 0, SEEK_SET);
        // allocate and read len amt of bytes
        char *data = malloc(bodySize);
        fread(data, sizeof(char), bodySize, fp);
        fclose(fp);

        status = 200;
        ret = toStr(status);
        statusMessage = " OKn";
        body = data;
        mode = jpeg;
        goto shutdown;
    }
 

мой shutdown код, который просто устанавливает заголовки, коды состояния и т. Д. Данные файла jpeg должны находиться в пределах body переменной

     // cleanup and closing
    shutdown:
      // send just the headers first
      size = strlen(header)   strlen(ret)   strlen(statusMessage)   strlen(mode)   1;
      reply = (char *)malloc(size);
      reply[size - 1] = '';
      snprintf(reply, size, "%s%s%s%s", header, ret, statusMessage, mode);
      printf("n%snn", reply);
      send(new_sd, reply, size, 0);

      // send just body
      if (!strcmp(mode, jpeg)) // jpeg uses bodySize
      {
        printf("body size (%d):n%sn", bodySize, body);
        send(new_sd, body, bodySize, 0);
      }
      else // non jpeg uses strlen
      {
        printf("body size (%d):n%sn", (int)strlen(body), body);
        send(new_sd, body, strlen(body), 0);
      }

      shutdown(new_sd, SHUT_RDWR);
      close(new_sd);
      exit(0);
 

Вот мой ответ сервера браузеру (клиенту):

введите описание изображения здесь

Комментарии:

1. Вероятно, не следует обрабатывать данные jpeg так, как будто это строка…

2. Согласовано — проверьте, что strlen(body) на самом деле оценивается в вашем shutdown коде. Я предполагаю, что 4, учитывая ответ сервера. При strlen попадании на a 0 он вернется — ваши данные в формате JPEG могут содержать много 0 символов. Вместо этого вы могли бы использовать свою len переменную из ранее для этой цели.

3. Или вы могли бы просто сначала отправить заголовки, а затем отправить тело, избегая копирования данных изображения

4. Я принял предложение @vmt и теперь отправляю body и headesr отдельно. Я также перестал использовать strlen для двоичных файлов jpeg. Я обновил свой пост, чтобы отразить этот код. Файл jpeg по-прежнему отображается неправильно

Ответ №1:

Ничто не выделяется для меня с точки зрения кода сокета. Вместо того, чтобы быть проблемой (только) сокета, может быть, это (также) проблема протокола? Возможно, проблема с несоответствием HTTP / 1.1 (RFC 7230 )? не указаны ни длина содержимого, ни кодировка передачи? плохие окончания строк? (строка состояния обычно заканчивается на OK r n, а не OK n n)

Ответ №2:

не следует использовать snprintf with %s для двоичных данных, таких как байты jpeg, я считаю %s , что завершается нулевым байтом (0x00), что приводит к повреждению двоичного файла.

Также strlen(body) не будет работать, поскольку strlen также завершается нулевым байтом (0x00), что дает неверную длину для данных jpeg.

Попробуйте memcpy вместо этого.

Откройте файл в этом режиме "rb "

Простой псевдокод (может не компилироваться)

 int jpegDataSize = len;
char* jpegdata = data;
reply_size = strlen(HEADER)   strlen(ret)   strlen(statusMessage)   strlen(mode)   jpegDataSize   1; //can't use strlen for jpeg because it's binary
reply = (char *)malloc(reply_size);
memcpy(amp;reply[0], HEADER, strlen(HEADER));
memcpy(amp;reply[strlen(HEADER)], ret, strlen(ret));
memcpy(amp;reply[strlen(ret)], statusMessage, strlen(statusMessage));
memcpy(amp;reply[strlen(statusMessage)], mode, strlen(mode));
memcpy(amp;reply[strlen(mode)], jpegdata, jpegDataSize); //avoid strlen(jpegdata)
reply[reply_size - 1] = '';
printf("n-------------------------------------nresponse is (%d): n%snn", reply_size, reply); //can't use strlen (because jpegdata is binary)
send(new_sd, reply, reply_size, 0); //can't use strlen because jpegdata is binary.
shutdown(new_sd, SHUT_RDWR);
close(new_sd);
exit(0);
 

Комментарии:

1. Я воспользовался предложением @vmt из другого комментария отправить заголовок и тело отдельно, поэтому использование snprintf должно быть в порядке, поскольку оно предназначено только для HTTP-заголовка / кода состояния / сообщения / типа содержимого.

Ответ №3:

Оказывается, в коде моего отредактированного сообщения нет никаких логистических проблем. Проблема заключалась в том, что в коде завершения работы, когда я отправляю ответ в заголовке HTTP, я использовал size в качестве длины ответа, когда я должен был использовать strlen(reply) или size - 1 для send(new_sd, reply, size, 0) .