【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)

Web服务器系列相关文章编写如下:

  1. 【Web开发】Node.js实现Web服务器(http模块)
  2. 【Web开发】Node.js实现Web服务器(express模块)
  3. 【Web开发】Python实现Web服务器(Flask入门)
  4. 【Web开发】Python实现Web服务器(Flask测试)
  5. 【Web开发】Python实现Web服务器(Tornado入门)
  6. 【Web开发】Python实现Web服务器(Tornado+flask+nginx)
  7. 【Web开发】Python实现Web服务器(FastAPI)
  8. 【Web开发】Android手机上基于Termux实现Web服务器(Python、node.js)
  9. 【Web开发】C++实现Web服务器(libevent,libcurl,libuv,poco)

文章目录

  • 1、简介
    • 1.1 libevent
    • 1.2 libcurl
    • 1.3 openssl
    • 1.4 cJSON
  • 2、libevent
    • 2.1 下载
    • 2.2 VS2017编译
    • 2.3 VS2008编译
    • 2.4 代码测试(http服务端)
    • 2.5 代码测试(http客户端)
  • 3、libcurl
    • 3.1 下载
    • 3.2 编译
    • 3.3 代码测试(http客户端)
    • 3.4 命令行
  • 4、poco
    • 4.1 下载
    • 4.2 编译
    • 4.3 代码测试(http客户端)
    • 4.4 代码测试(http服务端)
  • 5、libuv
    • 5.1 下载
    • 5.2 编译
    • 5.3 代码测试(http客户端、服务端)
  • 结语

1、简介

1.1 libevent

官网地址:
https://libevent.org/

libevent - 一个事件通知库。

目前,libevent支持 /dev/poll、 kqueue(2)、 event ports、 POSIX select(2)、 Windows select()、 poll(2)和epoll(4)。内部事件机制完全独立于暴露的事件 API,简单更新 libevent 即可提供新功能,而无需重新设计应用程序。结果,Libevent允许可移植的应用程序开发,并提供操作系统上可用的最具可扩展性的事件通知机制。Libevent 也可以用于多线程应用程序,通过隔离每个 event_base 以便只有单个线程访问它,或者通过锁定对单个共享 event_base 的访问。 Libevent应该在 Linux、*BSD、Mac OS X、Solaris、Windows 等上编译。

Libevent 还为缓冲网络 IO 提供了一个复杂的框架,支持套接字、过滤器、速率限制、SSL、零拷贝文件传输和 IOCP。Libevent 包括对几个有用协议的支持,包括 DNS、HTTP 和最小的 RPC 框架。

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第1张图片

1.2 libcurl

官网地址:
https://curl.se/libcurl/

libcurl - 多协议文件传输库.

libcurl 是一个免费且易于使用的客户端 URL 传输库,支持 DICT、FILE、FTP、FTPS、GOPHER、GOPHERS、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、 RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET 和 TFTP。libcurl 支持 SSL 证书、HTTP POST、HTTP PUT、FTP 上传、基于 HTTP 表单的上传、代理、HTTP/2、HTTP/3、cookies、用户+密码认证(Basic、Digest、NTLM、Negotiate、Kerberos)、文件传输恢复,http代理隧道等等!

libcurl 是高度可移植的,它可以在多种平台上构建和工作,包括 Solaris、NetBSD、FreeBSD、OpenBSD、Darwin、HPUX、IRIX、AIX、Tru64、Linux、UnixWare、HURD、Windows、Amiga、OS/2、BeOs、Mac OS X、Ultrix、QNX、OpenVMS、RISC OS、Novell NetWare、DOS 等…

libcurl 是免费的、线程安全的、与 IPv6 兼容、功能丰富、支持良好、速度快、文档完整,并且已被许多知名、大型和成功的公司使用。
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第2张图片

1.3 openssl

详细介绍请查看本人另外一篇文章:
https://blog.csdn.net/hhy321/article/details/125922276

1.4 cJSON

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。
https://github.com/DaveGamble/cJSON

  • 从git下载源代码
    【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第3张图片
  • 源代码文件夹如下:
    【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第4张图片
mkdir build
cd build
cmake ..
# or
cmake .. -DENABLE_CJSON_UTILS=On -DENABLE_CJSON_TEST=Off

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第5张图片

  • 生成的工程文件如下:
    【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第6张图片
  • 举例说明:
    这里有一个json字符串如下:
{
    "name": "Awesome 4K",
    "resolutions": [
        {
            "width": 1280,
            "height": 720
        },
        {
            "width": 1920,
            "height": 1080
        },
        {
            "width": 3840,
            "height": 2160
        }
    ]
}
  • 通过cJSON代码构建上面的json字符串如下:
//create a monitor with a list of supported resolutions
//NOTE: Returns a heap allocated string, you are required to free it after use.
char *create_monitor(void)
{
    const unsigned int resolution_numbers[3][2] = {
        {1280, 720},
        {1920, 1080},
        {3840, 2160}
    };
    char *string = NULL;
    cJSON *name = NULL;
    cJSON *resolutions = NULL;
    cJSON *resolution = NULL;
    cJSON *width = NULL;
    cJSON *height = NULL;
    size_t index = 0;

    cJSON *monitor = cJSON_CreateObject();
    if (monitor == NULL)
    {
        goto end;
    }

    name = cJSON_CreateString("Awesome 4K");
    if (name == NULL)
    {
        goto end;
    }
    /* after creation was successful, immediately add it to the monitor,
     * thereby transferring ownership of the pointer to it */
    cJSON_AddItemToObject(monitor, "name", name);

    resolutions = cJSON_CreateArray();
    if (resolutions == NULL)
    {
        goto end;
    }
    cJSON_AddItemToObject(monitor, "resolutions", resolutions);

    for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
    {
        resolution = cJSON_CreateObject();
        if (resolution == NULL)
        {
            goto end;
        }
        cJSON_AddItemToArray(resolutions, resolution);

        width = cJSON_CreateNumber(resolution_numbers[index][0]);
        if (width == NULL)
        {
            goto end;
        }
        cJSON_AddItemToObject(resolution, "width", width);

        height = cJSON_CreateNumber(resolution_numbers[index][1]);
        if (height == NULL)
        {
            goto end;
        }
        cJSON_AddItemToObject(resolution, "height", height);
    }

    string = cJSON_Print(monitor);
    if (string == NULL)
    {
        fprintf(stderr, "Failed to print monitor.\n");
    }

end:
    cJSON_Delete(monitor);
    return string;
}
  • 通过cJSON代码解析上面的json字符串如下:
/* return 1 if the monitor supports full hd, 0 otherwise */
int supports_full_hd(const char * const monitor)
{
    const cJSON *resolution = NULL;
    const cJSON *resolutions = NULL;
    const cJSON *name = NULL;
    int status = 0;
    cJSON *monitor_json = cJSON_Parse(monitor);
    if (monitor_json == NULL)
    {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL)
        {
            fprintf(stderr, "Error before: %s\n", error_ptr);
        }
        status = 0;
        goto end;
    }

    name = cJSON_GetObjectItemCaseSensitive(monitor_json, "name");
    if (cJSON_IsString(name) && (name->valuestring != NULL))
    {
        printf("Checking monitor \"%s\"\n", name->valuestring);
    }

    resolutions = cJSON_GetObjectItemCaseSensitive(monitor_json, "resolutions");
    cJSON_ArrayForEach(resolution, resolutions)
    {
        cJSON *width = cJSON_GetObjectItemCaseSensitive(resolution, "width");
        cJSON *height = cJSON_GetObjectItemCaseSensitive(resolution, "height");

        if (!cJSON_IsNumber(width) || !cJSON_IsNumber(height))
        {
            status = 0;
            goto end;
        }

        if ((width->valuedouble == 1920) && (height->valuedouble == 1080))
        {
            status = 1;
            goto end;
        }
    }

end:
    cJSON_Delete(monitor_json);
    return status;
}
  • 再举例说明如下:
{
    name: "tomcat",
    age: "21",
    city: "beijing"
}
/*--------------------------------------------------*/
cJSON *root = cJSON_CreateObject();

cJSON_AddStringToObject(root, "name", "tomcat");
cJSON_AddStringToObject(root, "age", "21");
cJSON_AddStringToObject(root, "city", "beijing");

char* str_json = cJSON_Print(root);
printf("构建:%s\n", str_json);

cJSON_Delete(root);
root = NULL;

/*--------------------------------------------------*/
cJSON *root2;
root2 = cJSON_Parse(str_json);
cJSON *result = cJSON_GetObjectItem(root2, "name");
if(result) printf("解析name:%s\n", result->valuestring);
result = cJSON_GetObjectItem(root2, "age");
if (result) printf("解析age:%s\n", result->valuestring);
result = cJSON_GetObjectItem(root2, "city");
if (result) printf("解析city:%s\n", result->valuestring);

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第7张图片

2、libevent

2.1 下载

https://github.com/libevent/libevent
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第8张图片

2.2 VS2017编译

采用最新的版本2.1.12-stable。
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第9张图片

2.3 VS2008编译

采用比较早期版本,比如2.0.22-stable。
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第10张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第11张图片

  • makefile.nmake
# WATCH OUT!  This makefile is a work in progress.  It is probably missing
# tons of important things.  DO NOT RELY ON IT TO BUILD A GOOD LIBEVENT.

# Needed for correctness
CFLAGS=/IWIN32-Code /Iinclude /Icompat /DWIN32 /DHAVE_CONFIG_H /I.

# For optimization and warnings
CFLAGS=$(CFLAGS) /Ox /W3 /wd4996 /nologo

# XXXX have a debug mode

LIBFLAGS=/nologo

CORE_OBJS=event.obj buffer.obj bufferevent.obj bufferevent_sock.obj \
	bufferevent_pair.obj listener.obj evmap.obj log.obj evutil.obj \
	strlcpy.obj signal.obj bufferevent_filter.obj evthread.obj \
	bufferevent_ratelim.obj evutil_rand.obj
WIN_OBJS=win32select.obj evthread_win32.obj buffer_iocp.obj \
	event_iocp.obj bufferevent_async.obj
EXTRA_OBJS=event_tagging.obj http.obj evdns.obj evrpc.obj

