LINUX.ORG.RU

Как сервер отдаёт картинки?

 


1

1

Здравствуйте.

Я пишу маленький серверок на СИ, работа которого заключается в отдаче html-странички и css-файла. С текстовыми файлами вроде просто, открыл файл ⇨ прочитал ⇨ отдал клиенту.

Скажите, а как отдавать картинки? Если можно пример. Спасибо.


В stackoverflow с... дети!

Гуглить на предмет MIME-Type и Content-Type.

  • Plain text: text/plain
  • HTML: text/html
  • CSS: text/css
  • JPEG: image/jpeg
  • PNG: image/png
beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)
6 октября 2016 г.
Ответ на: комментарий от quest

Не понимаю как передать картинку клиенту.

Вот часть кода (сервер очень простой):

Клиент запрашивает index.html и я ему его отдаю вот так.

    else if((strstr(buffer, "GET /index.html ")) != NULL) 
     {
       memset(send2_array, 0, ARRAY_SIZE * sizeof(char));
       read_in_file("index.html");
       int len_ara = count_simvol + (int)strlen(response) + 1;

       snprintf(send2_array, len_ara, "%s%s", response, send1_array);
       write(client_fd, send2_array, count_simvol  + 59); 
       close(client_fd);
       warning_access_log(buffer);
       printf("Trans index.html.\n\n");
     }

Здесь файл заносится в переменную и она возвращается в функцию (написаную выше) и улетает клиенту.

void read_in_file(char *name_file) 
{ 
   count_simvol = 0;
   memset(send1_array, 0, ARRAY_SIZE * sizeof(char));
   memset(fpfile, 0, 64 * sizeof(char));
   snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen(name_file) + 1, "%s%s", patch_to_dir, name_file);

   FILE *file; 
   file = fopen(fpfile,"r");
   if(file == NULL) error_log("open file.");

   int ch;
   while(ch = getc(file), ch != EOF)
    {
      send1_array[count_simvol] = (char) ch;
      count_simvol++;
      if(count_simvol == ARRAY_SIZE - 2)  break;
    }

   fclose(file);
}

Вопрос в том что делать, когда клиент запрашивает картинку:

else if((strstr(buffer, "png")) != NULL) 
     {
...

Что делать дальше? Как картинку засунуть в переменнную и отдать клиенту? Или как это вобще делается?

Люди добрые, напишите пожалуйста пример, а то уже неделю ковыряюсь в чужих исходниках и не нахожу нужного.

Спасибо.

stD
() автор топика
Ответ на: комментарий от devl547

Если не затруднит, вставьте сюда часть которая отвечает за отдачу картинки.

stD
() автор топика
Ответ на: комментарий от stD

Файл весит 53кб, а в консоли браузера пишет, что передано 50кб и соответственно «изображение не может быть показано, так как содержит ошибки». Может конец файла не правильно определяется?

stD
() автор топика
Ответ на: комментарий от stD

это бинарный поток идет, не все символы печатаемые и тут заменяются на точки, ты в другую вкладку переключись в headers и там смотри response headers это то что должен отдавать сервер а дальше сам файл.

можешь взять какую нибудь спец прокси и через нее запрос отправить и ею залогировать байтовый поток чтобы было еще более наглядно

quest ★★★★
()
Ответ на: комментарий от stD

Response Headers нужно смотреть, у тебя это Content-Type - это то что нужно отдать перед бинарным потоком. это, два перевода строки и бинарный поток. это в самом примитивном случае, дальше можешь почитать про статус 304, Etag и тд

quest ★★★★
()
Ответ на: комментарий от quest

Проклятье какое-то), можно я сначала начну:

Мне нужно передать рисунок клиенту.

Я считываю его в массив:

    
   char send1_array[ARRAY_SIZE] = {0,};
   int ch;
   int count_simvol = 0;

   ...

   FILE *file; 
   file = fopen(fpfile,"rb");
   if(file == NULL) error_log("open file.");

   while(ch = getc(file), ch != EOF)
    {
      send1_array[count_simvol] =  ch;      
      count_simvol++;    
      if(count_simvol == ARRAY_SIZE - 2)  break;
    }

   fclose(file);

Это правильно?

stD
() автор топика

С текстовыми файлами вроде просто, открыл файл ⇨ прочитал ⇨ отдал клиенту.

