Linux下编写C++服务器(HTTP服务器)

今天使用Makefile写一个HTTP的服务器,之前没有接触过Makefile,需要探索一下。

VS2015下测试Makefile

  1. 新建一个Makefile Project(Linux),这里名为HTTPTest;
    在这里插入图片描述
  2. 添加新建项, 添加C ++文件(.cpp),名为main.cpp
    在这里插入图片描述
    内容如下:
#pragma once
#pragma execution_character_set("utf-8")

#include <stdio.h>

int main()
{
	printf("HttpServer启动\n");
	int count = 0;
	while (count<3)
	{
		count++;
		printf("hello %d\n",count);
	}

	printf("HttpServer关闭\n");
	return 0;
}
  1. 在main.cpp的同级目录下创建一个Makefile的空文件,注意文件名的大小写,并添加到HTTPTest项目中,
    在这里插入图片描述
    写入如下内容:
build:
	gcc -gdwarf-2 -o HTTPTest main.cpp

clean:
	rm -rf HTTPTest
  1. 右键项目,选择属性,在GeneralRemote Build Root Directory中写入Linux上的项目目录,
    在这里插入图片描述
    Remote Build四项中分别写入
cd $(RemoteRootDir)/$(ProjectName); make build
cd $(RemoteRootDir)/$(ProjectName); make clean build
cd $(RemoteRootDir)/$(ProjectName); make clean
$(RemoteRootDir)/$(ProjectName)/HTTPTest

如图:
在这里插入图片描述

  1. main.cpp的其中一行打上断点,按F5调试,测试成功了。在这里插入图片描述

安装libevent

HTTP服务器我们选择libevent库实现。

  1. libevent下载到Linux虚拟机中,这里选择2.1.10版本,下载完成后对压缩包进行解压
    在这里插入图片描述
tar xvf libevent-2.1.10-stable.tar.gz

在这里插入图片描述

  1. 输入如下命令,用于进入解压后的文件夹,并生成makefile,–prefix用来指定libevent的安装目录;
cd libevent-2.1.10-stable/
./configure --prefix=/usr/libevent

在这里插入图片描述

  1. 输入make编译;
    在这里插入图片描述
  2. 输入make install开始安装,安装成功后,/usr/libevent生成了binincludelib文件夹。在这里插入图片描述在这里插入图片描述

配置环境

  1. 我们需要把上面生成的/usr/libevent/include复制一份到windows中,这里用到共享文件夹,
mkdir /mnt/hgfs/LinuxShare/libevent
cp -r /usr/libevent/include /mnt/hgfs/LinuxShare/libevent

windows出现了E:\LinuxShare\libevent\include目录,是我们需要的头文件;

  1. 右键项目->属性->C++->Include Search Path,输入
    E:\LinuxShare\include;E:\LinuxShare\include\c++\4.8.2;E:\LinuxShare\libevent\include;,前两个是上一编配置的Linux头文件,最后一个是libevent头文件;
    在这里插入图片描述

Makefile文件的build需要修改

gcc -gdwarf-2 -o HTTPTest main.cpp  -I /usr/libevent/include/ -L /usr/libevent/lib/ -levent

在这里插入图片描述
按F5后编译通过,但无法运行,报错如下:
error while loading shared libraries: libevent-2.1.so.6: cannot open shared object file: No such file or directory,提示没有找到这个库,解决方法为在linux终端输入

ln  -s /usr/libevent/lib/libevent-2.1.so.6 /usr/lib64/libevent-2.1.so.6

这样运行时就可以链接库。

封装HTTP

创建myhttpserver.hmyhttpserver.cpp,参考libevent实现http server,经历了不少bug后,实现了get和post方法。

myhttpserver.h

#pragma once

#include <event2/event.h>
#include <evhttp.h>
#include <sys/stat.h>

static bool closesign = false;
//获取Content-Type
const char *get_file_type(char *name);
//发送目录html
int send_dir(struct evbuffer* bev, const char *dirname);
//http头
int send_header(struct evhttp_request* request, const char* filename, long filelen, const char* connest);
//传送文件
int send_file_to_http(const char *filename, struct evbuffer* bev);
//回调函数
void HttpGenericCallback(struct evhttp_request* request, void* arg);

