我有一个用C编写的简单Web服务器,当向浏览器提供带有一些图像的小型HTML文件时,该服务器可以正常工作。当我尝试为更复杂的网站提供更多具有不同内容类型的对象(如css和js文件)的网站时,我发现我没有收到正确加载index.html所需的许多对象的请求-浏览器一直在等待无限期地为主机服务。如果我刷新页面两次,最终所有内容都可以正确加载,并且可以跟踪超链接。我注意到的另一件事是,通常它们是相同的文件,不会发送回浏览器。
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdbool.h>
#include <pthread.h>
bool writeDataToClient(int sckt, const void *data, int datalen)
{
const char *pdata = (const char*) data;
while (datalen > 0){
int numSent = send(sckt, pdata, datalen, 0);
if (numSent <= 0){
if (numSent == 0){
printf("The client was not written to: disconnected\n");
} else {
perror("The client was not written to");
}
return false;
}
pdata += numSent;
datalen -= numSent;
}
return true;
}
bool writeStrToClient(int sckt, const char *str)
{
return writeDataToClient(sckt, str, strlen(str));
}
int get_filename_and_method(char *str, char **buf1, char **buf2)
{
char *request = str;
char *status_line;
char *url;
char *token = strtok(request, "\r\n");
status_line = token;
*buf1 = strtok(status_line, " ");
if (strcasecmp(*buf1, "GET") != 0) return -1;
url = strtok(NULL, " ");
if (strncmp(url, "/", strlen("/")) != 0) return -1;
if (strlen(url) == 1) strcat(url, "index.html");
if (url[strlen(url) - 1] == '/') strcat(url, "index.html");
char *tmp = strdup(url);
strcpy(url, "web");
strcat(url, tmp);
*buf2 = url;
free(tmp);
return 0;
}
int get_connection_type(char *str, char **buf)
{
char *req = str;
char *token = strtok(req, "\r\n");
char *connection;
while (token != NULL)
{
if (strncmp(token, "Connection:", 11) == 0)
{
connection = token;
strtok(connection, " ");
if (strcasecmp(strtok(NULL, " "), "Keep-Alive") == 0)
{
*buf = "Connection: keep-alive\r\n\r\n";
return 0;
}
}
token = strtok(NULL, "\r\n");
}
*buf = "Connection: close\r\n\r\n";
return 0;
}
void *connection_handler (void *sockfd)
{
// Connection handler
int sock = *(int*)sockfd;
char *buffer, *method, *filename, *connection_type, *content_type;
int bufsize = 2048;
const char *HTTP_404_CONTENT = "<html><head><title>404 Not "
"Found</title></head><body><h1>404 Not Found</h1>The requested "
"resource could not be found but may be available again in the "
"future."</body></html>";
const char *HTTP_501_CONTENT = "<html><head><title>501 Not "
"Implemented</title></head><body><h1>501 Not Implemented</h1>The "
"server either does not recognise the request method, or it lacks "
"the ability to fulfill the request.</body></html>";
buffer = (char*) malloc(bufsize);
if (!buffer){
printf("The receive buffer was not allocated\n");
exit(1);
}
while (1)
{
int numRead = recv(sock, buffer, bufsize, 0);
if (numRead < 1){
if (numRead == 0){
printf("The client was not read from: disconnected\n");
break;
} else {
perror("The client was not read from");
break;
}
close(sock);
continue;
}
printf("%.*s\n", numRead, buffer);
// Extract info from request header
get_connection_type(buffer, &connection_type);
if (get_filename_and_method(buffer, &method, &filename) == -1)
{
char clen[40];
writeStrToClient(sock, "HTTP/1.1 501 Not Implemented\r\n");
sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_501_CONTENT));
writeStrToClient(sock, clen);
writeStrToClient(sock, "Content-Type: text/html\r\n");
writeStrToClient(sock, connection_type);
writeStrToClient(sock, HTTP_501_CONTENT);
}
else
{
// Open and read file
long fsize;
FILE *fp = fopen(filename, "rb");
if (!fp){
perror("The file was not opened");
char clen[40];
writeStrToClient(sock, "HTTP/1.1 404 Not Found\r\n");
sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_404_CONTENT));
writeStrToClient(sock, clen);
writeStrToClient(sock, "Content-Type: text/html\r\n");
writeStrToClient(sock, connection_type);
writeStrToClient(sock, HTTP_404_CONTENT);
if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
break;
continue;
}
printf("The file was opened\n");
if (fseek(fp, 0, SEEK_END) == -1){
perror("The file was not seeked");
exit(1);
}
fsize = ftell(fp);
if (fsize == -1) {
perror("The file size was not retrieved");
exit(1);
}
rewind(fp);
char *msg = (char*) malloc(fsize);
if (!msg){
perror("The file buffer was not allocated\n");
exit(1);
}
if (fread(msg, fsize, 1, fp) != 1){
perror("The file was not read\n");
exit(1);
}
fclose(fp);
// Get extension of filename
char *ext = strrchr(filename, '.');
if (ext != NULL)
ext++;
if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0)
content_type = "Content-Type: text/html\r\n";
else if (strcmp(ext, "css") == 0)
content_type = "Content-Type: text/css\r\n";
else if (strcmp(ext, "jpg") == 0)
content_type = "Content-Type: image/jpeg\r\n";
else if (strcmp(ext, "png") == 0)
content_type = "Content-Type: image/png\r\n";
else if (strcmp(ext, "gif") == 0)
content_type = "Content-Type: image/gif\r\n";
else
content_type = "Content-Type: text/plain\r\n";
if (!writeStrToClient(sock, "HTTP/1.1 200 OK\r\n")){
close(sock);
continue;
}
char clen[40];
sprintf(clen, "Content-length: %ld\r\n", fsize);
if (!writeStrToClient(sock, clen)){
printf("Cannot write content length\n");
close(sock);
continue;
}
if (!writeStrToClient(sock, content_type)){
close(sock);
continue;
}
if (!writeStrToClient(sock, connection_type) == -1){
close(sock);
continue;
}
if (!writeDataToClient(sock, msg, fsize)){
close(sock);
continue;
}
printf("The file was sent successfully\n");
}
if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
break;
}
close(sock);
pthread_exit(0);
}
int main(int argc, char *argv[]){
int create_socket, new_socket;
struct sockaddr_in address;
socklen_t addrlen;
char *ptr;
if (argc != 2)
{
printf("Usage: %s <port number>\n", argv[0]);
exit(0);
}
create_socket = socket(AF_INET, SOCK_STREAM, 0);
if (create_socket == -1){
perror("The socket was not created");
exit(1);
}
printf("The socket was created\n");
const unsigned short port = (unsigned short) strtol(argv[1], &ptr, 10);
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == -1){
printf("The socket was not bound because that port is not available\n");
exit(1);
}
printf("The socket is bound\n");
if (listen(create_socket, 10) == -1){
perror("The socket was not opened for listening");
exit(1);
}
printf("The socket is listening\n");
while (1) {
addrlen = sizeof(address);
pthread_t tid;
new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen);
if (new_socket == -1) {
perror("A client was not accepted");
exit(1);
}
printf("A client is connected from %s:%hu...\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
if (pthread_create(&tid, NULL, connection_handler, (void *)&new_socket) < 0)
{
perror("Could not create thread");
return 1;
}
pthread_join(tid, NULL);
}
if (new_socket < 0)
{
perror("accept failed");
return 1;
}
close(create_socket);
printf("Socket was closed\n");
return 0;
}
此外,关闭浏览器(与服务器断开连接)会导致另一个连接被接受,该连接将发送浏览器请求但未收到的第一个文件,然后服务器程序结束而没有任何错误消息。
更新:删除pthread_join允许页面正确加载。正如用户提到的那样,浏览器并行执行几个连接,所以我认为正在发生的是所有请求都通过多个连接发送(从我程序的输出看,到服务器有5个连接)。当pthread_join等待一个线程(连接)完成时,一次只处理一个连接,这就是为什么我没有收到所有请求的原因。
答案 0 :(得分:0)
HTTP可能比您想象的还要复杂。您是否已完整阅读其规范(对于HTTP 1.1为RFC 7230)或{ {3}}关于HTTP?您是否考虑过使用某些HTTP服务器库,例如some book或libonion(或libhttp或其他)?这些库的大小说明了HTTP的复杂性!这些库是libmicrohttpd,因此您可以研究它们的源代码并从中获得启发。 (如果您诚实地告诉他,您学习过libonion
的源代码并已阅读free software,您的老师应该会感到高兴。)
顺便说一句,现代浏览器(最近的Firefox或Chrome等)往往会同时使用几个连接来显示一个页面。现代浏览器能够向您显示实际的HTTP流量和网络协议。
我的建议是使用一些现有的库。我很高兴使用libonion
,即使它有一些限制。
最后,阅读RFC 7230 。启用所有警告和调试信息(因此,请使用gcc -Wall -Wextra -g
和 recent how to debug small programs进行编译,例如2018年底的GCC)。了解如何GCC 8(并在2018年底使用最新的debug with GDB)。还要使用GDB 8.2,也可以使用valgrind。
我有一个用C语言编写的简单Web服务器
这是一个矛盾。 Web服务器要么不能简单,要么不能实现所有HTTP。
您使用sprintf
是危险的(clang-analyzer有风险)。我强烈建议改用buffer overflow。而且您的404处理看起来真的很糟糕。
另一个问题(我不知道它的答案)是,如果在您的特定情况下,对于某些特定的浏览器客户端,您是否可以针对特定情况实现一小部分HTTP(对于您的浏览器和客户端来说足够了)。