с точки зрения сервера, отдача текстового файла и картинки - «до лампочки!» (c)

Если сложно для понимания, то остается конвертнуть картинку в текстовое представление ...

anonymous
()
Ответ на: комментарий от stD

Нет, у тебя в картинке может попасть EOF и на этом чтение остановится. Да и зачем читать всю картинку в массив? А если там например большой файл? Читай небольшую порцию из файла, затем отправляй и так снова.

Да кроме прочего у тебя статический размер массива и чтение по одному байту.

Рано тебе еще такими вещами заниматься, почитай про malloc/free, fread и тому подобное.

PoMbl4
()
Последнее исправление: PoMbl4 (всего исправлений: 1)
Ответ на: комментарий от stD

открывай файл open(), далее делай на него fstat(), бери размер файла и делай malloc(), далее читай read() в цикле пока не прочитаешь, далее закрывай файл, отдавай клиенту содержимое и делай free().

в идеале не malloc()/read()/free() а mmap() и munmap()

quest ★★★★
()
Ответ на: комментарий от PoMbl4

Нет, у тебя в картинке может попасть EOF и на этом чтение остановится

Да, может попасть EOF, но не в моём случае. Файл читается полностью.

А если там например большой файл?

Сервер специфичный, я знаю какие картинки будут запрашиваться. Их вобще всего четыре.

Да кроме прочего у тебя статический размер массива и чтение по одному байту.

Рано тебе еще такими вещами заниматься, почитай про malloc/free, fread и тому подобное.

Статичный массив сделан специально, чтение побайтно взято из другой функции, там так нужно. В калоках-малоках сейчас нет нужды.

stD
() автор топика
Ответ на: комментарий от quest

Вобщем заработало, похоже дело кроется в snprintf

snprintf(send2_array, len_ara, «%s%s», response, send1_array);

Что-то неправильно здесь происходит.

stD
() автор топика

На самом деле идея «прочитать файл в память целиком и потом отдать по сети» - неправильная. Лучше читать файл блоками и блоками же сразу отдавать. А не работает, прозреваю, потому что картинки не лезут в ARRAY_SIZE.

legolegs ★★★★★
()
Ответ на: комментарий от stD

%s - '\0'-терминальная однобайтовая строка. То есть snprintf обрабатывает её пока в ней не встретится '\0'. Что характерно в png файлах переодически встречается 0 байт (можешь открыть картинку hexdump'ом и полюбоваться на них). Один из возможных способов конкатенировать блоки памяти:

memcpy(send2_array, response, strlen(response));
memcpy(send2_array + strlen(response), send1_array, count_simvol);

anonymous
()
Ответ на: комментарий от stD

юзай memcpy как уже сказали. %s выводит до нуля который может встретиться в твоей картинке и таким образом отдана будет только часть картинки

quest ★★★★
()

pwritev() можешь заюзать чтобы не копировать

quest ★★★★
()
Ответ на: комментарий от anonymous

То есть snprintf обрабатывает её пока в ней не встретится '\0'.

Не понял про что вы говорите, snprintf засовывает в массив столько символов, сколько указано во втором параметре.

snprintf(send2_array, len_ara, «%s%s», response, send1_array);

И не важно, есть там нули или нет. Нуль она добавит в самый конец. Или я не прав?

...

Вот за это спасибо.

memcpy(send2_array + strlen(response), send1_array, count_simvol);

...

Я почему строки то склеивал, дело в том, что если в функции отправки...

else if((strstr(buffer, "GET / ")) != NULL) 
     {
       memset(send2_array, 0, ARRAY_SIZE * sizeof(char));
       read_in_file("index.html");
       int len_ara = count_simvol + (int)strlen(response) + 1;

       snprintf(send2_array, len_ara, "%s%s", response, send1_array);
   
       write(client_fd, send2_array, len_ara ); 
       close(client_fd);
     }

...строки склеить и сделать write один раз, тогда запустив сервер можно спокойно зажать F5 и прога будет работать.

А если делать так:

else if((strstr(buffer, "GET / ")) != NULL) 
     {
       read_in_file("index.html");

       write(client_fd, response, (int)strlen(response));
       write(client_fd, send1_array, count_simvol); 
       close(client_fd);
     }

То есть отправлять кусками - два раза write, тогда запустив сервер и зажав F5 прога падает через пару-тройку секунд. Это почему так?

stD
() автор топика
Ответ на: комментарий от stD

Не понял про что вы говорите, snprintf засовывает в массив столько символов, сколько указано во втором параметре.

snprintf:

The snprintf() function shall be equivalent to sprintf(), with the addition of the n argument which states the size of the buffer referred to by s. If n is zero, nothing shall be written and s may be a null pointer. Otherwise, output bytes beyond the n-1st shall be discarded instead of being written to the array, and a null byte is written at the end of the bytes actually written into the array.
Перевожу: snprintf игнорирует всё что ты засовываешь в массив после количества байт заданного вторым параметром.Ничего не мешает snprintf записать в массив меньшее число элементов.

И не важно, есть там нули или нет.

%s - The argument shall be a pointer to an array of char. Bytes from the array shall be written up to (but not including) any terminating null byte. If the precision is specified, no more than that many bytes shall be written. If the precision is not specified or is greater than the size of the array, the application shall ensure that the array contains a null byte.

Перевожу: %s аргументом считается последовательность байт в массиве до первого нулевого байта. Остальные элементы могут катиться колбаской(а могут и нет).

Нуль она добавит в самый конец. Или я не прав?

The sprintf() function shall place output followed by the null byte, '\0', ...

Перевожу: да, прав.

То есть отправлять кусками - два раза write, тогда запустив сервер и зажав F5 прога падает через пару-тройку секунд. Это почему так?

На этот вопрос сможет ответить только экстрасенс или человек видевший весь код. Отсюда два варианта: ждать человека с хрустальным шаром или открыть код проекта. Хотя я бы посоветовал взять в одну руку gdb, в другую руку голову и приятно провести вечер за отладкой.

anonymous
()

Это не твоё. )