class Myhttpserver
{
public:
	Myhttpserver();
	~Myhttpserver();

private:
	struct event_base* base;
	struct evhttp* http;
	char errmsg[512];
public:
	//初始化
	bool inithttp();
	//打开服务器
	bool start(unsigned int port);
	//调用回调
	void set_gencb(void(*cb)(struct evhttp_request *, void *));
	//循环处理
	void dispatch();
	static void stop(struct event_base* base);
	void free();
	char* geterrmsg();
};

myhttpserver.cpp

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include "myhttpserver.h"

/*
charset=iso-8859-1	西欧的编码,说明网站采用的编码是英文;
charset=gb2312		说明网站采用的编码是简体中文;
charset=utf-8			代表世界通用的语言编码;
						可以用到中文、韩文、日文等世界上所有语言编码上
charset=euc-kr		说明网站采用的编码是韩文;
charset=big5			说明网站采用的编码是繁体中文;

以下是依据传递进来的文件名,使用后缀判断是何种文件类型
将对应的文件类型按照http定义的关键字发送回去
*/
const char *get_file_type(const char *name) {
	char* dot;
	char tmpname[128] = {0};
	strncpy(tmpname, name, strlen(name));
	dot = strrchr(tmpname, '.');	//自右向左查找‘.’字符;如不存在返回NULL

	if (dot == (char*)0)
		return "text/plain; charset=utf-8";
	if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
		return "text/html; charset=utf-8";
	if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
		return "image/jpeg";
	if (strcmp(dot, ".gif") == 0)
		return "image/gif";
	if (strcmp(dot, ".png") == 0)
		return "image/png";
	if (strcmp(dot, ".css") == 0)
		return "text/css";
	if (strcmp(dot, ".au") == 0)
		return "audio/basic";
	if (strcmp(dot, ".wav") == 0)
		return "audio/wav";
	if (strcmp(dot, ".avi") == 0)
		return "video/x-msvideo";
	if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
		return "video/quicktime";
	if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
		return "video/mpeg";
	if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
		return "model/vrml";
	if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
		return "audio/midi";
	if (strcmp(dot, ".mp3") == 0)
		return "audio/mpeg";
	if (strcmp(dot, ".ogg") == 0)
		return "application/ogg";
	if (strcmp(dot, ".pac") == 0)
		return "application/x-ns-proxy-autoconfig";

	return "text/plain; charset=utf-8";
}


int send_header(struct evhttp_request* request, const char* filename, long filelen, const char* connest) {

	// 文件类型
	evhttp_add_header(request->output_headers, "Content-Type", get_file_type(filename));
	// 文件大小
	if (filelen > 0)
	{
		char charsize[50] = { 0 };
		sprintf(charsize, "%ld", filelen);
		evhttp_add_header(request->output_headers, "Content-Length", charsize);
	}
	// Connection: close/keep-alive
	evhttp_add_header(request->output_headers, "Connection", connest);

	return 0;
}

int send_dir(struct evbuffer* bev, const char *dirname) {
	char encoded_name[1024];
	char path[1024];
	char timestr[64];
	struct stat sb;
	struct dirent **dirinfo;

	char buf[4096] = { 0 };
	sprintf(buf, "<html><head><meta charset=\"utf-8\"><title>%s</title></head>", dirname);
	sprintf(buf + strlen(buf), "<body><h1>当前目录:%s</h1><table>", dirname);
	//添加目录内容
	int num = scandir(dirname, &dirinfo, NULL, alphasort);
	for (int i = 0; i<num; ++i) {
		// 编码
		strcpy(encoded_name, dirinfo[i]->d_name);

		sprintf(path, "%s%s", dirname, dirinfo[i]->d_name);
		printf("############# path = %s\n", path);
		if (lstat(path, &sb) < 0) {
			sprintf(buf + strlen(buf),
				"<tr><td><a href=\"%s\">%s</a></td></tr>\n",
				encoded_name, dirinfo[i]->d_name);
		}
		else {
			strftime(timestr, sizeof(timestr),
				"  %d  %b   %Y  %H:%M", localtime(&sb.st_mtime));
			if (S_ISDIR(sb.st_mode)) {
				sprintf(buf + strlen(buf),
					"<tr><td><a href=\"%s/\">%s/</a></td><td>%s</td><td>%ld</td></tr>\n",
					encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);
			}
			else {
				sprintf(buf + strlen(buf),
					"<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%ld</td></tr>\n",
					encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);
			}
		}
		//bufferevent_write(bev, buf, strlen(buf));
		evbuffer_add_printf(bev, buf);
		printf(buf);
		memset(buf, 0, sizeof(buf));
	}
	sprintf(buf + strlen(buf), "</table></body></html>");
	//bufferevent_write(bev, buf, strlen(buf));
	evbuffer_add_printf(bev, buf);
	printf(buf);
	printf("################# Dir Read OK !!!!!!!!!!!!!!\n");

	return 0;
}


