在这个简易版的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
#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服务器,再来增加更多的功能。