Ну или толсто тролишь, кто тебя разберёт. Я без проблем понял, как это работает, когда мне было... Дай подумать, что-то около 12 лет, и я не вундеркинд. Так что вариант, что это просто не твоё, если даже у ребёнка не было подобных проблем.

anonymous
()
Ответ на: комментарий от anonymous

%s

По поводу %s - я правильно понимаю что если я хочу засунуть в массив строку...

snprintf(array, 7, "%s", "строка");

...и в строке «строка», между р и о будет нулевой байт, то в массив array запишется только «стр». Я прав?

...

То есть отправлять кусками - два раза write,

По поводу этого постараюсь правильно сформулировать и задать вопрос заново.

И да, спасибо, что отвечаете.

stD
() автор топика
Ответ на: комментарий от stD

По поводу - два раза write:

Весь код выкладывать не имеет смысла, так как всё будет понятно из ниже написанного.

Приходит запрос на файл - index.html, я его открываю, засовываю в массив send1_array, потом склеиваю с заголовком response в send2_array и отправляю клиенту - write.

char response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n";

...

 else if((strstr(buffer, "GET / ")) != NULL) 
     {
       int count_simvol = 0;
       memset(send1_array, 0, ARRAY_SIZE * sizeof(char));
       memset(send2_array, 0, ARRAY_SIZE * sizeof(char));
       memset(fpfile, 0, 64 * sizeof(char));
       snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen("index.html "), "%s%s", patch_to_dir, "index.html");

       FILE *file; 
       file = fopen(fpfile,"r");
       if(file == NULL) error_log("open file.");

       int ch;
       while(ch = getc(file), ch != EOF)
        {
          send1_array[count_simvol] = (char) ch;
          count_simvol++;
          if(count_simvol == ARRAY_SIZE - 2)  break;
        }

       fclose(file);

       int len_ara = count_simvol + (int)strlen(response) + 1;

       snprintf(send2_array, len_ara, "%s%s", response, send1_array);

       write(client_fd, send2_array, count_simvol); 
       close(client_fd);
     }

То есть, здесь write делается один раз.

Запускаю сервер, открываю страничку и зажимаю F5 - всё прекрасно работает. . . . Если же отправлю по отдельности заголовок и контент, то есть делаю два раза write...

char response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n";

