Web服务器系列相关文章编写如下:
- 【Web开发】Node.js实现Web服务器(http模块)
- 【Web开发】Node.js实现Web服务器(express模块)
- 【Web开发】Python实现Web服务器(Flask入门)
- 【Web开发】Python实现Web服务器(Flask测试)
- 【Web开发】Python实现Web服务器(Tornado入门)
- 【Web开发】Python实现Web服务器(Tornado+flask+nginx)
- 【Web开发】Python实现Web服务器(FastAPI)
- 【Web开发】Android手机上基于Termux实现Web服务器(Python、node.js)
- 【Web开发】C++实现Web服务器(libevent,libcurl)
- 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 命令行
- 结语
官网地址:
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 框架。
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 兼容、功能丰富、支持良好、速度快、文档完整,并且已被许多知名、大型和成功的公司使用。
详细介绍请查看本人另外一篇文章:
https://blog.csdn.net/hhy321/article/details/125922276
cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。
https://github.com/DaveGamble/cJSON
- 从git下载源代码
- 源代码文件夹如下:
mkdir build cd build cmake .. # or cmake .. -DENABLE_CJSON_UTILS=On -DENABLE_CJSON_TEST=Off
- 生成的工程文件如下:
- 举例说明:
这里有一个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; } 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字符串如下:
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: %sn", 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("构建:%sn", str_json); cJSON_Delete(root); root = NULL; cJSON *root2; root2 = cJSON_Parse(str_json); cJSON *result = cJSON_GetObjectItem(root2, "name"); if(result) printf("解析name:%sn", result->valuestring); result = cJSON_GetObjectItem(root2, "age"); if (result) printf("解析age:%sn", result->valuestring); result = cJSON_GetObjectItem(root2, "city"); if (result) printf("解析city:%sn", result->valuestring);2、libevent 2.1 下载
https://github.com/libevent/libevent
采用最新的版本2.1.12-stable。
采用比较早期版本,比如2.0.22-stable。
- 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
cd C:UserstomcatDesktoplibevent-2.0.22-stable.tarlibevent-2.0.22-stable nmake Makefile.nmake
考虑到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_libs2.4 代码测试(http服务端)
#include2.5 代码测试(http客户端)#include #include #pragma comment(lib, "libevent\libevent.lib") #pragma comment(lib, "libevent\libevent_core.lib") #pragma comment(lib, "libevent\libevent_extras.lib") #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 #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; } 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; 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"; } 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 %snHeaders: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: %sn", 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); } 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; } decoded = evhttp_uri_parse(uri); if (!decoded) { printf("It's not a good URI. Sending BADREQUESTn"); 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(); path = evhttp_uri_get_path(decoded); if (!path) path = "/"; decoded_path = evhttp_uridecode(path, 0, NULL); if (decoded_path == NULL) goto err; if (strstr(decoded_path, "..")) goto err; evb = evbuffer_new(); / const char* name_prefix = strstr(uri, "name="); if (name_prefix == NULL) { goto err; } printf("client: %sn", 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-servern"); } 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: exitingn"); return 1; } http = evhttp_new(base); if (!http) { fprintf(stderr, "couldn't create evhttp. Exiting.n"); return 1; } evhttp_set_cb(http, "/dump", dump_request_cb, NULL); evhttp_set_gencb(http, send_document_cb, "c://"); 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; } { 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 %dn", ss.ss_family); return 1; } addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, sizeof(addrbuf)); if (addr) { printf("Listening on %s:%dn", addr, got_port); evutil_snprintf(uri_root, sizeof(uri_root), "http://%s:%d",addr,got_port); } else { fprintf(stderr, "evutil_inet_ntop failedn"); return 1; } } event_base_dispatch(base); return 0; } void main() { https_createServer(5000); }
#include "StdAfx.h" #include "FxHttpClient.h" #include3、libcurl 3.1 下载#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 closedn"); 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:%sn", 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"); }
https://curl.se/download.html
解压上面下载的源代码压缩包如下:
#include3.4 命令行#include int main(void) { CURL *curl; CURLcode res; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1/login"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "username=test"); res = curl_easy_perform(curl); if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %sn", curl_easy_strerror(res)); curl_easy_cleanup(curl); } curl_global_cleanup(); return 0; }
- 输出帮助信息
curl --help
- 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
- 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结语
如果您觉得该方法或代码有一点点用处,可以给作者点个赞,或打赏杯咖啡;╮( ̄▽ ̄)╭
如果您感觉方法或代码不咋地//(ㄒoㄒ)//,就在评论处留言,作者继续改进;o_O???
如果您需要相关功能的代码定制化开发,可以留言私信作者;(✿◡‿◡)
感谢各位大佬童鞋们的支持!( ´ ▽´ )ノ ( ´ ▽´)っ!!!