ALL_OBJS=$(CORE_OBJS) $(WIN_OBJS) $(EXTRA_OBJS)
STATIC_LIBS=libevent_core.lib libevent_extras.lib libevent.lib


all: static_libs tests

static_libs: $(STATIC_LIBS)

libevent_core.lib: $(CORE_OBJS) $(WIN_OBJS)
	lib $(LIBFLAGS) $(CORE_OBJS) $(WIN_OBJS) /out:libevent_core.lib 

libevent_extras.lib: $(EXTRA_OBJS)
	lib $(LIBFLAGS) $(EXTRA_OBJS) /out:libevent_extras.lib

libevent.lib: $(CORE_OBJS) $(WIN_OBJS) $(EXTRA_OBJS)
	lib $(LIBFLAGS) $(CORE_OBJS) $(EXTRA_OBJS) $(WIN_OBJS) /out:libevent.lib

clean:
	del $(ALL_OBJS)
	del $(STATIC_LIBS)
	cd test
	$(MAKE) /F Makefile.nmake clean

tests:
	cd test
	$(MAKE) /F Makefile.nmake

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第12张图片

cd C:\Users\tomcat\Desktop\libevent-2.0.22-stable.tar\libevent-2.0.22-stable
nmake Makefile.nmake

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第13张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第14张图片
考虑到lib文件区分有md、mdd、mt、mtd所以我们在编译前,修改makefile.nmake文件:

CFLAGS=$(CFLAGS) /Ox /MD /W3 /wd4996 /nologo
CFLAGS=$(CFLAGS) /Ox /MDD /W3 /wd4996 /nologo
CFLAGS=$(CFLAGS) /Ox /MT /W3 /wd4996 /nologo
CFLAGS=$(CFLAGS) /Ox /MTD /W3 /wd4996 /nologo
nmake /f makefile.nmake clean
nmake /f makefile.nmake static_libs

2.4 代码测试(http服务端)

#include 
#include 
#include 

#pragma comment(lib, "libevent\\libevent.lib")
#pragma comment(lib, "libevent\\libevent_core.lib")
#pragma comment(lib, "libevent\\libevent_extras.lib")

/*
cd libevent-2.0.22-stable
nmake /f makefile.nmake static_libs
如果想重新编译:
nmake /f makefile.nmake clean
nmake /f makefile.nmake static_libs

Vs2008命令提示和Vs2008 X64 Win64命令提示,一个是编译32位一个是编译64位。

考虑到lib文件区分有md、mdd、mt、mtd所以我们在编译前,修改makefile.nmake文件:
CFLAGS=$(CFLAGS) /Ox /W3 /wd4996 /nologo
CFLAGS=$(CFLAGS) /Ox /MD /W3 /wd4996 /nologo
CFLAGS=$(CFLAGS) /Ox /MT /W3 /wd4996 /nologo
*/

/*
  A trivial static http webserver using Libevent's evhttp.

  This is not the best code in the world, and it does some fairly stupid stuff
  that you would never want to do in a production webserver. Caveat hackor!

 */

 /* Compatibility for possible missing IPv6 declarations */
#include "../util-internal.h"

#include 
#include 
#include 

#include 
#include 

#ifdef WIN32
#include 
#include 
#include 
#include 
#include 
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
#else
#include 
#include 
#include 
#include 
#include 
#include 
#endif

#include 
#include 
#include 
#include 
#include 

#ifdef _EVENT_HAVE_NETINET_IN_H
#include 
# ifdef _XOPEN_SOURCE_EXTENDED
#  include 
# endif
#endif

/* Compatibility for possible missing IPv6 declarations */
#include "../util-internal.h"

#ifdef WIN32
//#define stat _stat
//#define fstat _fstat
//#define open _open
//#define close _close
//#define O_RDONLY _O_RDONLY
#endif

char uri_root[512];

static const struct table_entry {
	const char *extension;
	const char *content_type;
} content_type_table[] = {
	{ "txt", "text/plain" },
	{ "c", "text/plain" },
	{ "h", "text/plain" },
	{ "html", "text/html" },
	{ "htm", "text/htm" },
	{ "css", "text/css" },
	{ "gif", "image/gif" },
	{ "jpg", "image/jpeg" },
	{ "jpeg", "image/jpeg" },
	{ "png", "image/png" },
	{ "pdf", "application/pdf" },
	{ "ps", "application/postsript" },
	{ NULL, NULL },
};

