目录1.基于asio2实现的http示例代码2.修改服务线程数3.在绑定的路由请求中获取请求的远程的IP和端口4.静态资源绑定5.在 url 中传递参数的原理5.1.简介5.2.浏览器本地预处理输入 URL 后发请求前5.3.建立 TCP 连接5.4.浏览器发送 HTTP 请求报文5.5.服务端asio2接收、解析、处理请求5.6.服务端返回 HTTP 响应报文5.7.浏览器接收响应解析渲染页面5.8.连接维持与关闭5.9.服务端把参数传给前端的两种方式6.唯一标识用户还是需要token7.跨域处理8.QtWebEngine 拼接 URL 携带 Token9.asio2 内置 URL 工具http 命名空间Asio2: 一个基于 Boost.Asio 封装的高性能网络编程库(一)1.基于asio2实现的http示例代码源码如下#include iostream #include asio2/asio2.hpp struct aop_log { bool before(http::web_request req, http::web_response rep) { asio2::ignore_unused(rep); printf(aop_log before %s\n, req.method_string().data()); return true; } bool after(std::shared_ptrasio2::http_session session_ptr, http::web_request req, http::web_response rep) { ASIO2_ASSERT(asio2::get_current_callerstd::shared_ptrasio2::http_session().get() session_ptr.get()); asio2::ignore_unused(session_ptr, req, rep); printf(aop_log after\n); return true; } }; struct aop_check { bool before(std::shared_ptrasio2::http_session session_ptr, http::web_request req, http::web_response rep) { ASIO2_ASSERT(asio2::get_current_callerstd::shared_ptrasio2::http_session().get() session_ptr.get()); asio2::ignore_unused(session_ptr, req, rep); printf(aop_check before\n); return true; } bool after(http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); printf(aop_check after\n); return true; } }; int main() { // try open http://localhost:8080 in your browser std::string_view host 0.0.0.0; std::string_view port 8080; asio2::http_server server; server.support_websocket(true); // root /asio2/bin/x64 std::filesystem::path root std::filesystem::current_path(); // root /asio2/example/wwwroot root root.parent_path().parent_path().append(example/wwwroot); // set the http server root directory server.set_root_directory(std::move(root)); server.bind_recv([](http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); // all http and websocket request will goto here first. std::cout req.path() req.query() std::endl; }).bind_connect([](auto session_ptr) { printf(client enter : %s %u %s %u\n, session_ptr-remote_address().c_str(), session_ptr-remote_port(), session_ptr-local_address().c_str(), session_ptr-local_port()); //session_ptr-set_response_mode(asio2::response_mode::manual); session_ptr-set_disconnect_timeout(std::chrono::seconds(2)); }).bind_disconnect([](auto session_ptr) { printf(client leave : %s %u %s\n, session_ptr-remote_address().c_str(), session_ptr-remote_port(), asio2::last_error_msg().c_str()); }).bind_start([]() { printf(start http server : %s %u %d %s\n, server.listen_address().c_str(), server.listen_port(), asio2::last_error_val(), asio2::last_error_msg().c_str()); }).bind_stop([]() { printf(stop http server : %d %s\n, asio2::last_error_val(), asio2::last_error_msg().c_str()); }); server.bindhttp::verb::get, http::verb::post(/, [](http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); rep.fill_file(/index.html); }, aop_log{}, http::enable_cache); // If no method is specified, GET and POST are both enabled by default. server.bind(*, [](http::web_request req, http::web_response rep) { rep.fill_file(http::url_decode(req.target())); rep.chunked(true); }, aop_check{}, http::enable_cache); // Test http multipart server.bindhttp::verb::get, http::verb::post(/multipart_test, [](http::web_request req, http::web_response rep) { auto body req.body(); auto target req.target(); std::stringstream ss; ss req; auto str_req ss.str(); asio2::ignore_unused(body, target, str_req); for (auto it req.begin(); it ! req.end(); it) { std::cout it-name_string() : it-value() std::endl; } auto mpbody req.multipart(); for (auto it mpbody.begin(); it ! mpbody.end(); it) { std::cout filename: it-filename() name: it-name() content_type: it-content_type() std::endl; } if (req.has_multipart()) { http::multipart_fields multipart_body req.multipart(); auto field_username multipart_body[username]; auto username field_username.value(); auto field_password multipart_body[password]; auto password field_password.value(); std::string str http::to_string(multipart_body); std::string type multipart/form-data; boundary; type multipart_body.boundary(); rep.fill_text(str, http::status::ok, type); http::web_request req_copy; req_copy.method(http::verb::post); req_copy.set(http::field::content_type, type); req_copy.keep_alive(true); req_copy.target(/api/user/); req_copy.body() str; req_copy.prepare_payload(); std::stringstream ress; ress req_copy; auto restr ress.str(); asio2::ignore_unused(username, password, restr); } else { rep.fill_page(http::status::ok); } }, aop_log{}); server.bindhttp::verb::get(/del_user, [](std::shared_ptrasio2::http_session session_ptr, http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); printf(del_user ip : %s\n, session_ptr-remote_address().data()); rep.fill_page(http::status::ok, del_user successed.); }, aop_check{}); server.bindhttp::verb::get, http::verb::post(/api/user/*, [](http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); // if the url is : // /api/user/add_user?namehanmeimeiage20height168 [[maybe_unused]] std::string_view t req.target(); // t /api/user/add_user?namehanmeimeiage20height168 [[maybe_unused]] std::string_view p req.get_path(); // p /api/user/add_user [[maybe_unused]] std::string_view q req.get_query(); // q namehanmeimeiage20height168 std::vectorstd::string_view kvs asio2::split(q, ); for (std::string_view kv : kvs) { [[maybe_unused]] std::string_view k kv.substr(0, kv.find()); [[maybe_unused]] std::string_view v kv.substr(kv.find() 1); } //rep.fill_text(the user name is hanmeimei, .....); }, aop_log{}, aop_check{}); server.bindhttp::verb::get(/defer, [](http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); // use defer to make the reponse not send immediately, util the derfer shared_ptr // is destroyed, then the response will be sent. std::shared_ptrhttp::response_defer rep_defer rep.defer(); std::thread([rep_defer, rep]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); rep asio2::http_client::execute(http://www.baidu.com); }).detach(); }, aop_log{}, aop_check{}); // the /ws is the websocket upgraged target server.bind(/ws, websocket::listenerasio2::http_session{}. on(message, [](std::shared_ptrasio2::http_session session_ptr, std::string_view data) { printf(ws msg : %zu %.*s\n, data.size(), (int)data.size(), data.data()); session_ptr-async_send(data); }).on(open, [](std::shared_ptrasio2::http_session session_ptr) { printf(ws open\n); // print the websocket request header. std::cout session_ptr-request() std::endl; // same as above auto req session_ptr-get_request(); std::cout req.get_path() std::endl; // Set the binary message write option. session_ptr-ws_stream().binary(true); // Set the text message write option. //session_ptr-ws_stream().text(true); // how to set custom websocket response data : session_ptr-ws_stream().set_option(websocket::stream_base::decorator( [](websocket::response_type rep) { rep.set(http::field::authorization, http-server-coro); })); // how to send a message to client when the websocket connection is connected. // cant use session_ptr-async_send(...) directly, because the websocket connection is not ready. session_ptr-post_queued_event([session_ptr]() { session_ptr-async_send(eg: hello websocket); }); }).on(close, [](std::shared_ptrasio2::http_session session_ptr) { asio2::ignore_unused(session_ptr); printf(ws close\n); }).on_ping([](std::shared_ptrasio2::http_session session_ptr) { asio2::ignore_unused(session_ptr); printf(ws ping\n); }).on_pong([](std::shared_ptrasio2::http_session session_ptr) { asio2::ignore_unused(session_ptr); printf(ws pong\n); })); server.bind_not_found([](http::web_request req, http::web_response rep) { asio2::ignore_unused(req); rep.fill_page(http::status::not_found); }); server.start(host, port); while (std::getchar() ! \n); // press enter to exit this program return 0; }2.修改服务线程数如果不人为控制asio2内部会自动获取系统的CPU核数启动对于核数的线程如果控制启动的线程数可以手动传入参数如asio2::http_server server(1); //1个线程http客户端(如浏览器)发送请求是并发的服务端多线程处理就会涉及到并发的去访问文件、数据库、网络等资源设计的时候需要考虑到。3.在绑定的路由请求中获取请求的远程的IP和端口server.bindhttp::verb::get, http::verb::post(/, [](http::web_request req, http::web_response rep) { asio2::ignore_unused(req, rep); rep.fill_file(/index.html); }, aop_log{}, http::enable_cache);在 asio2 库中不能直接从http::web_request对象获取远端的 IP 和端口。http::web_request只封装了 HTTP 请求本身的内容如方法、路径、头部等不包含底层连接信息。asio2 在绑定 HTTP 路由回调时允许将会话对象指针std::shared_ptrasio2::http_session作为第一个参数传入。你需要通过这个会话对象来获取远端地址和端口。server.bindhttp::verb::get(/, [](std::shared_ptrasio2::http_session session_ptr, http::web_request req, http::web_response rep) { // 获取远端 IP 和端口 std::string remote_ip session_ptr-remote_address(); uint16_t remote_port session_ptr-remote_port(); printf(client ip: %s, port: %u\n, remote_ip.c_str(), remote_port); rep.fill_page(http::status::ok, success); }, aop_check{});关键点说明session_ptr是回调的第一个参数asio2 允许你在 HTTP 回调签名中选择是否添加这个参数。如果不需要远端信息可以省略它如果需要就把它加上。remote_address()返回std::string_view可以直接.data()或转换为std::string使用。remote_port()返回端口号类型通常为uint16_t。4.静态资源绑定// If no method is specified, GET and POST are both enabled by default. server.bind(*, [](http::web_request req, http::web_response rep) { rep.fill_file(http::url_decode(req.target())); rep.chunked(true); }, aop_check{}, http::enable_cache);req的路径函数对应含义req.path()服务器切开?后得到的路径部分req.query()服务器切开?后得到的原始参数字符串req.target()完整原始请求目标path ? queryreq.body()body内容parse_query(raw_q)按分割、按拆键值、执行 URL 解码rep.fill_file(req.path())只用 path 拼接 root_dir 找文件query 完全不参与// 完整原始target std::string full_target req.target();//如/index.html?a10b10 // 拆分好的路径 std::string path req.path(); //如/index.html // 拆分好的查询串 std::string query_raw req.query();//如a10b10如果在主目录下有个index.html那么就可以通过下面的地址访问http://192.168.3.62:8080/index.html5.在 url 中传递参数的原理5.1.简介基于RFC 3986 URI 标准和 HTTP 协议规范URL 参数Query String本质是在请求行中附加的键值对信息和资源路径严格分离全程不影响服务端文件定位。一个完整 HTTP URL 的结构如下?是路径和参数的法定分隔符http://127.0.0.1:8080/index.html?uid1001name张三#section └──┬───┘ └────┬────┘ └──┬───┘ └────────┬────────┘ └──┬───┘ 协议 主机:端口 路径 查询参数 锚点(fragment)部分作用是否发给服务端路径/index.html定位服务器上的资源 / 路由是查询参数uid1001name张三传递业务参数是锚点#section浏览器内页面定位否完全不发给服务器核心结论服务器解析 URL 时会在第一个?处直接切开前面的部分作为资源路径后面的部分作为纯参数字符串。 这就是你之前观察到的「asio2 自动分开 path 和 query」的底层原因 —— 所有标准 HTTP 服务器都是这么实现的。整个过程是「浏览器封装编码 → 网络传输 → 服务端解析处理 → 网络返回 → 浏览器解析渲染」的完整闭环底层基于 TCP/IP 协议栈和 HTTP 应用层协议。5.2.浏览器本地预处理输入 URL 后发请求前用户在地址栏输入 URL 并回车浏览器先做 4 件事1. 拆分 URL 结构按标准把 URL 拆成独立字段?是路径和参数的法定分界符协议: http 主机: 127.0.0.1 端口: 8080 路径: /index.html 查询参数: uid1001name张三tokenabc1232.URL 编码百分号编码URL 只允许 ASCII 字符中文、空格、特殊符号会自动编码张三→%E5%BC%A0%E4%B8%89空格 →%20目的避免中文乱码、避免特殊字符如、破坏参数结构对应你代码里的url_decode就是做这个的逆过程。3. 缓存检查浏览器先查本地缓存强缓存 / 协商缓存如果页面之前访问过且没过期直接从本地加载不发请求到服务器。4. DNS 解析域名转 IP如果是域名如www.test.com浏览器会通过 DNS 服务器把域名解析成 IP 地址 你用127.0.0.1是本地回环地址直接跳过 DNS 解析。5.3.建立 TCP 连接HTTP 基于 TCP 传输浏览器和服务器通过TCP 三次握手建立可靠连接浏览器 → 服务器SYN 同步包服务器 → 浏览器SYNACK 确认包浏览器 → 服务器ACK 确认包连接建立后才能开始传输 HTTP 报文。 如果是 HTTPS这一步还会额外做 TLS 加密握手。5.4.浏览器发送 HTTP 请求报文连接建立后浏览器把请求内容封装成标准 HTTP 报文通过 TCP 发出去。HTTP 请求报文结构【请求行】GET /index.html?uid1001name%E5%BC%A0%E4%B8%89tokenabc123 HTTP/1.1 【请求头】 Host: 127.0.0.1:8080 User-Agent: Chrome/120.0.0.0 Accept: text/html,application/xhtmlxml Connection: keep-alive ... 【空行】 【请求体】GET 请求体为空POST 才会放数据核心要点参数在请求行的 URL 里不在请求体里GET 请求的参数永远跟在路径后面不会放进 body。参数已经编码完成name张三变成了name%E5%BC%A0%E4%B8%89。整个报文是纯文本格式按行分隔。5.5.服务端asio2接收、解析、处理请求这是你写的 C 代码真正执行的阶段asio2 内部按以下步骤执行步骤 1接收并解析 HTTP 报文asio2 从 TCP 连接读取数据按 HTTP 协议解析出请求方法GET完整 URL/index.html?uid1001name%E5%BC%A0%E4%B8%89tokenabc123所有请求头步骤 2拆分路径和查询参数在第一个?处直接切开分成两部分存储req.path()→/index.html只用来匹配路由、找文件req.query()→uid1001name%E5%BC%A0%E4%B8%89tokenabc123纯参数字符串✅ 这就是「带参数也能正常访问静态文件」的根本原因查询参数根本不参与文件路径拼接找文件只用 path 部分。步骤 3路由匹配asio2 按你写bind的从上到下顺序用req.path()去匹配每个路由的正则先匹配/api/echo等业务接口 → 不匹配再匹配最后的.*静态兜底路由 → 匹配成功执行对应的回调函数步骤 4执行业务回调你写的代码逻辑调用parse_query(req.query())按分割、按拆键值、做 URL 解码得到参数 mapToken 校验取出token判断是否合法不合法直接返回 401终止流程静态资源处理如果是index.html调用rep.load_file()读取 HTML 文件把{{uid}}、{{name}}替换成参数值服务端模板注入如果是 css/js调用rep.fill_file()直接读取文件内容设置 CORS 跨域响应头、Content-Type 等步骤 5构建 HTTP 响应报文asio2 把处理结果封装成标准 HTTP 响应报文包含状态行HTTP/1.1 200 OK响应头Content-Type、Access-Control-Allow-Origin等响应体处理后的 HTML 内容 / 文件二进制数据5.6.服务端返回 HTTP 响应报文asio2 把封装好的响应报文通过 TCP 连接发回给浏览器。HTTP 响应报文示例【状态行】HTTP/1.1 200 OK 【响应头】 Content-Type: text/html;charsetutf-8 Content-Length: 1256 Access-Control-Allow-Origin: * Connection: keep-alive 【空行】 【响应体】 !DOCTYPE html html...完整的HTML页面内容占位符已经替换成实际参数值5.7.浏览器接收响应解析渲染页面浏览器收到响应后按以下步骤处理最终显示出页面1. 解析响应读取状态码200 表示成功404 表示找不到401 表示未授权读取响应头根据Content-Type判断内容类型接收完整响应体2. HTML 解析 → DOM 树浏览器把 HTML 文本从上到下解析构建 DOM文档对象模型树页面上的每个标签都是树上的一个节点。如果是服务端模板注入的参数{{uid}}解析后直接就是实际值页面上立刻能看到。3. CSS 解析 → 渲染树解析 CSS 样式把 DOM 树和样式结合生成渲染树计算每个元素的位置、大小、颜色。4. JS 执行遇到script标签就暂停解析执行 JS 代码如果是前端 JS 读取 URL 参数JS 通过window.location.search拿到地址栏里的 query 字符串用URLSearchParams解析出 uid、name 等参数如果有 fetch/axios 请求JS 再发起新的 HTTP 请求去调用后端 API5. 绘制与显示浏览器根据渲染树把页面像素绘制到屏幕上用户最终看到完整的网页。5.8.连接维持与关闭默认开启Connection: keep-alive长连接同一个域名的后续请求复用这个 TCP 连接不用每次都三次握手连接空闲一段时间后通过TCP 四次挥手关闭连接5.9.服务端把参数传给前端的两种方式方式环节原理服务端模板注入阶段 4 步骤 4asio2 读取 HTML 文件把占位符替换成参数值再返回给浏览器前端 JS 读取 URL阶段 6 步骤 4浏览器地址栏本身就带参数JS 直接从window.location里解析6.唯一标识用户还是需要tokentoken的主要作用是用来验证这个请求是「我的合法客户端」拦截陌生 / 非法访问。还有一个重要作用是关联上下文。因为http客户端的请求是多线程多连接并发的用远程的ip和端口来表示唯一用户是不科学的。asio2中一般是在登陆请求处理后返回的http头添加token后面访问其它接口都必须加上token解析出的token的方法http::web_request req std::string token; for (auto it req.begin(); it ! req.end(); it) { std::cout it-name_string() : it-value() std::endl; if (it-name_string() Authorize){ token it-value(); } }7.跨域处理asio2::http_server server; server.bindasio2::http::verb::get(.*, [](asio2::http_request req, asio2::http_response rep) { // 获取TCP远端IP和端口 std::string client_ip req.remote_address(); uint16_t client_port req.remote_port(); std::cout [访问] client_ip : client_port | req.path() ? req.query() \n; auto params parse_query(req.query()); // Token校验 auto token_it params.find(token); if(token_it params.end() || token_it-second ! g_server_token) { rep.status(401); rep.set_header(Content-Type, text/html;charsetutf-8); rep.body() h1401 未授权Token无效/h1; return; } // // CORS跨域头 rep.set(Access-Control-Allow-Origin, *); rep.set(Access-Control-Allow-Methods, GET,POST,OPTIONS); rep.set_hseteader(Access-Control-Allow-Headers, *); if(req.path() /index.html) { std::string html; if(rep.load_file(req.path(), html)) { std::string uid params[uid]; replace_all(html, {{uid}}, uid); rep.body() html; rep.set_header(Content-Type, text/html;charsetutf-8); } else { rep.status(404); rep.body() h1404 Not Found/h1; } } else { bool ok rep.fill_file(req.path()); if(!ok) { rep.status(404); rep.body() h1404/h1; } } });8.QtWebEngine 拼接 URL 携带 TokenQUrl url(http://127.0.0.1:8080/index.html); QUrlQuery query; query.addQueryItem(token, g_server_token); query.addQueryItem(uid, 1001); url.setQuery(query); ui-webView-setUrl(url);9.asio2 内置 URL 工具http 命名空间asio2 自带 http url 解析、编解码工具直接分离 scheme/host/path/querypath、query 中文必须 url_encode不支持裸中文直接请求#include asio2/http.hpp using namespace asio2; // 1. URL编解码核心本地化中文、特殊字符百分号编码 UTF-8 std::string raw_url http://127.0.0.1:8080/api/文件?name测试; std::string url_enc http::url_encode(raw_url); std::string url_dec http::url_decode(url_enc); // 2. 拆分path / query std::string path http::url_to_path(/api/文件?name测试); std::string query http::url_to_query(/api/文件?name测试); // 3. 直接http_client执行url asio2::http_client client; std::error_code ec; auto rep client.execute(http://localhost:8080/hello, ec);接口说明http::url_encodeRFC3986 百分号编码路径、查询参数中文、空格、# 等特殊字符用这个http::url_decode解码url_to_path提取路径/xxxurl_to_query提取?后的 query 串注意asio2没有内置 IDNA (Punycode) 域名编码。如果是中文域名http://中文域名.com需要自己实现 punycode 转码后再传给 asio2。完整的示例如下#include asio2/http/http_client.hpp #include iostream int main() { std::string_view host 127.0.0.1; std::string_view port 8080; asio2::socks5::optionasio2::socks5::method::anonymous sock5_option{ 127.0.0.1,10808 }; //asio2::socks5::optionasio2::socks5::method::anonymous, asio2::socks5::method::password // sock5_option{ s5.doudouip.cn,1088,zjww-1,aaa123 }; // download and save the file directly. // see ssl_http_client.cpp // The file is in this directory: /asio2/example/bin/x64/100mb.test asio2::http_client::download(http://cachefly.cachefly.net/100mb.test, 100mb.test); std::string_view url R(http://www.baidu.com/cond?json{qeury:name like %abc%,id:1}); std::string en http::url_encode(url); std::cout en std::endl; std::string de http::url_decode(en); std::cout de std::endl; auto path asio2::http::url_to_path(url); std::cout path std::endl; auto query asio2::http::url_to_query(url); std::cout query std::endl; auto rep21 asio2::http_client::execute(url); std::cout rep21 std::endl; http::web_request req24 http::make_request(url); req24.set(http::field::user_agent, Chrome); auto rep26 asio2::http_client::execute(req24); std::cout rep26 std::endl; asio2::http_client::execute(www.baidu.com, 80, /, std::chrono::seconds(5)); asio2::http_client::execute(www.baidu.com, 80, /, sock5_option); // GET auto req2 http::make_request(GET / HTTP/1.1\r\nHost: 192.168.0.1\r\n\r\n); auto rep2 asio2::http_client::execute(www.baidu.com, 80, req2, std::chrono::seconds(3)); if (asio2::get_last_error()) std::cout asio2::last_error_msg() std::endl; else std::cout rep2 std::endl; // POST auto req4 http::make_request(POST / HTTP/1.1\r\nHost: 192.168.0.1\r\n\r\n); auto rep4 asio2::http_client::execute(www.baidu.com, 80, req4); if (asio2::get_last_error()) std::cout asio2::last_error_msg() std::endl; else std::cout rep4 std::endl; // GET http::web_request req6(http::verb::get, /, 11); req6.set(http::field::user_agent, Chrome); auto rep6 asio2::http_client::execute(www.baidu.com, 80, req6, sock5_option); if (asio2::get_last_error()) std::cout asio2::last_error_msg() std::endl; else std::cout rep6 std::endl; // POST http::web_request req7; req7.method(http::verb::post); req7.target(/); req7.set(http::field::user_agent, Chrome); req7.set(http::field::content_type, text/html); req7.body() Hello World.; req7.prepare_payload(); auto rep7 asio2::http_client::execute(www.baidu.com, 80, req7); if (asio2::get_last_error()) std::cout asio2::last_error_msg() std::endl; else std::cout rep7 std::endl; // convert the response body to string std::stringstream ss1; ss1 rep7.body(); std::cout ss1.str() std::endl; // convert the whole response to string std::stringstream ss2; ss2 rep7; std::cout ss2.str() std::endl; asio2::http_client client; client.bind_recv([](http::web_request req, http::web_response rep) { // print the whole response std::cout rep std::endl; // print the response body std::cout rep.body() std::endl; // convert the response body to string std::stringstream ss; ss rep.body(); std::cout ss.str() std::endl; // Remove all fields req.clear(); req.set(http::field::user_agent, Chrome); req.set(http::field::content_type, text/html); req.method(http::verb::get); req.keep_alive(true); req.target(/get_user?nameabc); req.body() Hello World.; req.prepare_payload(); client.async_send(std::move(req)); }).bind_connect([]() { if (asio2::get_last_error()) printf(connect failure : %d %s\n, asio2::last_error_val(), asio2::last_error_msg().c_str()); else printf(connect success : %s %u\n, client.local_address().c_str(), client.local_port()); // connect success, send a request. if (!asio2::get_last_error()) { const char * msg GET / HTTP/1.1\r\n\r\n; client.async_send(msg); } }); client.start(host, port/*, sock5_option*/); while (std::getchar() ! \n); return 0; }