首页 > 代码库 > C语言实现php服务器

C语言实现php服务器

原理介绍

原创性申明:

本文地址是 http://blog.csdn.net/zhujunxxxxx/article/details/40658925 转载请注明出处。作者联系邮箱 zhujunxxxxx@163.com

HTTP协议的作用原理

连接:Web浏览器与Web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。
请求:Web浏览器通过socket向Web服务器提交请求。HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命令的格式为:
GET 路径/文件名 HTTP/1.0
文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。

应答:Web浏览器提交请求后,通过HTTP协议传送给Web服务器。Web服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面。



代码实现

其实Web服务器也就是一个一般的Socket的服务器,只不过它遵守HTTP协议来实现的罢了

//
// server.c
//
// David J. Malan
// malan@harvard.edu
//

// feature test macro requirements
#define _GNU_SOURCE
#define _XOPEN_SOURCE 700
#define _XOPEN_SOURCE_EXTENDED

// limits on an HTTP request's size, based on Apache's
// http://httpd.apache.org/docs/2.2/mod/core.html
#define LimitRequestFields 50
#define LimitRequestFieldSize 4094
#define LimitRequestLine 8190

// number of octets for buffered reads
#define OCTETS 512

// header files
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

// types
typedef char octet;

// prototypes
bool connected(void);
bool error(unsigned short code);
void handler(int signal);
ssize_t load(void);
const char* lookup(const char* extension);
ssize_t parse(void);
void reset(void);
void start(short port, const char* path);
void stop(void);

// server's root
char* root = NULL;

// file descriptor for sockets
int cfd = -1, sfd = -1;

// buffer for request
octet* request = NULL;

// FILE pointer for files
FILE* file = NULL;

// buffer for response-body
octet* body = NULL;