int from_hex(char ch)
{
	return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

std::string UrlUnescapeString(const std::string& s)
{
	std::istringstream ss(s);
	std::string result;
	std::getline(ss, result, '%');
	std::string buffer;
	while (std::getline(ss, buffer, '%'))
	{
		if (buffer.size() >= 2)
		{
			result += char((from_hex(buffer[0]) << 4) | from_hex(buffer[1])) + buffer.substr(2);
		}
	}
	return result;
}

/* Try to guess a good content-type for 'path' */
static const char *
guess_content_type(const char *path)
{
	const char *last_period, *extension;
	const struct table_entry *ent;
	last_period = strrchr(path, '.');
	if (!last_period || strchr(last_period, '/'))
		goto not_found; /* no exension */
	extension = last_period + 1;
	for (ent = &content_type_table[0]; ent->extension; ++ent) {
		if (!evutil_ascii_strcasecmp(ent->extension, extension))
			return ent->content_type;
	}

not_found:
	return "application/misc";
}

/* Callback used for the /dump URI, and for every non-GET request:
 * dumps all information to stdout and gives back a trivial 200 ok */
static void
dump_request_cb(struct evhttp_request *req, void *arg)
{
	const char *cmdtype;
	struct evkeyvalq *headers;
	struct evkeyval *header;
	struct evbuffer *buf;

	switch (evhttp_request_get_command(req)) {
	case EVHTTP_REQ_GET: cmdtype = "GET"; break;
	case EVHTTP_REQ_POST: cmdtype = "POST"; break;
	case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
	case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
	case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
	case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
	case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
	case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
	case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
	default: cmdtype = "unknown"; break;
	}

	printf("Received a %s request for %s\nHeaders:\n",
	    cmdtype, evhttp_request_get_uri(req));

	headers = evhttp_request_get_input_headers(req);
	for (header = headers->tqh_first; header;
	    header = header->next.tqe_next) {
		printf("  %s: %s\n", header->key, header->value);
	}

	buf = evhttp_request_get_input_buffer(req);
	puts("Input data: <<<");
	while (evbuffer_get_length(buf)) {
		int n;
		char cbuf[128];
		n = evbuffer_remove(buf, cbuf, sizeof(cbuf));
		if (n > 0)
			(void) fwrite(cbuf, 1, n, stdout);
	}
	puts(">>>");

	evhttp_send_reply(req, 200, "OK", NULL);
}

/* This callback gets invoked when we get any http request that doesn't match
 * any other callback.  Like any evhttp server callback, it has a simple job:
 * it must eventually call evhttp_send_error() or evhttp_send_reply().
 */

sPluginNotify m_notify;
sHttpParam mParam;

static void
send_document_cb(struct evhttp_request *req, void *arg)
{
	struct evbuffer *evb = NULL;
	const char *docroot = (const char *)arg;
	const char *uri = evhttp_request_get_uri(req);
	struct evhttp_uri *decoded = NULL;
	const char *path;
	char *decoded_path;
	char *whole_path = NULL;
	size_t len;
	int fd = -1;
	struct stat st;

	if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) {
		dump_request_cb(req, arg);
		return;
	}
	if (uri == NULL) {
		return;
	}

	printf("Got a GET request for <%s>\n", uri);
	if (strstr(uri, "favicon.ico")) {
		return;
	}

	/* Decode the URI */
	decoded = evhttp_uri_parse(uri);
	if (!decoded) {
		printf("It's not a good URI. Sending BADREQUEST\n");
		evhttp_send_error(req, HTTP_BADREQUEST, 0);
		return;
	}

	std::string uri_ansi = UrlUnescapeString(uri);
	FxLib::Type::CUtf8Buffer str_utf8(uri_ansi.c_str());
	uri = str_utf8.Get();

	/* Let's see what path the user asked for. */
	path = evhttp_uri_get_path(decoded);
	if (!path) path = "/";

	/* We need to decode it, to see what path the user really wanted. */
	decoded_path = evhttp_uridecode(path, 0, NULL);
	if (decoded_path == NULL)
		goto err;
	/* Don't allow any ".."s in the path, to avoid exposing stuff outside
	 * of the docroot.  This test is both overzealous and underzealous:
	 * it forbids aceptable paths like "/this/one..here", but it doesn't
	 * do anything to prevent symlink following." */
	if (strstr(decoded_path, ".."))
		goto err;

	/* This holds the content we're sending. */
	evb = evbuffer_new();
	/
	const char* name_prefix = strstr(uri, "name=");
	if (name_prefix == NULL) {
		goto err;
	}
	printf("client: %s\n", uri);

	evhttp_add_header(evhttp_request_get_output_headers(req),
		"Content-Type", "text/html;charset=gb2312");
		
	evbuffer_add_printf(evb, "\n");
	if(uri != NULL) evbuffer_add_printf(evb, uri);
	if (name_prefix && name_prefix + 5 != NULL) {
		evbuffer_add_printf(evb, "
\n"
); evbuffer_add_printf(evb, name_prefix + 5); } evbuffer_add_printf(evb, "\n"); evhttp_send_reply(req, 200, "OK", evb); / goto done; err: evhttp_send_error(req, 404, "Document was not found"); if (fd>=0) close(fd); done: if (decoded) evhttp_uri_free(decoded); if (decoded_path) free(decoded_path); if (whole_path) free(whole_path); if (evb) evbuffer_free(evb); } static void syntax(void) { fprintf(stdout, "Syntax: http-server \n"); } DWORD WINAPI https_createServer(LPVOID lpParam) { struct event_base *base; struct evhttp *http; struct evhttp_bound_socket *handle; // unsigned short port = (unsigned short)lpParam; if(port == 0) { std::string dllPath = "C:\\Config\\"; if (!::PathFileExists(dllPath.c_str())) { ::CreateDirectory(dllPath.c_str(), NULL); port = 5050; } else { std::string iniPath = dllPath; iniPath += "server.ini"; std::ifstream ifs(iniPath.c_str(), std::ifstream::in); std::string line; std::getline(ifs, line); if (line.size() > 5) { port = atoi(line.substr(5).c_str()); } ifs.close(); } } // #ifdef WIN32 WSADATA WSAData; WSAStartup(0x101, &WSAData); #else if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) return (1); #endif base = event_base_new(); if (!base) { fprintf(stderr, "Couldn't create an event_base: exiting\n"); return 1; } /* Create a new evhttp object to handle requests. */ http = evhttp_new(base); if (!http) { fprintf(stderr, "couldn't create evhttp. Exiting.\n"); return 1; } /* The /dump URI will dump all requests to stdout and say 200 ok. */ evhttp_set_cb(http, "/dump", dump_request_cb, NULL); /* We want to accept arbitrary requests, so we need to set a "generic" * cb. We can also add callbacks for specific paths. */ evhttp_set_gencb(http, send_document_cb, "c://"); /* Now we tell the evhttp what port to listen on */ handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port); if (!handle) { fprintf(stderr, "couldn't bind to port %d. Exiting.\n", (int)port); return 1; } { /* Extract and display the address we're listening on. */ struct sockaddr_storage ss; evutil_socket_t fd; ev_socklen_t socklen = sizeof(ss); char addrbuf[128]; void *inaddr; const char *addr; int got_port = -1; fd = evhttp_bound_socket_get_fd(handle); memset(&ss, 0, sizeof(ss)); if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) { perror("getsockname() failed"); return 1; } if (ss.ss_family == AF_INET) { got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port); inaddr = &((struct sockaddr_in*)&ss)->sin_addr; } else if (ss.ss_family == AF_INET6) { got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port); inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; } else { fprintf(stderr, "Weird address family %d\n", ss.ss_family); return 1; } addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, sizeof(addrbuf)); if (addr) { printf("Listening on %s:%d\n", addr, got_port); evutil_snprintf(uri_root, sizeof(uri_root), "http://%s:%d",addr,got_port); } else { fprintf(stderr, "evutil_inet_ntop failed\n"); return 1; } } event_base_dispatch(base); return 0; } void main() { https_createServer(5000); }

2.5 代码测试(http客户端)

#include "StdAfx.h"
#include "FxHttpClient.h"
#include 
#include 
#include 

#include "event2/http.h"
#include "event2/http_struct.h"
#include "event2/event.h"
#include "event2/buffer.h"
#include "event2/dns.h"
#include "event2/thread.h"

#include 
#include 
#include 
#include 
#include 

void RemoteReadCallback(struct evhttp_request* remote_rsp, void* arg)
{
	event_base_loopexit((struct event_base*)arg, NULL);
}

void ReadChunkCallback(struct evhttp_request* remote_rsp, void* arg)
{
	char buf[4096];
	struct evbuffer* evbuf = evhttp_request_get_input_buffer(remote_rsp);
	int n = 0;
	while ((n = evbuffer_remove(evbuf, buf, 4096)) > 0)
	{
		fwrite(buf, n, 1, stdout);
	}
}

void RemoteConnectionCloseCallback(struct evhttp_connection* connection, void* arg)
{
	fprintf(stderr, "remote connection closed\n");
	event_base_loopexit((struct event_base*)arg, NULL);
}

DWORD_PTR https_createClient(const char* dwUrl)
{
	const char* url = (const char*)dwUrl;
	if(dwUrl == NULL) url = "http://127.0.0.1:5000/hello";
	std::string strUrl = url;

	std::string dllPath = "C:\\Config\\";
	if (!::PathFileExists(dllPath.c_str())) {
		::CreateDirectory(dllPath.c_str(), NULL);
	}
	else if (*url == '#'){
		std::string iniPath = dllPath;
		iniPath += "client.ini";
		std::ifstream ifs(iniPath.c_str(), std::ifstream::in);
		if (!ifs || ifs.is_open() == false){  
			std::cout << "fail to open the file" << std::endl;  
			return -1;
		}else{ 
			std::cout << "open the file successfully" << std::endl;
		}

		std::string line;
		std::string cmdName = "cmd";
		cmdName += (url+1);
		
		while (std::getline(ifs, line)) {
			const char* line_ptr = line.c_str();
			if(strstr(line_ptr, cmdName.c_str()) == line_ptr) {
				int pos = line.find("=") + 1;
				std::string line_text = line.substr(pos);
				strUrl = line_text.c_str();
				break;
			}
		}
		ifs.close();
	}
	url = strUrl.c_str();

	struct evhttp_uri* uri = evhttp_uri_parse(url);
	if (!uri)
	{
		fprintf(stderr, "parse url failed!\n");
		return 1;
	}

	struct event_base* base = event_base_new();
	if (!base)
	{
		fprintf(stderr, "create event base failed!\n");
		return 1;
	}

	struct evdns_base* dnsbase = evdns_base_new(base, 1);
	if (!dnsbase)
	{
		fprintf(stderr, "create dns base failed!\n");
	}
	assert(dnsbase);

	struct evhttp_request* request = evhttp_request_new(RemoteReadCallback, base);
	//evhttp_request_set_header_cb(request, ReadHeaderDoneCallback);
	evhttp_request_set_chunked_cb(request, ReadChunkCallback);
	//evhttp_request_set_error_cb(request, RemoteRequestErrorCallback);

	const char* host = evhttp_uri_get_host(uri);
	if (!host)
	{
		fprintf(stderr, "parse host failed!\n");
		return 1;
	}

	int port = evhttp_uri_get_port(uri);
	if (port < 0) port = 80;

	const char* request_url = url;
	const char* path = evhttp_uri_get_path(uri);
	if (path == NULL || strlen(path) == 0)
	{
		request_url = "/";
	}

	printf("url:%s host:%s port:%d path:%s request_url:%s\n", url, host, port, path, request_url);

	struct evhttp_connection* connection = evhttp_connection_base_new(base, dnsbase, host, port);
	if (!connection)
	{
		fprintf(stderr, "create evhttp connection failed!\n");
		return 1;
	}

	evhttp_connection_set_closecb(connection, RemoteConnectionCloseCallback, base);

	evhttp_add_header(evhttp_request_get_output_headers(request), "Host", host);
	evhttp_make_request(connection, request, EVHTTP_REQ_GET, request_url);

	event_base_dispatch(base);

	return 0;
}

void main()
{
	https_createClient("http://127.0.0.1:5000/hello");
}

3、libcurl

3.1 下载

https://curl.se/download.html
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第15张图片

3.2 编译

解压上面下载的源代码压缩包如下:
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第16张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第17张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第18张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第19张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第20张图片
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第21张图片

3.3 代码测试(http客户端)

/* 
 * simple HTTP POST using the easy interface
 * 
 */
#include 
#include 

int main(void)
{
  CURL *curl;
  CURLcode res;

  /* In windows, this will init the winsock stuff */
  curl_global_init(CURL_GLOBAL_ALL);

  /* get a curl handle */
  curl = curl_easy_init();
  if(curl) {
    /* First set the URL that is about to receive our POST. This URL can
       just as well be a https:// URL if that is what should receive the
       data. */
    curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1/login");
    /* Now specify the POST data */
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "username=test");

    /* Perform the request, res will get the return code */
    res = curl_easy_perform(curl);
    /* Check for errors */
    if(res != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));

    /* always cleanup */
    curl_easy_cleanup(curl);
  }
  curl_global_cleanup();
  return 0;
}