int send_file_to_http(const char *filename, struct evbuffer* bev) {
	int fd = open(filename, O_RDONLY);
	int ret = 0;
	char buf[4096] = { 0 };
	//单次上限4096
	//evbuffer_read(bev, fd, 30556);
	while ((ret = read(fd, buf, sizeof(buf)))) {
		if (ret < 0)
			break;
		//不能传\0
		//evbuffer_add_printf(bev, buf);
		//可以传\0
		evbuffer_add(bev, buf, ret);
		memset(buf, 0, 4096);
	}
	close(fd);
	return 0;
}
//回调函数
void HttpGenericCallback(struct evhttp_request* request, void* arg)
{
	const struct evhttp_uri* evhttp_uri = evhttp_request_get_evhttp_uri(request);
	char url[8192];
	memset(url, 0x00, sizeof(url));
	evhttp_uri_join(const_cast<struct evhttp_uri*>(evhttp_uri), url, 8192);

	printf("accept request url:%s\n", url);

	if (request->kind == EVHTTP_REQUEST) {
		//get
		if (request->type == EVHTTP_REQ_GET) {
			/*
			http://foo.com/?q=test&s=some+thing
			evhttp_request_uri: 解析HTTP请求中的ur,得到/?q=test&s=some+thing
			evhttp_parse_query: 解析名值对,得到一个evkeyvalq结构,里面包含了key/value的数组.
			*/
			char* decode_uri = strdup((char*)evhttp_request_uri(request));
			struct evkeyvalq http_query;
			evhttp_parse_query(decode_uri, &http_query);
			free(decode_uri);

			//const char *request_value = evhttp_find_header(&http_query, "data");
			//解码
			//strcpy(url, evhttp_decode_uri(url));
			char *pf = &url[1];

			if (strcmp(url, "/") == 0 || strcmp(url, "/.") == 0) {
				pf = "./";
			}

			printf("***** http Request Resource Path =  %s, pf = %s\n", url, pf);
			//退出服务器
			if (strcmp(url, "/bye") == 0) {
				closesign = true;
				struct event_base* base = (struct event_base*)arg;
				Myhttpserver::stop(base);
				return;
			}
			struct stat sb;
			struct evbuffer* evbuf = evbuffer_new();
			if (!evbuf) {
				printf("create evbuffer failed!\n");
				return;
			}
			char dir[256] = { 0 }, filename[256] = { 0 };
			snprintf(dir, sizeof(dir), "/home/aubin/projects/HTTPTest/%s", pf);
			//打开文件
			if (stat(dir, &sb) == 0 && (sb.st_mode & S_IFREG)) {
				send_header(request, pf, sb.st_size, "close");
				send_file_to_http(dir, evbuf);
				evhttp_send_reply(request, HTTP_OK, "OK", evbuf);

				evbuffer_free(evbuf);
				return;

			}
			//打开子文件夹
			else if (stat(dir, &sb) == 0 && (sb.st_mode & __S_IFDIR)) {
				send_header(request, ".html", 0, "close");
				send_dir(evbuf, dir);
				evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
				evhttp_clear_headers(&http_query);
				evbuffer_free(evbuf);
				return;
			}
			//打开主文件夹
			else {
				send_header(request, ".html", 0, "close");
				send_dir(evbuf, "/home/aubin/projects/HTTPTest");
				evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
				evhttp_clear_headers(&http_query);
				evbuffer_free(evbuf);
				return;

			}
		}
		//post
		else if (request->type == EVHTTP_REQ_POST) {
			char charadd1[10], charadd2[10];
			struct evkeyvalq http_query_post;
			char decode_post_uri[1024] = { 0 };
			int buffer_data_len = EVBUFFER_LENGTH(request->input_buffer);
			char *post_data = (char *)malloc(buffer_data_len + 1);
			memset(post_data, 0, buffer_data_len + 1);
			memcpy(post_data, EVBUFFER_DATA(request->input_buffer), buffer_data_len);
			sprintf(decode_post_uri, "/?%s", post_data);  //处理数据格式
			evhttp_parse_query(decode_post_uri, &http_query_post);
			strcpy(charadd1, evhttp_find_header(&http_query_post, "add1"));
			strcpy(charadd2, evhttp_find_header(&http_query_post, "add2"));

			int sum = atoi(charadd1) + atoi(charadd2);

			struct evbuffer* evbuf = evbuffer_new();
			if (!evbuf) {
				printf("create evbuffer failed!\n");
				return;
			}

			evbuffer_add_printf(evbuf, "<!DOCTYPE html>\
							<html>\
							<head>\
							<meta charset = \"utf-8\">\
							<title>(runoob.com)</title>\
							</head>\
							<body>\
							<p>%d</p>\
							</body>\
							</html>", sum);
			evhttp_send_reply(request, HTTP_OK, "OK", evbuf);
			evbuffer_free(evbuf);
		}
	}


}