...

 else if((strstr(buffer, "GET / ")) != NULL) 
     {
       int count_simvol = 0;
       memset(send1_array, 0, ARRAY_SIZE * sizeof(char));
       memset(fpfile, 0, 64 * sizeof(char));
       snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen("index.html "), "%s%s", patch_to_dir, "index.html");

       FILE *file; 
       file = fopen(fpfile,"r");
       if(file == NULL) error_log("open file.");

       int ch;
       while(ch = getc(file), ch != EOF)
        {
          send1_array[count_simvol] = (char) ch;
          count_simvol++;
          if(count_simvol == ARRAY_SIZE - 2)  break;
        }

       fclose(file);

       write(client_fd, response, (int)strlen(response));
       write(client_fd, send1_array, count_simvol); 
       close(client_fd);
     }

Здесь делается write два раза.

Запускаю сервер, открываю страничку и зажимаю F5 и через пару десятков секунд сервер падает.

Я понимаю, что это выглядит как бред, но это происходит. Как-то можно это объяснить?

stD
() автор топика
Ответ на: комментарий от stD

GDB написал вот такое:

Program received signal SIGPIPE, Broken pipe.
0x00007ffff79c234d in write () at ../sysdeps/unix/syscall-template.S:81
81	../sysdeps/unix/syscall-template.S: Нет такого файла или каталога.
(gdb) r

Помогите понять, что это значит?

stD
() автор топика
Ответ на: комментарий от stD

...и в строке «строка», между р и о будет нулевой байт, то в массив array запишется только «стр». Я прав?

Let's find out. $ cat olo.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARRAY_LEN 1024

int main(void)
{
	char array[ARRAY_LEN] = {0};
	int chars_printed = snprintf(array, ARRAY_LEN, "%s", "str\0ing");

	printf("%d characters was printed in array\n", chars_printed);
	printf("BUF_LEN = %d\n", ARRAY_LEN);
	printf("array length = %lu\n", strlen(array));
	printf("array = %s\n", array);

	for (int i = chars_printed; i < ARRAY_LEN; ++i)
	{
		if (array[i])
		{
			printf("Wow! array contains another char %c\n", array[i]);
		}
	}

	return EXIT_SUCCESS;
}
$ gcc -std=c11 -pedantic -Wall olo.c -o olo && ./olo 
3 characters was printed in array
BUF_LEN = 1024
array length = 3
array = str
Да, ты прав.

anonymous
()
Ответ на: комментарий от stD

Program received signal SIGPIPE, Broken pipe.

signal.h:

SIGPIPE - Write on a pipe with no one to read it.
The default actions are as follows: Abnormal termination of the process.
Перевожу: если ты попробуешь писать в закрытый сокет, то будет вызван обработчик сигнала SIGPIPE. По умолчанию обработчик тупо убивает процесс.

Когда ты зажимаешь F5 происходит следуещее:

  • браузер открывает соединение
  • браузер отправляет запрос
  • браузер закрывает соединение
  • На колу мочало,начинай сначала.

Если сервер начинает писать в сокет после того как клиент закрыл соединение, то он имеет ненулевой такой шанс получить SIGPIPE. Поскольку объяснение того, почему один write вместо двух работает значительно лучше, содержит слишком много матана(и мне лень объяснять [и я могу ошибаться в деталях]), можешь считать, что в два раза больше вызовов write => в два раза больше вероятность получить SIGPIPE. Не в этом суть.

Самый простой способ избежать SIGPIPE при работе с сокетами в Linux - это использовать send с флагом MSG_NOSIGNAL вместо write. В случае с вышеописанным сервером это будет выглядеть примерно так:

#include <sys/socket.h>
...
send(client_fd, response, (int)strlen(response), MSG_NOSIGNAL);
send(client_fd, send1_array, count_simvol, MSG_NOSIGNAL);
...
вместо
#include <unistd.h>
...
write(client_fd, response, (int)strlen(response));
write(client_fd, send1_array, count_simvol);
...

anonymous
()
Ответ на: комментарий от anonymous

Благодарю Вас, терпеливый и отзывчивый человек!

А ещё сегодня я познакомился с GDB, прелюбопытнейшая штуковина. У меня там в проге ещё отдельный поток (работает с /dev/ttyUSB0), так он оказывается жив...

Ещё раз, безмерно благодарен!

stD
() автор топика
Ответ на: комментарий от quest

fstat(), бери размер файла и делай malloc(), далее читай read()