3.4 命令行

  • 输出帮助信息
curl --help

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第22张图片

  • GET 请求
curl https://www.baidu.com/         # GET请求, 输出 响应内容
curl -I https://www.baidu.com/      # GET请求, 只输出 响应头
curl -i https://www.baidu.com/      # GET请求, 输出 响应头、响应内容
curl -v https://www.baidu.com/      # GET请求, 输出 通讯过程、头部信息、响应内容等
curl https://www.baidu.com -o baidu.txt  #下载文件
curl https://www.baidu.com/index.html -O #下载文件
#下载文件
curl -A "Mozilla/5.0 Chrome/70.0.3538.110 Safari/537.36" \
     -e "https://www.baidu.com/" \
     https://www.baidu.com/index.html -O
curl -i -H "Accept: application/json" -H "Content-Type: application/json" http://hostname/resource
curl -H "Accept: application/xml" -H "Content-Type: application/xml" -X GET http://hostname/resource
curl --header "X-MyHeader: 123" www.baidu.com

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第23张图片

  • POST 请求
# POST 提交 JSON 数据
curl -H "Content-Type: application/json"                \
     -d '{"name":"test", "type":"100"}'     \
     http://localhost/login
 
# POST 提交 表单数据
curl -F "name=test"                \
     -F "type=100"               \
     http://localhost/login