int main(int argc, char* argv[])
{
	// a global variable defined in errno.h that's "set by system
	// calls and some library functions [to a nonzero value]
	// in the event of an error to indicate what went wrong"
	errno = 0;

	// default to a random port
	int port = 0;

	// usage
	const char* usage = "Usage: server [-p port] /path/to/root";

	// parse command-line arguments
	int opt;
	while ((opt = getopt(argc, argv, "hp:")) != -1)
	{
		switch (opt)
		{
		// -h
		case 'h':
			printf("%s\n", usage);
			return 0;

			// -p port
		case 'p':
			port = atoi(optarg);
			break;
		}
	}

	// ensure port is a non-negative short and path to server's root is specified
	if (port < 0 || port > SHRT_MAX || argv[optind] == NULL || strlen(argv[optind]) == 0)
	{
		// announce usage
		printf("%s\n", usage);

		// return 2 just like bash's builtins
		return 2;
	}

	// start server
	start(port, argv[optind]);

	// listen for SIGINT (aka control-c)
	signal(SIGINT, handler);

	// accept connections one at a time
	while (true)
	{
		// reset server's state
		reset();

		// wait until client is connected
		if (connected())
		{
			// parse client's HTTP request
			ssize_t octets = parse();
			if (octets == -1)
			{
				continue;
			}

			// extract request's request-line
			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
			const char* haystack = request;
			char* needle = strstr(haystack, "\r\n");
			if (needle == NULL)
			{
				error(400);
				continue;
			}
			else if (needle - haystack + 2 > LimitRequestLine)
			{
				error(414);
				continue;
			}
			char line[needle - haystack + 2 + 1];
			strncpy(line, haystack, needle - haystack + 2);
			line[needle - haystack + 2] = '\0';

			// log request-line
			printf("\n%s", line);


			// TODO: validate request-line
			char* method = line;
			char* request_target;
			char* version;
			char* absolute_path;
			char* s1;

			s1 = strstr (line," ");
			if (s1 == NULL || (version = strstr(s1 + 1, " ")) == NULL || strncmp(line + sizeof(line) - 2, "\r\n", 2) == 0)
			{
				error(400);
				continue;
			}

			request_target = strdup(s1 + 1);
			request_target[version - s1 - 1] = '\0';

			//printf("request_target is %s, version is %s", request_target, version);

			if (strncmp(method,"GET",3) != 0)
			{
				error(405);
				continue;
			}
			if (strncmp(request_target, "/", 1) != 0)
			{
				error(501);
				continue;
			}
			if (strchr(request_target, '\"') != NULL)
			{
				error(400);
				continue;
			}
			if (strncmp (version + 1,"HTTP/1.1",8) != 0)
			{
				error(505);
				continue;
			}
			if (strchr(request_target, '.') == NULL)
			{
				error(501);
				continue;
			}

			// TODO: extract query from request-target
			char *query = strchr(request_target, '?');
			if (query == NULL)
				query = "";
			else
				query = query + 1;
			//printf("query is %s\n", query);

			// TODO: concatenate root and absolute-path
			absolute_path = strdup(request_target);
//			printf("%s. %d\n", absolute_path, query-request_target);
			if (strcmp(query, "") != 0)
				absolute_path[query - request_target - 1] = '\0';
			//printf("absolute path is %s\n", absolute_path);

			char *path = (char*)malloc(1000);
			strcpy(path, root);
//			strcat(path, "/");
			strcat(path, absolute_path);
			//printf("path is %s\n", path);

			// TODO: ensure path exists
			if (access(path, F_OK) == -1) {
				error(404);
				continue;
			}

			// TODO: ensure path is readable
			if (access(path, R_OK) == -1) {
				error(403);
				continue;
			}
			// TODO: extract path's extension
			char* extension;
			if ((extension = strrchr(absolute_path, '.')) == NULL) {
				error(501);
				continue;
			}
			extension = extension + 1;
			//printf("extension is %s\n", extension);


			// dynamic content
			if (strcasecmp("php", extension) == 0)
			{
				// open pipe to PHP interpreter
				char* format = "QUERY_STRING=\"%s\" REDIRECT_STATUS=200 SCRIPT_FILENAME=\"%s\" php-cgi";
				char command[strlen(format) + (strlen(path) - 2) + (strlen(query) - 2) + 1];
				sprintf(command, format, query, path);
				file = popen(command, "r");
				if (file == NULL)
				{
					error(500);
					continue;
				}

				// load file
				ssize_t size = load();
				if (size == -1)
				{
					error(500);
					continue;
				}

				// subtract php-cgi's headers from body's size to get content's length
				haystack = body;
				needle = memmem(haystack, size, "\r\n\r\n", 4);
				if (needle == NULL)
				{
					error(500);
					continue;
				}
				size_t length = size - (needle - haystack + 4);

				// respond to client
				if (dprintf(cfd, "HTTP/1.1 200 OK\r\n") < 0)
				{
					continue;
				}
				if (dprintf(cfd, "Connection: close\r\n") < 0)
				{
					continue;
				}
				if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
				{
					continue;
				}
				if (write(cfd, body, size) == -1)
				{
					continue;
				}
			}

			// static content
			else
			{
				// look up file's MIME type
				const char* type = lookup(extension);
				if (type == NULL)
				{
					error(501);
					continue;
				}

				// open file
				file = fopen(path, "r");
				if (file == NULL)
				{
					error(500);
					continue;
				}

				// load file
				ssize_t length = load();
				if (length == -1)
				{
					error(500);
					continue;
				}

				// TODO: respond to client
				// respond with Status-Line
				dprintf(cfd, "HTTP/1.1 200 OK\r\n");

				// respond with Connection header
				dprintf(cfd, "Connection: close\r\n");

				// respond with Content-Length header
				dprintf(cfd, "Content-Length: %i\r\n", length);

				// respond with Content-Type header
				dprintf(cfd, "Content-Type: text/html\r\n");

				// respond with CRLF
				dprintf(cfd, "\r\n");

				
				write(cfd, body, length);
			}

			// announce OK
			printf("\033[32m");
			printf("HTTP/1.1 200 OK");
			printf("\033[39m\n");
		}
	}
}

/**
 * Accepts a connection from a client, blocking (i.e., waiting) until one is heard.
 * Upon success, returns true; upon failure, returns false.
 */
bool connected(void)
{
	struct sockaddr_in cli_addr;
	memset(&cli_addr, 0, sizeof(cli_addr));
	socklen_t cli_len = sizeof(cli_addr);
	cfd = accept(sfd, (struct sockaddr*) &cli_addr, &cli_len);
	if (cfd == -1)
	{
		return false;
	}
	return true;
}

