在这个简易版的Web服务器中,实现了GET静态数据,以及动态数据。

------------------------------------------------------------------------------------------------

2017.04.29更新

增加POST方法,修改线程池,将任务队列的list 改成queue,改进线程池,使线程池能正确的正常的退出,改用互斥量和条件变量来保护任务队列。

Http的响应报文,其中包含Content-Length的属性,客户端接收完之后,会主动关闭连接,而不用服务器关闭,防止大量的TIME_WAIT状态的连接,提高Server的可以性。


代码在github上:https://github.com/LZXxd/webServer.git


  下面的是全部的代码,其中有注释,可以很容易看明白。


locker.h文件

#ifndef _LOCKER_H_
#define _LOCKER_H_

#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>

/*信号量的类*/
class sem_locker
{
private:
    sem_t m_sem;

public:
    //初始化信号量
    sem_locker()
    {
	if(sem_init(&m_sem, 0, 0) != 0)
	    printf("sem init error\n");
    }
    //销毁信号量
    ~sem_locker()
    {
	sem_destroy(&m_sem);
    }

    //等待信号量
    bool wait()
    {
	return sem_wait(&m_sem) == 0;
    }
    //添加信号量
    bool add()
    {
	return sem_post(&m_sem) == 0;
    }
};


/*互斥 locker*/
class mutex_locker
{
private:
    pthread_mutex_t m_mutex;

public:
    mutex_locker()
    {
    	if(pthread_mutex_init(&m_mutex, NULL) != 0)
	    printf("mutex init error!");
    }
    ~mutex_locker()
    {
	pthread_mutex_destroy(&m_mutex);
    }

    bool mutex_lock()  //lock mutex
    {
	return pthread_mutex_lock(&m_mutex) == 0;
    }
    bool mutex_unlock()   //unlock
    {
	return pthread_mutex_unlock(&m_mutex) == 0;
    }
};

/*条件变量 locker*/
class cond_locker
{
private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;

public:
    // 初始化 m_mutex and m_cond
    cond_locker()
    {
	if(pthread_mutex_init(&m_mutex, NULL) != 0)
	    printf("mutex init error");
	if(pthread_cond_init(&m_cond, NULL) != 0)
	{   //条件变量初始化是被,释放初始化成功的mutex
	    pthread_mutex_destroy(&m_mutex);
	    printf("cond init error");
	}
    }
    // destroy mutex and cond
    ~cond_locker()
    {
	pthread_mutex_destroy(&m_mutex);
	pthread_cond_destroy(&m_cond);
    }
    //等待条件变量
    bool wait()
    {
	int ans = 0;
	pthread_mutex_lock(&m_mutex);
	ans = pthread_cond_wait(&m_cond, &m_mutex);
	pthread_mutex_unlock(&m_mutex);
	return ans == 0;
    }
    //唤醒等待条件变量的线程
    bool signal()
    {
	return pthread_cond_signal(&m_cond) == 0;
    }

    //唤醒all等待条件变量的线程
    bool broadcast()
    {
            return pthread_cond_broadcast(&m_cond) == 0;
    }
};

#endif


thread_pool.h头文件

#ifndef _PTHREAD_POOL_
#define _PTHREAD_POOL_

#include "locker.h"
#include <queue>
#include <stdio.h>
#include <exception>
#include <errno.h>
#include <pthread.h>
#include <iostream>

template<class T>
class threadpool
{
private:
    int thread_number;  //线程池的线程数
    //int max_task_number;  //任务队列中的最大任务数
    pthread_t *all_threads;   //线程数组
    std::queue<T *> task_queue; //任务队列
    mutex_locker queue_mutex_locker;  //互斥锁
    //sem_locker queue_sem_locker;   //信号量
    cond_locker queue_cond_locker; //cond
    bool is_stop; //是否结束线程
public:
    threadpool(int thread_num = 20);
    ~threadpool();
    bool append_task(T *task);
    void start();
    void stop();
private:
    //线程运行的函数。执行run()函数
    static void *worker(void *arg);
    void run();
    T *getTask();
};