curl --data "param1=value1¶m2=value2" http://hostname/resource
curl --form "[email protected]" http://hostname/resource
curl -X POST -d @filename http://hostname/resource
curl -d "username=admin&password=admin&submit=Login" --dump-header headers http://localhost/Login curl -L -b headers http://localhost/
curl -X POST  \
--header 'Content-Type: application/json' \
--header 'Accept: application/json'  \
--header 'appkey:key'  \
--header 'appsign=sign'  \
--header 'signmethod:md5'  \
--header 'deviceType:1'  \
--header 'deviceId:1'  \
-d '{"city":"beijing","country":"China","headimg":"https://www.test.com/head.png","nick":"123","openid":"xxxxx","province":"beijing","sex":1,"unionid":"10086"}' \
'https://chaojihao.net/user/transfer'
  • curl: (6) Could not resolve host无法解析主机地址
    参数转义有问题, 建议你都用双引号, 别用单引号, 对参数内部双引号用** 转义:
curl --header "Content-Type: application/json" --data "{\"personalizations\": [{\"to\": [{\"email\": \"[email protected]\"}]}],\"from\": {\"email\": \"[email protected]\"},\"subject\": \"Hello, World!\",\"content\": [{\"type\": \"text/plain\", \"value\": \"egg!\"}]}" 

4、poco

POCO C++ Libraries 项目由Günter Obiltschnig ( @obiltschnig ) 于 2004 年启动。当时 C++ 的流行度正迅速达到绝对低点,因为几乎每个人都在追随基于托管和基于虚拟机的编程语言的趋势。尽管如此,Günter 还是相信 C++。他想创建一套全面的库来满足所有现代编程需求。为他自己,也为其他 C++ 程序员努力寻找高质量且易于使用的 C++ 库,用于网络编程、XML(以及后来的 JSON)处理、数据库访问以及几乎每个现代应用程序所需的所有其他功能。