/**
 * Handles client errors (4xx) and server errors (5xx).
 */
bool error(unsigned short code)
{
	// ensure client's socket is open
	if (cfd == -1)
	{
		return false;
	}

	// ensure code is within range
	if (code < 400 || code > 599)
	{
		return false;
	}

	// determine Status-Line's phrase
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
	const char* phrase = NULL;
	switch (code)
	{
	case 400: phrase = "Bad Request"; break;
	case 403: phrase = "Forbidden"; break;
	case 404: phrase = "Not Found"; break;
	case 405: phrase = "Method Not Allowed"; break;
	case 413: phrase = "Request Entity Too Large"; break;
	case 414: phrase = "Request-URI Too Long"; break;
	case 418: phrase = "I'm a teapot"; break;
	case 500: phrase = "Internal Server Error"; break;
	case 501: phrase = "Not Implemented"; break;
	case 505: phrase = "HTTP Version Not Supported"; break;
	}
	if (phrase == NULL)
	{
		return false;
	}

	// template
	char* template = "<html><head><title>%i %s</title></head><body><h1>%i %s</h1></body></html>";
	char content[strlen(template) + 2 * ((int) log10(code) + 1 - 2) + 2 * (strlen(phrase) - 2) + 1];
	int length = sprintf(content, template, code, phrase, code, phrase);

	// respond with Status-Line
	if (dprintf(cfd, "HTTP/1.1 %i %s\r\n", code, phrase) < 0)
	{
		return false;
	}

	// respond with Connection header
	if (dprintf(cfd, "Connection: close\r\n") < 0)
	{
		return false;
	}

	// respond with Content-Length header
	if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0)
	{
		return false;
	}

	// respond with Content-Type header
	if (dprintf(cfd, "Content-Type: text/html\r\n") < 0)
	{
		return false;
	}

	// respond with CRLF
	if (dprintf(cfd, "\r\n") < 0)
	{
		return false;
	}

	// respond with message-body
	if (write(cfd, content, length) == -1)
	{
		return false;
	}

	// announce Response-Line
	printf("\033[31m");
	printf("HTTP/1.1 %i %s", code, phrase);
	printf("\033[39m\n");

	return true;
}

/**
 * Loads file into message-body.
 */
ssize_t load(void)
{
	// ensure file is open
	if (file == NULL)
	{
		return -1;
	}

	// ensure body isn't already loaded
	if (body != NULL)
	{
		return -1;
	}

	// buffer for octets
	octet buffer[OCTETS];

	// read file
	ssize_t size = 0;
	while (true)
	{
		// try to read a buffer's worth of octets
		ssize_t octets = fread(buffer, sizeof(octet), OCTETS, file);

		// check for error
		if (ferror(file) != 0)
		{
			if (body != NULL)
			{
				free(body);
				body = NULL;
			}
			return -1;
		}

		// if octets were read, append to body
		if (octets > 0)
		{
			body = realloc(body, size + octets);
			if (body == NULL)
			{
				return -1;
			}
			memcpy(body + size, buffer, octets);
			size += octets;
		}

		// check for EOF
		if (feof(file) != 0)
		{
			break;
		}
	}
	return size;
}

/**
 * Handles signals.
 */
void handler(int signal)
{
	// control-c
	if (signal == SIGINT)
	{
		// ensure this isn't considered an error
		// (as might otherwise happen after a recent 404)
		errno = 0;

		// announce stop
		printf("\033[33m");
		printf("Stopping server\n");
		printf("\033[39m");

		// stop server
		stop();
	}
}

/**
 * Returns MIME type for supported extensions, else NULL.
 */
const char* lookup(const char* extension)
{
	// TODO
	if (strcasecmp(extension, "css"))
			return "text/css";
	if (strcasecmp(extension, "html"))
			return "text/html";
	if (strcasecmp(extension, "gif"))
			return "image/gif";
	if (strcasecmp(extension, "ico"))
			return "image/x-icon";
	if (strcasecmp(extension, "jpg"))
			return "image/jpeg";
	if (strcasecmp(extension, "js"))
			return "text/javascript";
	if (strcasecmp(extension, "png"))
			return "image/png";
	return NULL;

}