Myhttpserver::Myhttpserver()
{
}

Myhttpserver::~Myhttpserver()
{
}

bool Myhttpserver::inithttp()
{
	event_init();
	base = event_base_new();
	if (!base)
	{
		sprintf(errmsg, "create event_base failed,error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	http = evhttp_new(base);
	if (!http)
	{
		sprintf(errmsg, "create evhttp failed,error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	return true;
}

bool Myhttpserver::start(unsigned int port)
{
	if (evhttp_bind_socket(http, "0.0.0.0", port) != 0)
	{
		sprintf(errmsg, "start evhttp failed,error: %s(errno: %d)\n", strerror(errno), errno);
		return false;
	}
	evhttp_set_timeout(http, 120);
	return true;
}

void Myhttpserver::set_gencb(void(*cb)(struct evhttp_request *, void *))
{
	evhttp_set_gencb(http, cb, base);
}

void Myhttpserver::dispatch()
{
	event_base_dispatch(base);
}

void Myhttpserver::stop(struct event_base* base)
{
	event_base_loopbreak(base);
	event_base_loopexit(base, 0);
	return;
}

void Myhttpserver::free()
{
	evhttp_free(http);
	event_base_free(base);
}

char * Myhttpserver::geterrmsg()
{
	return errmsg;
}

main.cpp

#pragma once
#pragma execution_character_set("utf-8")

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "myhttpserver.h"


int main(int argc, char **argv) {
	Myhttpserver httpserver;
	if (!httpserver.inithttp()) {
		printf(httpserver.geterrmsg());
		return 1;
	}
		
	if (!httpserver.start(5000))
	{
		printf(httpserver.geterrmsg());
		return 1;
	}
	httpserver.set_gencb(HttpGenericCallback);
	httpserver.dispatch();
	httpserver.free();

	return 0;
}

Makefile的gcc需要改成g++,否则会出现下面的问题,include和lib就可以编译运行了。
在这里插入图片描述
Makefile

INCLUDEPATH=/usr/libevent/include
LIBPATH=/usr/libevent/lib
CC1=g++

build:main.o myhttpserver.o
	$(CC1) -gdwarf-2 -o HTTPTest main.o myhttpserver.o -I $(INCLUDEPATH) -L $(LIBPATH) -levent

main.o:main.cpp myhttpserver.h
	$(CC1) -gdwarf-2 -c main.cpp -I $(INCLUDEPATH) -L $(LIBPATH) -levent

myhttpserver.o:myhttpserver.cpp myhttpserver.h
	$(CC1) -gdwarf-2 -c myhttpserver.cpp -I $(INCLUDEPATH) -L $(LIBPATH) -levent

clean:
	rm *.o HTTPTest

运行展示

服务端的HTTPTest有这些文件。
在这里插入图片描述
index.html内容如下

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>菜鸟教程(runoob.com)</title> 
</head>
<body>

<form name="input" action="/add" method="post">
add1: <input type="text" name="add1">
add2: <input type="text" name="add2">
<input type="submit" value="Submit">
</form>

<p><b>注意:</b> 表单本身是不可见的。并且注意一个文本字段的默认宽度是20个字符。</p>

</body>
</html>

首页
在这里插入图片描述
普通文件展示
打开获取影音需要在html写入连接,不能像图片、gif一样直接打开,否则会跳出下载界面。
在这里插入图片描述
index页面
在这里插入图片描述
点击提交按钮
在这里插入图片描述
bye关闭服务器
在这里插入图片描述

  • 2
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值