template <class T>
threadpool<T>::threadpool(int thread_num):
	thread_number(thread_num),is_stop(false), all_threads(NULL)
{
    if(thread_num <= 0)
	printf("threadpool can't init because thread_number = 0");

    all_threads = new pthread_t[thread_number];
    if(all_threads == NULL)
    	printf("can't init threadpool because thread array can't new");
}

template <class T>
threadpool<T>::~threadpool()
{
    delete []all_threads;
    stop();
}

template <class T>
void threadpool<T>::stop()
{
        is_stop = true;
        //queue_sem_locker.add();
        queue_cond_locker.broadcast();
}

template <class T>
void threadpool<T>::start()
{
    for(int i = 0; i < thread_number; ++i)
    {
	//printf("create the %dth pthread\n", i);
	if(pthread_create(all_threads + i, NULL, worker, this) != 0)
	{//创建线程失败,清除成功申请的资源并抛出异常
	    delete []all_threads;
	    throw std::exception();
	}
	if(pthread_detach(all_threads[i]))
	{//将线程设置为脱离线程,失败则清除成功申请的资源并抛出异常
	    delete []all_threads;
	    throw std::exception();
	}
    }
}
//添加任务进入任务队列
template <class T>
bool threadpool<T>::append_task(T *task)
{   //获取互斥锁
    queue_mutex_locker.mutex_lock();
    
    bool is_signal = task_queue.empty();
    //添加进入队列
    task_queue.push(task);
    queue_mutex_locker.mutex_unlock();
    //唤醒等待任务的线程
    if(is_signal)
    {
            queue_cond_locker.signal();
    }
    return true;
}

template <class T>
void *threadpool<T>::worker(void *arg)
{
    threadpool *pool = (threadpool *)arg;
    pool->run();
    return pool;
}

template <class T>
T* threadpool<T>::getTask()
{
    T *task = NULL;
    queue_mutex_locker.mutex_lock();
    if(!task_queue.empty())
    {
        task = task_queue.front();
        task_queue.pop();
    }
    queue_mutex_locker.mutex_unlock();
    return task;
}

template <class T>
void threadpool<T>::run()
{
    while(!is_stop){
        T *task = getTask();
        if(task == NULL)
                queue_cond_locker.wait();
        else
	{
                task->doit();
		delete task;
	}
    }
    //for test
    //printf("exit%d\n", (unsigned long)pthread_self());
}

#endif


任务类task.h头文件

#ifndef _TASK_
#define _TASK_

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/wait.h>

char *path = "/home/xd/XD/Web/WebServer"; //路径
const int BUFFER_SIZE = 4096;

class task
{
private:
	int connfd;

public:
	task(){}
	task(int fd):connfd(fd){}
	~task(){}

	void response(char *message, int status)  //错误响应函数,status是响应状态码
	{
		char buf[512];
		sprintf(buf, "HTTP/1.1 %d OK\r\nConnection: Close\r\n"  //响应头
		"content-length:%d\r\n\r\n", status, strlen(message));

		sprintf(buf, "%s%s", buf, message);
		write(connfd, buf, strlen(buf));

	}
	void response_file(int size, int status)  //请求静态文件响应函数,size为文件大小
	{
		char buf[128];
		sprintf(buf, "HTTP/1.1 %d OK\r\nConnection: Close\r\n"
		"content-length:%d\r\n\r\n", status, size);
		write(connfd, buf, strlen(buf));
	}

	void response_get(char *filename);   //Get函数

	void response_post(char *filename, char *argv);  //POST函数

	void doit();
};