4.1 下载

https://pocoproject.org/download.html

(1)通过github下载源代码

git clone -b master https://github.com/pocoproject/poco.git

(2)通过官网提供的压缩包下载源代码
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第24张图片

4.2 编译

源码下载之后,可以通过两种方式编译。
(1)运行bat文件生成vs工程文件
(2)通过cmake生成vs工程文件

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第25张图片

4.3 代码测试(http客户端)

#include "Poco/Net/HTTPClientSession.h"
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPCredentials.h"
#include "Poco/StreamCopier.h"
#include "Poco/NullStream.h"
#include "Poco/Path.h"
#include "Poco/URI.h"
#include "Poco/Exception.h"
#include 


using Poco::Net::HTTPClientSession;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPMessage;
using Poco::StreamCopier;
using Poco::Path;
using Poco::URI;
using Poco::Exception;


bool doRequest(Poco::Net::HTTPClientSession& session, Poco::Net::HTTPRequest& request, Poco::Net::HTTPResponse& response)
{
	session.sendRequest(request);
	std::istream& rs = session.receiveResponse(response);
	std::cout << response.getStatus() << " " << response.getReason() << std::endl;
	if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_UNAUTHORIZED)
	{
		StreamCopier::copyStream(rs, std::cout);
		return true;
	}
	else
	{
		Poco::NullOutputStream null;
		StreamCopier::copyStream(rs, null);
		return false;
	}
}


int main(int argc, char** argv)
{
	if (argc != 2)
	{
		Path p(argv[0]);
		std::cout << "usage: " << p.getBaseName() << " " << std::endl;
		std::cout << "       fetches the resource identified by  and print it to the standard output" << std::endl;
		return 1;
	}

	try
	{
		URI uri(argv[1]);
		std::string path(uri.getPathAndQuery());
		if (path.empty()) path = "/";

		std::string username;
		std::string password;
		Poco::Net::HTTPCredentials::extractCredentials(uri, username, password);
		Poco::Net::HTTPCredentials credentials(username, password);

		HTTPClientSession session(uri.getHost(), uri.getPort());
		HTTPRequest request(HTTPRequest::HTTP_GET, path, HTTPMessage::HTTP_1_1);
		HTTPResponse response;
		if (!doRequest(session, request, response))
		{
			credentials.authenticate(request, response);
			if (!doRequest(session, request, response))
			{
				std::cerr << "Invalid username or password" << std::endl;
				return 1;
			}
		}
	}
	catch (Exception& exc)
	{
		std::cerr << exc.displayText() << std::endl;
		return 1;
	}
	return 0;
}

4.4 代码测试(http服务端)

#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/HTMLForm.h"
#include "Poco/Net/PartHandler.h"
#include "Poco/Net/MessageHeader.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/CountingStream.h"
#include "Poco/NullStream.h"
#include "Poco/StreamCopier.h"
#include "Poco/Exception.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include 

using Poco::Net::ServerSocket;
using Poco::Net::HTTPRequestHandler;
using Poco::Net::HTTPRequestHandlerFactory;
using Poco::Net::HTTPServer;
using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPServerResponse;
using Poco::Net::HTTPServerParams;
using Poco::Net::MessageHeader;
using Poco::Net::HTMLForm;
using Poco::Net::NameValueCollection;
using Poco::Util::ServerApplication;
using Poco::Util::Application;
using Poco::Util::Option;
using Poco::Util::OptionSet;
using Poco::Util::HelpFormatter;
using Poco::CountingInputStream;
using Poco::NullOutputStream;
using Poco::StreamCopier;

class MyPartHandler: public Poco::Net::PartHandler
{
public:
	MyPartHandler():
		_length(0)
	{
	}

	void handlePart(const MessageHeader& header, std::istream& stream)
	{
		_type = header.get("Content-Type", "(unspecified)");
		if (header.has("Content-Disposition"))
		{
			std::string disp;
			NameValueCollection params;
			MessageHeader::splitParameters(header["Content-Disposition"], disp, params);
			_name = params.get("name", "(unnamed)");
			_fileName = params.get("filename", "(unnamed)");
		}

		CountingInputStream istr(stream);
		NullOutputStream ostr;
		StreamCopier::copyStream(istr, ostr);
		_length = istr.chars();
	}

	int length() const
	{
		return _length;
	}

	const std::string& name() const
	{
		return _name;
	}

	const std::string& fileName() const
	{
		return _fileName;
	}

	const std::string& contentType() const
	{
		return _type;
	}

private:
	int _length;
	std::string _type;
	std::string _name;
	std::string _fileName;
};

