一个精简服务器
一个Web服务器并没有必要比我们前面章节中创建的简单服务器复杂。接受TCP/IP连接之后,Web服务器需要使用HTTP协议实现更深层的通信。
除了将处理连接的代码分隔到它自己的函数之外,下面列出的服务器代码几乎与简单服务器相同。这个函数处理来自网络浏览器的HTTP GET和HEAD请求。程序会在本地目录webroot中查找浏览器请求的资源并将它发送给浏览器。如果不能找到文件,服务器会以404HTTP作为回应。您可能早已熟悉了这个响应,它的意思是文件没有找到(File Not Found)。完整的源代码如下所示。
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"hacking.h"
#include"hacking-network.h"
#define PORT80 // the port users will be connectingto
#defineWEBROOT "./webroot" // the web server's root directory
voidhandle_connection(int, struct sockaddr_in *); // handle web requests
intget_file_size(int); // returns the filesize of open file descriptor
int main(void){
int sockfd, new_sockfd, yes=1;
struct sockaddr_in host_addr,client_addr; // my address information
socklen_t sin_size;
printf("Accepting web requests on port%d\n", PORT);
if ((sockfd = socket(PF_INET, SOCK_STREAM,0)) == -1)
fatal("in socket");
if (setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, &yes, sizeof(int)) == -1)
fatal("setting socket optionSO_REUSEADDR");
host_addr.sin_family = AF_INET; // host byte order
host_addr.sin_port = htons(PORT); // short, network byte order
host_addr.sin_addr.s_addr = INADDR_ANY; //automatically fill with my IP
memset(&(host_addr.sin_zero), '\0', 8);// zero the rest of the struct
if (bind(sockfd, (struct sockaddr*)&host_addr, sizeof(struct sockaddr)) == -1)
fatal("binding to socket");
if (listen(sockfd, 20) == -1)
fatal("listening on socket");
while(1) { // Accept loop
sin_size = sizeof(struct sockaddr_in);
new_sockfd = accept(sockfd, (structsockaddr *)&client_addr, &sin_size);
if(new_sockfd == -1)
fatal("acceptingconnection");
handle_connection(new_sockfd,&client_addr);
}
return 0;
}
/* Thisfunction handles the connection on the passed socket from the
* passed client address. The connection is processed as a web request
* and this function replies over the connectedsocket. Finally, the
* passed socket is closed at the end of thefunction.
*/
/*该函数处理所传递的套接字上的链接,该套接字来自于所传递的客户端地址。链接作为网络请求进行处理,并且该函数通过已连接的套接字进行回复。最后,在函数的末尾关闭所传递的套接字*/
voidhandle_connection(int sockfd, struct sockaddr_in *client_addr_ptr) {
unsigned char *ptr, request[500],resource[500];
int fd, length;
length = recv_line(sockfd, request);
printf("Got request from %s:%d\"%s\"\n", inet_ntoa(client_addr_ptr->sin_addr),ntohs(client_addr_ptr->sin_port), request);
ptr = strstr(request, " HTTP/");// search for valid looking request
if(ptr == NULL) { // then this isn't validHTTP
printf(" NOT HTTP!\n");
} else {
*ptr = 0; // terminate the buffer at theend of the URL
ptr = NULL; // set ptr to NULL (used toflag for an invalid request)
if(strncmp(request, "GET ", 4)== 0) // get request
ptr = request+4; // ptr is the URL
if(strncmp(request, "HEAD ", 5)== 0) // head request
ptr = request+5; // ptr is the URL
if(ptr == NULL) { // then this is not arecognized request
printf("\tUNKNOWNREQUEST!\n");
} else { // valid request, with ptrpointing to the resource name
if (ptr[strlen(ptr) - 1] == '/') // for resources ending with '/'
strcat(ptr,"index.html"); // add'index.html' to the end
strcpy(resource, WEBROOT); // begin resource with web root path
strcat(resource, ptr); // and join it with resource path
fd = open(resource, O_RDONLY, 0); //try to open the file
printf("\tOpening \'%s\'\t",resource);
if(fd == -1) { // if file is not found
printf(" 404 NotFound\n");
send_string(sockfd, "HTTP/1.0404 NOT FOUND\r\n");
send_string(sockfd, "Server:Tiny webserver\r\n\r\n");
send_string(sockfd,"<html><head><title>404 Not Found</title></head>");
send_string(sockfd,"<body><h1>URL notfound</h1></body></html>\r\n");
} else { // otherwise, serve up the file
printf(" 200 OK\n");
send_string(sockfd, "HTTP/1.0200 OK\r\n");
send_string(sockfd, "Server:Tiny webserver\r\n\r\n");
if(ptr == request + 4) { // thenthis is a GET request
if( (length = get_file_size(fd))== -1)
fatal("getting resourcefile size");
if( (ptr = (unsigned char *)malloc(length)) == NULL)
fatal("allocating memoryfor reading resource");
read(fd, ptr, length); // readthe file into memory
send(sockfd, ptr, length,0); // send it to socket
free(ptr); // free file memory
}
close(fd); // close the file
} // end if block for file found/notfound
} // end if block for valid request
} // end if block for valid HTTP
shutdown(sockfd, SHUT_RDWR); // close thesocket gracefully
}
/* Thisfunction accepts an open file descriptor and returns
* the size of the associated file. Returns -1 on failure.
*/
intget_file_size(int fd) {
struct stat stat_struct;
if(fstat(fd, &stat_struct) == -1)
return -1;
return (int) stat_struct.st_size;
}
函数handle_connection使用strstr()函数在请求缓存中查找子字符串“HTTP/”。函数strstr()返回一个指向子字符串的指针,这个子串正好在请求报文的末尾。字符串在这里终止,并且请求的HEAD和GET被看作可处理的请求。HEAD请求会返回报头,GET请求还会返回请求的资源(如果能够找到)。
下面的代码已经在目录webroot中放置了文件index.html和index.jpg,然后编译了程序tinyweb。绑定小于1024的任意一个端口时,都需要root权限,所以程序是setuidroot并且可以执行。服务器的调试输出显是示了—个Web浏览器对http://127.0.0.1请求结果。
[root@localhost ~]# ls -l /tmp/webroot/
total 20
-rw-r--r-- 1 root root 1406 Feb 6 06:40 favicon.ico
-rw-r--r-- 1 root root 251 Feb 6 06:35 index.html
-rw-r--r-- 1 root root 12046 Feb 6 05:32 index.jpg
[root@localhost ~]# cat /tmp/webroot/index.html
<html>
<head><title>A samlp webpage</head>
<body bgcolor="#000000"text="#ffffff">
<center>
<h1>This is a sample webpage</h1>
...and here is some sample text<br>
<br>
.. and even a samle image:<br>
<img src="index.jpg"><br>
</center>
</body>
</html>
地址127.0.0.1是一个特殊的回环地址,它将路径导向本地计算机。初始请求从Web服务器获取index.html,该文件随后又请求image.jpg。此外,浏览器自动请求favicon.ico以尝试为网页获取一个图标。下图显示了浏览器中这个请求的结果。
下一篇文章介绍网络底层的知识。