Я пытаюсь использовать простой HTTP-сервер, написанный на C, который использует UNIX BSD-сокеты. У меня это работает, но у меня есть следующие проблемы.
Сервер печатает только "привет мир!". в браузерах Linux, а не в браузерах Windows; т.е. я могу использовать Chrome/Firefox в Linux и видеть текст в браузере, но не на Windows-машине в тех же браузерах.
Вместо того, чтобы посылать "привет мир" со всеми заголовками ответов и закрывать соединение, как обычная веб-страница, я вижу, что когда я тестирую страницу сервера (localhost: xxxx), мне представляется текст и ответы HTTP в коде, но страница находится в непрерывной загрузке (вкладка продолжает вращаться). Затем, как только я выключу сервер, страница вернется "не удалось подключиться к серверу".
Мой вопрос, учитывая приведенный ниже код, почему это не отображает браузеры в Windows и почему он не закрывает соединение, оставив клиента с переданной информацией?
Это использует простые сокеты BSD.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> //definitions for system variables
#include <sys/socket.h> //definitions / structures for sockets
#include <netinet/in.h> //const and structures used by internet domain
void error(const char *msg) //takes litteral msg passed in and prints, then exits.
{
perror(msg); //man perror
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, // socket file descriptor 1. both return values from system
newsockfd, // socket file descriptor 2
portno; // port number to associate with this socket
socklen_t clilen; //stored address size of each client
char buffer[256]; //stores chars from socket buffer
struct sockaddr_in serv_addr, cli_addr;
/*sockaddr_in, structure containing an internet address. most of the magic DEFINED BELOW
struct sockaddr_in
{
short sin_family; /* must be AF_INET
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8]; /* Not used, must be zero
};
*/
int n; //stores return values of read() / write() system calls)
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
exit(1);
} //self explanitory
/*socket() creates a new socket and takes 3 arguments, the first being the address domain of the socket
* Sockets in this case can use two types of domains which are both system constants
* AF_UNIX - Used for parent child prc on the same machine
* AF_INET - Used for host to host communication, Ie internet communication
*
* The second option SOCKET_STREAM is the type of stream/communication used in the socket. ie TCP/UDP, Being
* SOCK_STREAM - TCP - A continuous stream/connection during the life span of the sockets communication
* SOCK_DGRAM - UDP - A connectionless strea/connection which uses less resources/is faster but possible byte loss
*
* The third option is the protocol used, due to specifying the type of stream this is almost always left to 0
* Leaving the arg at 0 implies that the operating system will make the best call and due to setting the stream it will
* more then likely choose to use the requested method
*
* This socket() in general returns a value for the subsequent descriptor for reference. If on failure, the socket()
* will return -1 and fail hence if(sockfd<0){}
*
* man socket() for information Andrew,
*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
/*bzero() sets all values in buffer to 0(00000000)*/
bzero(
(char *) &serv_addr,
sizeof(serv_addr)
); //Deref &serv_addr, and ensures to 0 out all bytes in buffer(entire struct)
portno = atoi(argv[1]);//port number to be used for communication,
serv_addr.sin_family = AF_INET; //set to internet. sin_family is stored in sockadr_in structure
serv_addr.sin_addr.s_addr = INADDR_ANY; //structure in structure. s_addr holds the host address. IN_ADDR is always the systems ip
serv_addr.sin_port = htons(portno); //convert to network byte order. Holds th eport number, places into readable format
/*
* checks and binds to new file descripter, socketaddr(deref) and allocates the size of the address bound to.
* If less then zero. it will fail(-1)
*/
if (bind(sockfd,
(struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
error("ERROR on binding");
listen(sockfd,5); // listens on bound socket/descriptor and allows up to 5 connections to be waiting in queue(max allowed on system
clilen = sizeof(cli_addr); // assigns value based on size of bytes in clients address
newsockfd = accept(sockfd, // new descripter to use when accepting a client
(struct sockaddr *) &cli_addr, //gathers client connection information for arg(deref)
&clilen); // Passes in clilen, for lenght of client address. not a copy, the real shiz
if (newsockfd < 0)
error("ERROR on accept"); // again, don't be a fucking dick.
bzero(buffer,256); // ensure buffer is not tampered at this point or gross
n = read(newsockfd,buffer,255); // use sys-call read() to store data in buffer from client. use 255bytes or client input
// which ever is less.
if (n < 0) error("ERROR reading from socket"); // if failed as fuck, let cry
n = write(newsockfd,"HTTP/1.0 200 OK\n",16); // Write back to client. to bound socket from client. they see this shit.
n = write(newsockfd,"Connection: Keep-alive\n",23);
n = write(newsockfd,"Connection: close\n",18);
n = write(newsockfd,"Content-type: text/html; charset=UTF-8\n",39);
n = write(newsockfd,"\n",1);
n = write(newsockfd,"<!DOCTYPE HTML>\n<html><head><title>Hello World!</title></head>\n",62);
n = write(newsockfd,"<body><h1>Hello World!</h1>\n",28);
n = write(newsockfd,"</body></html>\n",15);
if (n < 0) error("ERROR writing to socket"); // duh
// cleaning up...
//close(newsockfd);
// close(sockfd);
getchar();
return 0;
}
В коде есть много ошибок. Основная проблема, которую вы, вероятно, имеете в том, что вы не сообщаете клиенту, когда достигнут конец тела ответа, так что клиент не знает, что тело сделано, и поэтому попытается прочитать данные навсегда и не отобразить, что это уже есть. Обычные способы - указать Content-Length
, используя chunked
передачей пакетов (только HTTP/1. 1+) или закрытие сокета после завершения действия тела. См. RFC 2616 Раздел 4.4 "Длина сообщения" для получения подробной информации.
Помимо этого (некоторые из вещей, о которых уже говорилось в комментариях к EJP):
Connection
write()
не гарантирует запись всего буфераЯ получил коды ответов на основе запросов веб-сайта путем проверки информации заголовка
Пожалуйста, не выполняйте реализацию, просто просматривая несколько проб трафика, даже не понимая их должным образом. Там уже достаточно сломанных HTTP-серверов и достаточно клиентов, которые работают вокруг этих серверов, и все это вызывает массу проблем. Обычно эти серверы также проверяются только на нескольких выбранных клиентах, а затем проблема начинается после того, как такие серверы развернуты, а затем неожиданно обрываются с другими клиентами. Пожалуйста, прочитайте фактическую спецификацию HTTP и напишите свой сервер, чтобы соответствовать ей.
\r\n
, а не\n.