void task::doit()
{
	char buffer[BUFFER_SIZE];
	int size;
read:	size = read(connfd, buffer, BUFFER_SIZE - 1);  //读取Http请求报文
	//printf("%s", buffer);
	if(size > 0)
	{
		char method[5];
		char filename[50];
		int i, j;
		i = j = 0;
		while(buffer[j] != ' ' && buffer[j] != '\0')//获取请求方法
		{
			method[i++] = buffer[j++];
		}
		++j;
		method[i] = '\0';
		i = 0;
		while(buffer[j] != ' ' && buffer[j] != '\0')//获取请求文件
		{
			filename[i++] = buffer[j++];
		}
		filename[i] = '\0';

		if(strcasecmp(method, "GET") == 0)  //get method
		{
			response_get(filename);
		}
		else if(strcasecmp(method, "POST") == 0)  //post method
		{
			//printf("Begin\n");
			char argvs[100];
			memset(argvs, 0, sizeof(argvs));
			int k = 0;
			char *ch = NULL;
			++j;
			while((ch = strstr(argvs, "Content-Length")) == NULL) //查找请求头部中的Content-Length行
			{
				k = 0;
				memset(argvs, 0, sizeof(argvs));
				while(buffer[j] != '\r' && buffer[j] != '\0')
				{
					argvs[k++] = buffer[j++];
				}
				++j;
				//printf("%s\n", argvs);
			}
			int length;
			char *str = strchr(argvs, ':');  //获取POST请求数据的长度
			//printf("%s\n", str);
			++str;
			sscanf(str, "%d", &length);
			//printf("length:%d\n", length);
			j = strlen(buffer) - length;    //从请求报文的尾部获取请求数据
			k = 0;
			memset(argvs, 0, sizeof(argvs));
			while(buffer[j] != '\r' && buffer[j] != '\0')
				argvs[k++] = buffer[j++];

			argvs[k] = '\0';
			//printf("%s\n", argvs);
			response_post(filename, argvs);  //POST方法
		}
		else  //未知的方法
		{
			char message[512];
			sprintf(message, "<html><title>Tinyhttpd Error</title>");
			sprintf(message, "%s<body>\r\n", message);
			sprintf(message, "%s 501\r\n", message);
			sprintf(message, "%s <p>%s: Httpd does not implement this method", 
				message, method);
			sprintf(message, "%s<hr><h3>The Tiny Web Server<h3></body>", message);
			response(message, 501);
		}


		//response_error("404");
	}
	else if(size < 0)//读取失败,重新读取
		goto read;

	sleep(3);  //wait for client close, avoid TIME_WAIT
	close(connfd);
}

void task::response_get(char *filename)
{
	char file[100];
	strcpy(file, path);

	int i = 0;
	bool is_dynamic = false;
	char argv[20];
	//查找是否有?号
	while(filename[i] != '?' && filename[i] != '\0')
		    ++i;
	if(filename[i] == '?')
	{	//有?号,则是动态请求
		int j = i;
		++i;
		int k = 0;
		while(filename[i] != '\0')  //分离参数和文件名
			argv[k++] = filename[i++];
		argv[k] = '\0';
		filename[j] = '\0';
		is_dynamic = true;
	}

	if(strcmp(filename, "/") == 0)
		strcat(file, "/index.html");
	else
		strcat(file, filename);


	//printf("%s\n", file);
	struct stat filestat;
	int ret = stat(file, &filestat);

	if(ret < 0 || S_ISDIR(filestat.st_mode)) //file doesn't exits
	{
		char message[512];
		sprintf(message, "<html><title>Tinyhttpd Error</title>");
		sprintf(message, "%s<body>\r\n", message);
		sprintf(message, "%s 404\r\n", message);
		sprintf(message, "%s <p>GET: Can't find the file", message);
		sprintf(message, "%s<hr><h3>The Tiny Web Server<h3></body>", 
			message);
		response(message, 404);
		return;
	}

	if(is_dynamic)
	{
		if(fork() == 0) 
		/*创建子进程执行对应的子程序,多线程中,创建子进程,
		只有当前线程会被复制,其他线程就“不见了”,这正符合我们的要求,
		而且fork后执行execl,程序被进程被重新加载*/
		{
			dup2(connfd, STDOUT_FILENO);  
			//将标准输出重定向到sockfd,将子程序输出内容写到客户端去。
	    		execl(file, argv); //执行子程序
		}
		wait(NULL);
	}
	else
	{
		int filefd = open(file, O_RDONLY);
		response_file(filestat.st_size, 200);
		//使用“零拷贝”发送文件
		sendfile(connfd, filefd, 0, filestat.st_size);
	}
}