class FormRequestHandler: public HTTPRequestHandler
	/// Return a HTML document with the current date and time.
{
public:
	FormRequestHandler()
	{
	}

	void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response)
	{
		Application& app = Application::instance();
		app.logger().information("Request from " + request.clientAddress().toString());

		MyPartHandler partHandler;
		HTMLForm form(request, request.stream(), partHandler);

		response.setChunkedTransferEncoding(true);
		response.setContentType("text/html");

		std::ostream& ostr = response.send();

		ostr <<
			"\n"
			"\n"
			"POCO Form Server Sample\n"
			"\n"
			"\n"
			"

POCO Form Server Sample

\n"
"

GET Form

\n"
"
\n" "\n" "\n" "\n"
"

POST Form

\n"
"
\n" "\n" "\n" "\n"
"

File Upload

\n"
"
\n" " \n" "\n" "\n"
; ostr << "

Request

\n"; ostr << "Method: " << request.getMethod() << "
\n"
; ostr << "URI: " << request.getURI() << "
\n"
; NameValueCollection::ConstIterator it = request.begin(); NameValueCollection::ConstIterator end = request.end(); for (; it != end; ++it) { ostr << it->first << ": " << it->second << "
\n"
; } ostr << "

"
; if (!form.empty()) { ostr << "

Form

\n"; it = form.begin(); end = form.end(); for (; it != end; ++it) { ostr << it->first << ": " << it->second << "
\n"
; } ostr << "

"
; } if (!partHandler.name().empty()) { ostr << "

Upload

\n"; ostr << "Name: " << partHandler.name() << "
\n"
; ostr << "File Name: " << partHandler.fileName() << "
\n"
; ostr << "Type: " << partHandler.contentType() << "
\n"
; ostr << "Size: " << partHandler.length() << "
\n"
; ostr << "

"
; } ostr << "\n"; } }; class FormRequestHandlerFactory: public HTTPRequestHandlerFactory { public: FormRequestHandlerFactory() { } HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) { return new FormRequestHandler; } }; class HTTPFormServer: public Poco::Util::ServerApplication /// To test the FormServer you can use any web browser (http://localhost:9980/). { public: HTTPFormServer(): _helpRequested(false) { } ~HTTPFormServer() { } protected: void initialize(Application& self) { loadConfiguration(); // load default configuration files, if present ServerApplication::initialize(self); } void uninitialize() { ServerApplication::uninitialize(); } void defineOptions(OptionSet& options) { ServerApplication::defineOptions(options); options.addOption( Option("help", "h", "display help information on command line arguments") .required(false) .repeatable(false)); } void handleOption(const std::string& name, const std::string& value) { ServerApplication::handleOption(name, value); if (name == "help") _helpRequested = true; } void displayHelp() { HelpFormatter helpFormatter(options()); helpFormatter.setCommand(commandName()); helpFormatter.setUsage("OPTIONS"); helpFormatter.setHeader("A web server that shows how to work with HTML forms."); helpFormatter.format(std::cout); } int main(const std::vector<std::string>& args) { if (_helpRequested) { displayHelp(); } else { unsigned short port = (unsigned short) config().getInt("HTTPFormServer.port", 9980); // set-up a server socket ServerSocket svs(port); // set-up a HTTPServer instance HTTPServer srv(new FormRequestHandlerFactory, svs, new HTTPServerParams); // start the HTTPServer srv.start(); // wait for CTRL-C or kill waitForTerminationRequest(); // Stop the HTTPServer srv.stop(); } return Application::EXIT_OK; } private: bool _helpRequested; }; int main(int argc, char** argv) { HTTPFormServer app; return app.run(argc, argv); }

5、libuv

libuv 是一个专注于异步 I/O 的多平台支持库。它主要是为Node.js开发的,但也被Luvit、 Julia、uvloop和其他人使用。

libuv特点如下:

  • 由 epoll、kqueue、IOCP、事件端口支持的全功能事件循环。
  • 异步 TCP 和 UDP 套接字
  • 异步 DNS 解析
  • 异步文件和文件系统操作
  • 文件系统事件
  • ANSI 转义码控制的 TTY
  • 具有套接字共享的 IPC,使用 Unix 域套接字或命名管道 (Windows)
  • 子进程
  • 线程池
  • 信号处理
  • 高分辨率时钟
  • 线程和同步原语

5.1 下载

http://libuv.org/
【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第26张图片

  • (1)官网
    https://dist.libuv.org/dist/
    【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第27张图片

  • (2)github
    https://github.com/libuv/libuv

【小沐学C++】C++实现Web服务器(libevent,libcurl,libuv,poco)_第28张图片

5.2 编译

通过CMake直接构建相应工程,进行编译。

5.3 代码测试(http客户端、服务端)

相关代码请参考如下网友的代码:
(1)libuv-webserver
https://github.com/bodokaiser/libuv-webserver

(2)基于libuv封装的http server client 服务端用法
https://github.com/zhs077/Http

结语

如果您觉得该方法或代码有一点点用处,可以给作者点个赞,或打赏杯咖啡;╮( ̄▽ ̄)╭
如果您感觉方法或代码不咋地//(ㄒoㄒ)//,就在评论处留言,作者继续改进;o_O???
如果您需要相关功能的代码定制化开发,可以留言私信作者;(✿◡‿◡)
感谢各位大佬童鞋们的支持!( ´ ▽´ )ノ ( ´ ▽´)っ!!!

在这里插入图片描述

你可能感兴趣的:(C/C++,Web,c++,http服务器,http,client,http,server,cJSON)