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

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

VS2015下测试Makefile

  1. 新建一个Makefile Project(Linux),这里名为HTTPTest;
    Linux下编写C++服务器(HTTP服务器)_第1张图片
  2. 添加新建项, 添加C ++文件(.cpp),名为main.cpp
    Linux下编写C++服务器(HTTP服务器)_第2张图片
    内容如下:
#pragma once
#pragma execution_character_set("utf-8")

#include 

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项目中,
    Linux下编写C++服务器(HTTP服务器)_第3张图片
    写入如下内容:
build:
	gcc -gdwarf-2 -o HTTPTest main.cpp

clean:
	rm -rf HTTPTest
  1. 右键项目,选择属性,在GeneralRemote Build Root Directory中写入Linux上的项目目录,
    Linux下编写C++服务器(HTTP服务器)_第4张图片
    Remote Build四项中分别写入
cd $(RemoteRootDir)/$(ProjectName); make build
cd $(RemoteRootDir)/$(ProjectName); make clean build
cd $(RemoteRootDir)/$(ProjectName); make clean
$(RemoteRootDir)/$(ProjectName)/HTTPTest

如图:
Linux下编写C++服务器(HTTP服务器)_第5张图片

  1. main.cpp的其中一行打上断点,按F5调试,测试成功了。Linux下编写C++服务器(HTTP服务器)_第6张图片

安装libevent

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

  1. 把libevent下载到Linux虚拟机中,这里选择2.1.10版本,下载完成后对压缩包进行解压
    Linux下编写C++服务器(HTTP服务器)_第7张图片
tar xvf libevent-2.1.10-stable.tar.gz

Linux下编写C++服务器(HTTP服务器)_第8张图片

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

Linux下编写C++服务器(HTTP服务器)_第9张图片

  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头文件;
    Linux下编写C++服务器(HTTP服务器)_第10张图片

Makefile文件的build需要修改

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

Linux下编写C++服务器(HTTP服务器)_第11张图片
按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 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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, "%s", dirname);
	sprintf(buf + strlen(buf), "

当前目录:%s

", 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),"\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),"\n", encoded_name, dirinfo[i]->d_name, timestr, sb.st_size);}else{sprintf(buf +strlen(buf),"\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),"
%s
%s/%s%ld
%s%s%ld
"
); //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, "\ \ \ \ (runoob.com)\ \ \

%d

\ \ "
, 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 
#include 
#include 
#include 
#include 
#include 
#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就可以编译运行了。
Linux下编写C++服务器(HTTP服务器)_第12张图片
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有这些文件。
Linux下编写C++服务器(HTTP服务器)_第13张图片
index.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>

首页
Linux下编写C++服务器(HTTP服务器)_第14张图片
普通文件展示
打开获取影音需要在html写入连接,不能像图片、gif一样直接打开,否则会跳出下载界面。
Linux下编写C++服务器(HTTP服务器)_第15张图片
index页面
Linux下编写C++服务器(HTTP服务器)_第16张图片
点击提交按钮
Linux下编写C++服务器(HTTP服务器)_第17张图片
bye关闭服务器
Linux下编写C++服务器(HTTP服务器)_第18张图片

你可能感兴趣的:(Linux)