void task::response_post(char *filename, char *argvs)
{
	char file[100];
	strcpy(file, path);

	strcat(file, filename);

	struct stat filestat;
	int ret = stat(file, &filestat);
	printf("%s\n", file);
	if(ret < 0 || S_ISDIR(filestat.st_mode)) //file doesn't exits
	{
		char message[512];
		sprintf(message, "<html><title>Tinyhttpd Error</title>");
		sprintf(message, "%s<body>\r\n", message);
		sprintf(message, "%s 404\r\n", message);
		sprintf(message, "%s <p>GET: Can't find the file", message);
		sprintf(message, "%s<hr><h3>The Tiny Web Server<h3></body>", 
			message);
		response(message, 404);
		return;
	}

	char argv[20];
	int a, b;
	ret = sscanf(argvs, "a=%d&b=%d", &a, &b);//判断参数是否正确
	if(ret < 0 || ret != 2)
	{
		char message[512];
		sprintf(message, "<html><title>Tinyhttpd Error</title>");
		sprintf(message, "%s<body>\r\n", message);
		sprintf(message, "%s 404\r\n", message);
		sprintf(message, "%s <p>GET: Parameter error", message);
		sprintf(message, "%s<hr><h3>The Tiny Web Server<h3></body>", 
			message);
		response(message, 404);
		return;
	}
	sprintf(argv, "%d&%d", a, b);
	if(fork() == 0) 
	/*创建子进程执行对应的子程序,多线程中,创建子进程,
	只有当前线程会被复制,其他线程就“不见了”,这正符合我们的要求,
	而且fork后执行execl,程序被进程被重新加载*/
	{
		dup2(connfd, STDOUT_FILENO);  
		//将标准输出重定向到sockfd,将子程序输出内容写到客户端去。
		execl(file, argv); //执行子程序
	}
	wait(NULL);
}



#endif //


webServer.cpp文件

#include "task.h"
#include "thread_pool.h"

int main(int argc, char *argv[])
{
	if(argc != 2)
    	{
		printf("usage : %s port\n", argv[0]);
		return 1;
    	}

    	int sockfd, connfd;
   	struct sockaddr_in servaddr, client;
    	int port = atoi(argv[1]);  //获取端口
    	//设置服务端的sockaddr_in
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(port);
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	//创建socket
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    	if(sockfd < 0)
    	{
		printf("socket error\n");
		return 1;
    	}
    	//绑定
    	int ret = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    	if(ret < 0)
    	{
		printf("bind error\n");
		return 1;
    	}
    	//监听
    	ret = listen(sockfd, 10);
    	if(ret < 0)
    	{
		printf("listen error\n");
		return 1;
    	}
    	//创建线程池
    	threadpool<task> pool(20);
    	pool.start();  //线程池开始运行

    	while(1)
    	{
    		//printf("new connection\n");
		socklen_t len = sizeof(client);
		//接受连接
		connfd = accept(sockfd, (struct sockaddr *)&client, &len);
		task *ta = new task(connfd);
		//向线程池添加任务
		pool.append_task(ta);
    	}
    	return 0;
}




下面的是两个html文件:

首先是index.html:

<html>
<head>
<title> index.html </title>
</head>

<body bgcolor="blue">
	This is a html page.
	<hr>

	<image src="test.jpg"></image>
	<p>The Tiny Web server</p><br>
</body>
</html>


执行结果为:



然后是submit.html:

<html>
<head><title>request test</title></head>
<body> 
<form name = "input" action = "/cgi/adder" method = "post">
a: <input type = "text" name = "a"/> <br/>
b: <input type = "text" name = "b"/> <br/>
<input type = "submit" value = "Submit"/>
<form/>
</body>
</html>

adder是下面的add.c编译之后的可执行文件。

执行结果为:




参数错误情况:




下面的是文件不存在的情况:




下面的是获取动态内容:

首先写两个小程序,加法和乘法:

加法的:

#include <stdio.h>
#include <string.h>

#define MAXBUFFER 2048
#define MAXLINE 1024