/**
 * Parses an HTTP request.
 */
ssize_t parse(void)
{
	// ensure client's socket is open
	if (cfd == -1)
	{
		return -1;
	}

	// ensure request isn't already parsed
	if (request != NULL)
	{
		return -1;
	}

	// buffer for octets
	octet buffer[OCTETS];

	// parse request
	ssize_t length = 0;
	while (true)
	{
		// read from socket
		ssize_t octets = read(cfd, buffer, sizeof(octet) * OCTETS);
		if (octets == -1)
		{
			error(500);
			return -1;
		}

		// if octets have been read, remember new length
		if (octets > 0)
		{
			request = realloc(request, length + octets);
			if (request == NULL)
			{
				return -1;
			}
			memcpy(request + length, buffer, octets);
			length += octets;
		}

		// else if nothing's been read, socket's been closed
		else
		{
			return -1;
		}

		// search for CRLF CRLF
		int offset = (length - octets < 3) ? length - octets : 3;
		char* haystack = request + length - octets - offset;
		char* needle = memmem(haystack, request + length - haystack, "\r\n\r\n", 4);
		if (needle != NULL)
		{
			// trim to one CRLF and null-terminate
			length = needle - request + 2 + 1;
			request = realloc(request, length);
			if (request == NULL)
			{
				return -1;
			}
			request[length - 1] = '\0';
			break;
		}

		// if buffer's full and we still haven't found CRLF CRLF,
		// then request is too large
		if (length - 1 >= LimitRequestLine + LimitRequestFields * LimitRequestFieldSize)
		{
			error(413);
			return -1;
		}
	}
	return length;
}

/**
 * Resets server's state, deallocating any resources.
 */
void reset(void)
{
	// free response's body
	if (body != NULL)
	{
		free(body);
		body = NULL;
	}

	// close file
	if (file != NULL)
	{
		fclose(file);
		file = NULL;
	}

	// free request
	if (request != NULL)
	{
		free(request);
		request = NULL;
	}

	// close client's socket
	if (cfd != -1)
	{
		close(cfd);
		cfd = -1;
	}
}

/**
 * Starts server.
 */
void start(short port, const char* path)
{
	// path to server's root
	root = realpath(path, NULL);
	if (root == NULL)
	{
		stop();
	}

	// ensure root exists
	if (access(root, F_OK) == -1)
	{
		stop();
	}

	// ensure root is executable
	if (access(root, X_OK) == -1)
	{
		stop();
	}

	// announce root
	printf("\033[33m");
	printf("Using %s for server's root, size is %ld", root, sizeof(root));
	printf("\033[39m\n");

	// create a socket
	sfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sfd == -1)
	{
		stop();
	}

	// allow reuse of address (to avoid "Address already in use")
	int optval = 1;
	setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

	// assign name to socket
	struct sockaddr_in serv_addr;
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(port);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(sfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
	{
		stop();
	}

	// listen for connections
	if (listen(sfd, SOMAXCONN) == -1)
	{
		stop();
	}

	// announce port in use
	struct sockaddr_in addr;
	socklen_t addrlen = sizeof(addr);
	if (getsockname(sfd, (struct sockaddr*) &addr, &addrlen) == -1)
	{
		stop();
	}
	printf("\033[33m");
	printf("Listening on port %i", ntohs(addr.sin_port));
	printf("\033[39m\n");
}

/**
 * Stop server, deallocating any resources.
 */
void stop(void)
{
	// preserve errno across this function's library calls
	int errsv = errno;

	// reset server's state
	reset();

	// free root, which was allocated by realpath
	if (root != NULL)
	{
		free(root);
	}

	// close server socket
	if (sfd != -1)
	{
		close(sfd);
	}

	// terminate process
	if (errsv == 0)
	{
		// success
		exit(0);
	}
	else
	{
		// announce error
		printf("\033[33m");
		printf("%s", strerror(errsv));
		printf("\033[39m\n");

		// failure
		exit(1);
	}
}


编译方法
gcc -o server server.c -lm

运行方法

./server -p 8080 public 

其中public目录是存放html和php文件的





C语言实现php服务器