'Writing an image to a socket in C
I made a server that can serve html to a browser, but for some reason it wont send images. Every time I send an image over the socket, the browser receives some of the image, like the headers specifying the format, but the body of the image is all /00/00/00 just null characters.
Here is the code: (I just changed the response header from content-type: text/html to image/gif, so ignore the name of the variable, I know it is wrong.)
/*Refer to documentation for questions and functionality, as well as limitations*/
/*I have given each section of code an index in the form of a comment, which you can refer to in the documentation*/
#include <arpa/inet.h> /* htons() htonl() */
#include <unistd.h> /* read() write() */
#include <stdlib.h> /* exit() */
#include <string.h> /* strlen() strcat() */
#include <stdio.h> /* FILE scanf() printf() */
int main(void)
{
/*startof[a]*/
char filename[256];
FILE *file_pointer;
unsigned int file_size;
unsigned short int port_number;
struct sockaddr_in listening_socket_address;
int socket_address_size;
int listening_socketfd;
int new_socketfd;
struct sockaddr_in new_socket_address;
char HTTP_REQUEST[8192];
char file_buffer[2097152];
char HTTP_TEXT_RESPONSE_HEADER[] = "HTTP/1.0 200 OK\nContent-Type: image/*\n\n";
char HTTP_RESPONSE[2097152];
int response_size;
/*endof[a]*/
/*startof[b]*/
printf("Please enter the name of the file (text formats only) to be transmitted today, you can give a whole path, or just the name if it is in the same directory as this program :)\nFilename: ");
scanf("%255s", filename);
file_pointer = fopen(filename, "r");
fseek(file_pointer, 0L, SEEK_END);
file_size = ftell(file_pointer);
printf("File: %s is Size: %d Bytes\n", filename, file_size);
rewind(file_pointer);
printf("File: %s has been rewinded to position: 0\n\n", filename);
/*endof[b]*/
/*startof[c]*/
printf("Enter port number: ");
scanf("%hu", &port_number);
listening_socket_address.sin_family = AF_INET;
listening_socket_address.sin_port = htons(port_number);
listening_socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
socket_address_size = sizeof(listening_socket_address);
/*endof[c]*/
/*startof[d]*/
listening_socketfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listening_socketfd, (struct sockaddr *) &listening_socket_address, socket_address_size);
listen(listening_socketfd, 1);
new_socketfd = accept(listening_socketfd, (struct sockaddr *) &new_socket_address, &socket_address_size);
/*endof[d]*/
/*startof[e]*/
read(new_socketfd, HTTP_REQUEST, 8192);
printf("\nHere is the HTTP Request:\n\n%s", HTTP_REQUEST);
/*endof[e]*/
/*startof[f]*/
fread(file_buffer, 1, file_size, file_pointer);
strcat(HTTP_RESPONSE, HTTP_TEXT_RESPONSE_HEADER);
strncat(HTTP_RESPONSE, file_buffer, file_size);
response_size = sizeof(HTTP_TEXT_RESPONSE_HEADER) + file_size;
write(new_socketfd, (char*)HTTP_RESPONSE, response_size);
close(new_socketfd);
printf("Here is the HTTP response:\n\n%s", HTTP_RESPONSE);
/*endof[f]*/
return 0;
}
The image I am using is a gif89a, and the browser is able to tell it is a gif89a, but all the actual data inside the gif is just null characters.
I am thinking that perhaps because the image is in binary, it is being smashed by some weird formatting/ byte order mishap that the browser(firefox) is using, an I need to turn it into base64 for portability reasons.
Solution 1:[1]
There are a lot of things wrong with this code, but most importantly, you are simply not sending a valid HTTP response:
You must use
\r\nfor line breaks.image/*is not a valid media type for a single file, use a more appropriate media type, likeimage/gif, etc.You are missing a
Content-Lengthheader to specify the image's size, since you are not using HTTP'schunkedtransfer encoding.Do not use
sizeof(HTTP_TEXT_RESPONSE_HEADER), which includes the null-terminator. Usestrlen()instead.Don't use
strncat()to append binary data to achar[]array. That will break on the first0x00byte encountered.Open image files in binary mode.
Don't try to
write()the entire HTTP response in a single send. That simply will not work. The kernel buffer may not be large enough to send it all. The receiver's kernel buffer may not be large enough to receive it all. You need to pay attention to the return value ofwrite(), which will tell you how many bytes were actually accepted by the kernel for sending. Callwrite()in a loop until all of the bytes have been accepted.
With that said, try something more like this:
/*Refer to documentation for questions and functionality, as well as limitations*/
/*I have given each section of code an index in the form of a comment, which you can refer to in the documentation*/
#include <arpa/inet.h> /* htons() htonl() */
#include <unistd.h> /* read() write() */
#include <stdlib.h> /* exit() */
#include <string.h> /* strlen() strcat() */
#include <stdio.h> /* FILE scanf() printf() */
int write_all(int fd, const void *data, size_t data_size)
{
char *ptr = (const char*) data;
ssize_t written;
while (data_size > 0)
{
written = write(fd, ptr, data_size);
if (written < 0) return -1;
ptr += written;
data_size -= written;
}
return 0;
}
int main(void)
{
/*startof[a]*/
char filename[256];
FILE *file_pointer;
unsigned int file_size;
unsigned short int port_number;
struct sockaddr_in listening_socket_address;
int socket_address_size;
int listening_socketfd;
int new_socketfd;
struct sockaddr_in new_socket_address;
char HTTP_REQUEST[8192];
char HTTP_RESPONSE_HEADER[128];
char HTTP_RESPONSE_HEADER_FMT[] = "HTTP/1.0 200 OK\r\nContent-Type: %s\r\nContent-Length: %u\r\nConnection: close\r\n\r\n";
char file_buffer[1024];
char media_type[] = "image/gif";
int request_size;
response_size;
/*endof[a]*/
/*startof[b]*/
printf("Please enter the name of the file (text formats only) to be transmitted today, you can give a whole path, or just the name if it is in the same directory as this program :)\nFilename: ");
scanf("%255s", filename);
file_pointer = fopen(filename, "rb");
if (!file_pointer) {
// display error message...
return -1;
}
fseek(file_pointer, 0L, SEEK_END);
file_size = ftell(file_pointer);
printf("File: %s is Size: %u Bytes\n", filename, file_size);
rewind(file_pointer);
printf("File: %s has been rewinded to position: 0\n\n", filename);
// TODO: set media_type to appropriate type
// based on what kind of file is being sent...
/*endof[b]*/
/*startof[c]*/
printf("Enter port number: ");
scanf("%hu", &port_number);
memset(&listening_socket_address, 0, sizeof(listening_socket_address));
listening_socket_address.sin_family = AF_INET;
listening_socket_address.sin_port = htons(port_number);
listening_socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
/*endof[c]*/
/*startof[d]*/
listening_socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (listening_socketfd < 0) {
// display error message...
return -1;
}
if (bind(listening_socketfd, (struct sockaddr *) &listening_socket_address, sizeof(listening_socket_address)) < 0) {
// display error message...
return -1;
}
if (listen(listening_socketfd, 1) < 0) {
// display error message...
return -1;
}
socket_address_size = sizeof(new_socket_address);
new_socketfd = accept(listening_socketfd, (struct sockaddr *) &new_socket_address, &socket_address_size);
if (new_socketfd < 0) {
// display error message...
return -1;
}
/*endof[d]*/
/*startof[e]*/
// TODO: read client's request per HTTP protocol...
request_size = read(new_socketfd, HTTP_REQUEST, 8192);
if (request_size < 0) {
// display error message...
return -1;
}
printf("\nHere is the HTTP Request:\n\n%.*s", request_size, HTTP_REQUEST);
/*endof[e]*/
/*startof[f]*/
response_size = sprintf(HTTP_RESPONSE_HEADER, HTTP_RESPONSE_HEADER_FMT, media_type, file_size);
printf("Here is the HTTP response:\n\n%s", HTTP_RESPONSE_HEADER);
if (write_all(new_socketfd, HTTP_RESPONSE_HEADER, response_size) < 0) {
return -1;
}
while (file_size > 0) {
response_size = fread(file_buffer, 1, min(file_size, sizeof(file_buffer)), file_pointer);
if (response_size < 1) break;
if (write_all(new_socketfd, file_buffer, response_size) < 0) break;
file_size -= response_size;
}
close(new_socketfd);
/*endof[f]*/
close(listening_socketfd);
return 0;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