int main(int argc, char *argv[])
{
    int a, b;
	char body[MAXBUFFER], content[MAXLINE];
    int ret = sscanf(argv[1], "%d&%d", &a, &b);
    if(ret < 0 || ret != 2)
	{
	 	//设置消息体
		sprintf(body, "<html><title>add.action</title>");
		sprintf(body, "%s<body bgcolor=""green"">\r\n", body);
		sprintf(body, "%s %s\r\n", body, "200");
		sprintf(body, "%s <p>%s: %s", body, "GET", "fail");
		sprintf(body, "%s <p>The addition\r\n", body);
		sprintf(body, "%s The parameter is wrong\r\n", body);
		sprintf(body, "%s<hr><h3>The Tiny Web Server<h3></body>\r\n", body);

		//设置请求头
		sprintf(content, "HTTP/1.1 %s\r\n", "GET");
		sprintf(content, "%sContent-type: text/html\r\n", content);
		sprintf(content, "%sContent-length: %d\r\n", content, 
						(int)strlen(body));
		sprintf(content, "%sEncoding:UTF-8\r\n\r\n", content);
	}
	else
	{
		//设置消息体
		sprintf(body, "<html><title>add.action</title>");
		sprintf(body, "%s<body bgcolor=""green"">\r\n", body);
		sprintf(body, "%s %s\r\n", body, "200");
		sprintf(body, "%s <p>%s: %s", body, "GET", "success");
		sprintf(body, "%s <p>The addition\r\n", body);
		sprintf(body, "%s The answer of %d + %d = %d\r\n", body, a, b, a + b);
		sprintf(body, "%s<hr><h3>The Tiny Web Server<h3></body>\r\n", body);

		//设置请求头
		sprintf(content, "HTTP/1.1 %s\r\n", "GET");
		sprintf(content, "%sContent-type: text/html\r\n", content);
		sprintf(content, "%sContent-length: %d\r\n", content, 
						(int)strlen(body));
		sprintf(content, "%sEncoding:UTF-8\r\n\r\n", content);
	}
	printf("%s", content);
	printf("%s", body);
	fflush(stdout);
	return 0;
}

乘法的:

#include <stdio.h>
#include <string.h>

#define MAXBUFFER 2048
#define MAXLINE 1024

int main(int argc, char *argv[])
{
    int a, b;
	char body[MAXBUFFER], content[MAXLINE];
    int ret = sscanf(argv[1], "%d&%d", &a, &b);
    if(ret < 0 || ret != 2)  //参数格式错误
	{
	 	//设置消息体
		sprintf(body, "<html><title>add.action</title>");
		sprintf(body, "%s<body bgcolor=""green"">\r\n", body);
		sprintf(body, "%s %s\r\n", body, "200");
		sprintf(body, "%s <p>%s: %s", body, "GET", "fail");
		sprintf(body, "%s <p>The multiplication\r\n", body);
		sprintf(body, "%s The parameter is wrong\r\n", body);
		sprintf(body, "%s<hr><h3>The Tiny Web Server<h3></body>\r\n", body);

		//设置请求头
		sprintf(content, "HTTP/1.1 %s\r\n", "GET");
		sprintf(content, "%sContent-type: text/html\r\n", content);
		sprintf(content, "%sContent-length: %d\r\n", content, 
						(int)strlen(body));
		sprintf(content, "%sEncoding:UTF-8\r\n\r\n", content);
	}
	else  //参数正确
	{
		//设置消息体
		sprintf(body, "<html><title>add.action</title>");
		sprintf(body, "%s<body bgcolor=""pink"">\r\n", body);
		sprintf(body, "%s %s\r\n", body, "200");
		sprintf(body, "%s <p>%s: %s", body, "GET", "success");
		sprintf(body, "%s <p>The multiplication\r\n", body);
		sprintf(body, "%s The answer of %d * %d = %d\r\n", body, a, b, a * b);
		sprintf(body, "%s<hr><h3>The Tiny Web Server<h3></body>\r\n", body);

		//设置请求头
		sprintf(content, "HTTP/1.1 %s\r\n", "GET");
		sprintf(content, "%sContent-type: text/html\r\n", content);
		sprintf(content, "%sContent-length: %d\r\n", content, 
						(int)strlen(body));
		sprintf(content, "%sEncoding:UTF-8\r\n\r\n", content);
	}
	printf("%s", content);
	printf("%s", body);
	fflush(stdout);
	return 0;
}

都编译成adder 和muler。

这部分是Get获取动态数据。

这里就演示adder的情况。

看执行结果:




简易版的小型的web服务器就搞定了。功能单一了点,等再深入了解web服务器,再来增加更多的功能。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