Нафейхоа малок? Узнав размер файла, сформировать респонз хедер и сразу срать картинку в сокет. Таким образом еще и партиал 206 легко прикрутить. Но ТС не слегка так тупит, да.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Возник ещё один вопрос на тему картинок:

Если картинка запрашивается из файла index.html

$('#pir2').html('<img src="tux.png">');

...

<div id="pir2"></div>

То всё хорошо, картинка прилетает. Вот скрин с консоли браузера.

https://www.dropbox.com/s/axk57a7kpqgwstm/1.png?dl=0

В заголовке ответа «image/png»


А если запрашивать рисунок через файл style.css

body {
/*background: #312f2f;*/
background: url(tux.png);
margin: 30px 60px;
...

Тогда рисунка нет. Скрин консоли.

https://www.dropbox.com/s/0o1bgwfnstqttuc/2.png?dl=0

Видимо, потому что в заголовке ответа написано «text/css».

Как быть в такой ситуации?

stD
() автор топика
Ответ на: комментарий от deep-purple

Дак я в самом начале топика спрашивал - как отдавать картинки?

stD
() автор топика
Ответ на: комментарий от stD

Не надо так делать, это плохо. Если хочется разобраться именно с тем, как сервер делать, лучше взять другой язык. На Си код без должной дисциплины быстро превратится в запутанный клубок, который будет падать от любого неосторожного запроса.

strstr(buffer, «GET /index.html »)

Некорректно подстроку в буфере искать. Если у тебя в буфер собирается весь заголовок, туда попадут и поля, не только первая строка. Да и долго это. Нужно разобрать заголовок на составные части. Для первой строки это будет глагол, потом промежуток, в котором может быть и несколько пробелов, затем URI, затем промежуток, затем версия HTTP. В URI нужно отрезать query часть. Если у тебя в запросе будет /index.html?something=more, часть после вопроса нужно отрезать, если ты только файл отдаёшь.

memset(fpfile, 0, 64 * sizeof(char));

snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen(name_file) + 1, «%s%s»

Во-первых, sizeof — это размер в char'ах. То есть по его определению sizeof(char) всегда равен 1.

Во-вторых, у тебя явно fpfile — это буфер фиксированного размера. Наверное, даже, 64 байта. В этом случае ты указываешь этот размер два раза, один раз при объявлении fpfile, а второй раз при вызове memset. Если когда-то потом будешь менять, есть шанс, что ты забудешь поменять во втором месте. Нужно пользоваться sizeof: memset(fpfile, 0, sizeof(fpfile)); Это если fpfile — массив. Если выделяешь в куче, объяви где-нибудь размер константой или переменной и используй его.

В-третьих, в fpfile у тебя 64 байта, а в snprintf ты передаёшь (int)strlen(patch_to_dir) + (int)strlen(name_file) + 1, которое запросто может быть больше 64-х. Из-за этого данные могут быть записаны за границей буфера.

strstr(buffer, «png»)

Аналогично. Нужно проверять, что имя файла оканчивается на ".png", а не искать подстроку «png». Иначе код будет глючить на файлах типа «fpng1.jpeg».

read_in_file(«index.html»);

Даже если ты пишешь однопоточный сервер, нужно ввести понятие контекста, в котором сохраняются нужные для обработки запроса данные. Это понадобится, когда ты захочешь сделать сервер многопоточным, способным обрабатывать более одного подключения за раз.

i-rinat ★★★★★
()
Ответ на: комментарий от deep-purple

Получать майм тайп картинки для картинки из файловой системы

Какой странный совет. Откуда он в файловой системе? Не проще табличку по расширениям составить?

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Проще. Так и сделано практически везде. Согласен.

Но, если у тебя будет картинка без расширения в имени, то никак не получится, кроме как открывать и читать первые байты и сравнивать их значения с таблицей для сопоставления маймтайпа. Но это если уж совсем специфично и клиент будет требовать. Да и даже если по первым байтам мы определим что там картинка, то дальше легко может быть мусор.

Помню апач для файла с именем «foo.php.idjfhglkjhd» не найдя в своей таблице расширение «idjfhglkjhd» переходил к расширению «php» и запускал нужный хендлер. Это была дыра в безопасности.

Кстати надо будет попробовать вставить в стили картинку без расширения и посмотреть заголовки и реакцию браузеров.

deep-purple ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.