一、认识URL
平时我们俗称的 "网址" 其实就是说的 URL-统一资源定位符
(1)我们在网络编程中,知道了可以通过ip地址标识唯一的一台主机,可事实是使用ip地址并不符合我们普通人的使用习惯,因此我们需要在内部建立域名到ip的映射,这样用户在使用的时候只需要通过域名,然后由浏览器帮我们去解析成ip地址,所以其实浏览器的内部是内置了域名解析的方法的!!(比如www.baidu.com)
(2) 我们在网络编程中,还知道要通过端口号来标识这台主机上的唯一一个服务,而该进程自带着网络协议http的解析方法,可是我们普通人使用的时候是没有端口号这个概念的! 那么浏览器怎么知道你这个端口号呢?? 因为浏览器会默认使用http协议,所以他必须知道绑定443号端口,所以默认会在请求里给我们添加端口号!
(3) 可是,我找到了这台主机和http服务,但是我想访问的是什么呢???比如我们平时在搜图片的时候,这个图片其实是被存储在该主机上的,而我们知道Linux一切皆文件,所以每个资源在自己的单机上都有自己的所属路径。因此我们还需要有一个带层次的文件路径。来标识我们具体想访问该主机上哪个地方的资源(一般来说可能是从web根目录开始 也有可能是相对路径)!/是文件路径分隔符
(4)当我们找到了这个资源的路径,那么这个路径可能有很多很多格式各种的资源,那么我们继续需要锁定其中的一个,就需要有查询字符串,一般在查询标示符?的后面,表明该路径下的唯一资源,而要锁定唯一资源,就需要传入一些指定的参数,不同参数之间用&去分隔,让url支持多参数的提交!
(5) 而#后面的是片段标示符,也就是说可能我们想访问的是该资源的某一个位置,可以用这个去进行标识!
——>总结
域名和路径 标识唯一一台主机上的一个唯一文件资源
端口号和协议 可以唯一确认用什么样的服务来获取或者上传资源
后面的资源路径(字符串) 可以标识唯一能找到的一张网页资源
凡是能通过url访问的资源,我们都叫做万维网!!
二、urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
浏览器会将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
"+" 被转义成了 "+"
urldecode就是urlencode的逆过程;
三、Http协议格式
最常见的两个网络行为:
(1)把别人的东西拿下来 GET(2)把自己的东西传上去 POST
我们会发现一共包含四个部分:请求行、请求报头、空行、请求正文(这个不一定得有)
HTTP请求:
首行: [方法] + [url] + [版本]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个
Content-Length属性来标识Body的长度;
HTTP响应:
首行: [版本号] + [状态码] + [状态码解释]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个
Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在
body中.
问题1:你怎么确保正文部分能够一字不落地读完呢??
——>报头属性里面有 标注正文部分的长度的属性,所以他只要根据换行规则去读取整个报文,然后一直读到空行就说明报头读完了,然后再根据length字段 直接向后读相应长度的字节 就可以把正文部分也给读完了!
问题2:为什么信息需要有版本号呢??
——>版本号是便于我们双方标识对应版本号的功能,因为可能有的用户没有更新客户端,所以服务端是有可能会接受到各种不同版本的客户端请求的,而较新的版本可能会存在一些旧版本没有的功能!!(比如有的版本可以发消息,而有的版本还可以发朋友圈)
问题3:状态码的含义
——>一般来讲不同的状态码对应不同的状况 比如常见的200是正常 404就是出错
问题4:常见的两个网络测试工具
——>fiddler和postman
fildder原理:先一步抓包拿到请求和响应 (默认是加密的)
postman:可以通过他发送具体请求
ubunt 20.04 最新版本
四、Http版本1之Hello World
发送请求用到的方法:
响应用到的方法:
平时浏览器看到的内容,底层是通过浏览器给我们返回的按协议解析的字符串信息,我们只会看到他的正文部分
问题1:我们的网页信息,难道每次都要静态编码写到服务器里面吗??
-——> 关于网页,我们一般需要用到html !这是属于前端的知识了!!
header包含的是头部的报文信息
body包含的是主体信息,然后整体再用html包起来!!
这样未来我们想要修改网页的内容,只需要在html文件里面做,从而不需要修改源代码
问题2:如何理解web根目录呢??
——> 按理来说,我们如果访问的时不带路径(只用IP加端口号),那么默认其实就是web根目录,可是web根目录难道就是Linux的根目录吗?? 显然不一定,因为我们在底层的Linux代码中去设置某个路径来充当Web根目录!!
并且我们要知道,网页文件肯定不止一个,所以必然需要有目录结构,所以未来我们需要将我们写的网页、图片、视频、首页等信息以树状的结构放在这个目录下让其他人去访问,所以这个就叫做Web根目录!未来设置的任何一个网页都可以放在web根目录下!
所以浏览器访问服务器的本质实际上是把服务器上指定路径下的文件资源获取道浏览器中,然后由 浏览器进行解释最后呈现的就是网页资源!!
问题3:我们的一些常量信息难道一定也是要静态编码进去吗??
——>既然网页信息都可以写到html文件里了!!那么我们的一些重要的常量信息(比如说我们设置的Web根目录)也可以写到配置文件里,这样未来我们就可以通过修改配置文件来修改这些常量,而源代码里只需要提供读取配置文件的方法即可,从而就不需要大幅度变动代码,并且可以在配置文件里一次性修改很多信息更加方便!!!
问题4:用户如果什么路径也不带,那么默认访问的就是web根目录,可是这不就相当于把整个网站资源都呈现给你了吗??
——>这显然是不合理的,就像我们的百度一样,百度首页其实也是一个Web根目录下的一个文件,因此我们需要在底层自己做一个网站的首页,然后当我们发现用户填的路径是/的时候,我们将他转移到我们的网站上并呈现给对方!!
问题5:C++其实提供了一些方法能够帮助我们快速分割字符串!!
所以协议的本质其实就是对字符串做一个规范的解析!!
五、Http方法
但是一般来说,最常用的就是GET和POST
问题:我们日常使用某些网站(http/https),究竟是如何把数据提交给服务器的?
----->数据都是通过表单提交的!!!
当我们需要提交参数给服务器
1、如果我们使用的是GET方法,我们提交的参数是通过url提交的,但是(1)参数会受限 (2)不够私密
2、而如果使用POST方法,会采用请求的正文提交参数,相对来说更私密一点!
其实提交参数的本质意义就是,fork子进程让他通过进程替换去执行这个程序,然后我们再通过进程间通信的方法将参数传递给他,然后让他完成相应的功能!!
六、Http状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
404表示资源不存在,比如客户端请求的资源,服务器打开失败所以产生错误,是客户端的错误(所以一般来说我们除了要写一个网站首页的文件,还需要写一个如果请求失败的一个返回404页面的网页)
403是禁止访问,一般就是因为你没有授权然后去访问了服务端不让你访问的信息
服务器错误,一般来说就是比如连接不上,或者是服务的线程创建失败
3XX一般是由于我要访问的服务器可能处于某种原因无法为我提供服务,所以他会传一个地址告诉客户端你想要的资源应该去这里查找(通过参数Location),这就是重定向!
更多的信息可以去百度搜状态码列表
七、 HTTP常见Header
1、Content-Type: 数据类型(text/html等)
读html的时候他是文本文件,那么就可以按照字符串读,但是如果我们是图片资源(png),那么就得按照二进制文件的方式来读!!因此在读取html之前需要确认一下文件格式,而不同的格式对应的content-type可以到网上搜索对照表。所以读网页之前必须确定一下后缀,然后不同的后缀就根据content-type对照表去找。
2、Content-Length: Body的长度
3、Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
4、User-Agent: 声明用户的操作系统和浏览器版本信息;
(1)他可以用来反爬,比方说他可以看看你这个客户端是不是一个合法的客户端,如果不合法的话就不给你做http响应。
(2)看看你是什么样的平台,然后可以根据你的平台给你推送信息比如APP
5、referer: 当前页面是从哪个页面跳转过来的;
6、location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
重定向分为永久重定向和临时重定向
讲个故事:
比如说你的学校东门有一家火锅店非常有名,但是他需要长时间的装修,为了继续生意,他将生意开到了学校的西门,然后在东门这里贴了一张纸条“正在装修,请到西门用餐”,这个时候你和你的同学到东门时看到了这个纸条,于是就到西门去吃了了,过了一礼拜你们还打算去的时候,你知道东门那边只是暂时关闭,所以你还是会先去东门看看,如果还没开的话才会去西门,这个其实就是临时重定向,因为我知道这个店只是暂时开到西门,随时可能会回来!
而后来东门开了的时候,为了能够让之前去西门吃饭的同学知道东门开启了,他会在之前西门的地方贴上“东门已经装修完成,以后请都前往东门用餐” 这个时候你们就知道这个店会一直在东门,这其实就是永久重定向!!
所以有的网站时间比较老,做更新的时候需要把域名和网址换了,可是很多老用户并不知道,所以就会给老网站部署永久性定向服务,让用户直接跳转到新网站!
7、connection:是否支持长连接(是基于服务费和客户端的版本去协商的)
短连接其实就是一次请求响应一个资源,然后就关闭连接
长连接其实就是一次请求连接上之后可以一直服务直到服务结束再关闭连接
为什么要有长连接呢??
---——>首先我们要知道,一个巨大的网页上的元素是很多的,而每一个元素其实就是一个资源,所以我们在发出http请求申请到网页资源的时候,同时也需要把网页上附带的资源(比如图片、音频) 都申请了,所以如果用短连接的话显然效率是不够高的!!
长连接和短连接并没有绝对的优劣,只不过应用场景不一样!!
但是要注意的是,具体采用长连接还是短连接,是要基于双方的HTTP版本协商的,其中HTTP1.0其实就是短连接 而HTTP1.1其实就是长连接 要支持长连接的话必须要求双方的版本都是1.1 这样Connection就会呈现keep-alive表示支持长连接
8、Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
比方说我们的b站,要看一个视频的话必须要先登录,你如果没登录他会提示你,然后你输入了你的用户名密码之后,你成功登录了,可是当你关闭浏览器然后再打开一次的时候,却不需要登录了,可是浏览器究竟是如何知道你此刻属于登录状态呢? 其实这就是http的会话保持功能!!
首先我们要知道,HTTP默认是无状态的,而他之所以能够知道你你处在登录状态,是因为你之前登录的时候,在浏览器里形成了一个cookie文件,这个文件里存储着你在这个网站的认证信息,而当你打开这个网站时,浏览器向对应服务端发送请求的时候会将你的认证信息(就是用户名和密码)放在cookie参数里面带过去直接认证(认证其实就是拿着你的用户名和密码去他后端的数据库做搜索,所以你想使用的前提是必须得注册,才能在他的数据库里留存数据),认证通过之后会直接将你从原先的登录页面重定向到目标页面,这样你就不需要再次登录了!!当然这个保存一般是有时间限制的!!
而一般cookie文件有两种类型,一种是内存级,一种是文件级,不同的浏览器可能不一样,如果是内存级的话,一般来说就是你的浏览器一旦关闭那么这个信息就不在了,但是如果是文件级的话,可能会有具体的过期时间,在你没过期之前你只要还是用该浏览器打开这个页面,那么cookie的存在就可以帮助你免去登录的过程!!
可是cookie的存在虽然方便了用户的使用体验,但他同样也伴生了安全问题,比如说别人如果把你的cookie文件窃取了,等于说看到了你的用户名和密码,那么问题就很严重了!!(qq盗号原理)
所以我们会面临两个问题(1)cookie被盗取 (2)个人信息泄露
我们要知道我们小白用户的防范能力基本为0,他们的电脑在黑客的眼里其实就相当于裸奔,比如你不小心点开了一个病毒,你或许可以通过杀毒软件去杀毒,但是也很有可能在你清理这个病毒之前你的信息就已经被窃取了!! 所以显然不能让客户端来维护这个安全问题,必须由更专业的服务端来维护!!
所以引入了session技术,我们输入用户名密码的时候,他会将这个用户名密码存在服务端,然后生成一个session id(数字指纹)返回给客户端存在cookie文件里,然后服务端会将session id管理起来(redis),这样认证的时候就会用cookie文件里面存储的session id去服务端做对比,只要通过了就可以直接登录了!!
可是你可能会问,如果我的session id泄露了,那别人不是也可以拿我的session id以我的身份去访问我的信息吗???
----->确实session id被盗取,是客户端的行为,这个是很难避免的,可能你不小心点开了一个病毒就被窃取了,并且我们作为小白本身是无感的,但是跟之前最大的区别就是session id是由服务端统一管理的,也就意味着他具备回收、停用、甄别异常等能力(比如说他发现你的ip地址突然发生了很大的变动,察觉异常就会暂时给你停止,等你发现问题了再次申诉的时候,他就会把你之前的session id清理掉然后重新分配) 这其实就是达到了控制客户端的目的!!
八、代码
HttpServer.hpp
#pragma once #include <iostream> #include <string> #include <pthread.h> #include <fstream> #include <vector> #include <sstream> #include <sys/types.h> #include <sys/socket.h> #include <unordered_map> #include "Socket.hpp" #include "Log.hpp" const std::string wwwroot="./wwwroot"; // web 根目录 const std::string sep = "\r\n"; const std::string homepage = "index.html"; static const int defaultport = 8082; class HttpServer; class ThreadData { public: ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s) { } public: int sockfd; HttpServer *svr; }; class HttpRequest { public: void Deserialize(std::string req) { while(true) { std::size_t pos = req.find(sep); if(pos == std::string::npos) break; std::string temp = req.substr(0, pos); if(temp.empty()) break; req_header.push_back(temp); req.erase(0, pos+sep.size()); } text = req; } // .png:image/png void Parse() { std::stringstream ss(req_header[0]); ss >> method >> url >> http_version; file_path = wwwroot; // ./wwwroot if(url == "/" || url == "/index.html") { file_path += "/"; file_path += homepage; // ./wwwroot/index.html } else file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html auto pos = file_path.rfind("."); if(pos == std::string::npos) suffix = ".html"; else suffix = file_path.substr(pos); } void DebugPrint() { for(auto &line : req_header) { std::cout << "--------------------------------" << std::endl; std::cout << line << "\n\n"; } std::cout << "method: " << method << std::endl; std::cout << "url: " << url << std::endl; std::cout << "http_version: " << http_version << std::endl; std::cout << "file_path: " << file_path << std::endl; std::cout << text << std::endl; } public: std::vector<std::string> req_header; std::string text; // 解析之后的结果 std::string method; std::string url; std::string http_version; std::string file_path; // ./wwwroot/a/b/c.html 2.png std::string suffix; }; class HttpServer { public: HttpServer(uint16_t port = defaultport) : _port(port) { content_type.insert({".html", "text/html"}); content_type.insert({".png", "image/png"}); content_type.insert({".jpg", "image/jpeg"}); } bool Start() { _listensock.Socket(); _listensock.Bind(_port); _listensock.Listen(); for (;;) { std::string clientip; uint16_t clientport; int sockfd = _listensock.Accept(&clientip, &clientport); if (sockfd < 0) continue; lg(Info, "get a new connect, sockfd: %d", sockfd); pthread_t tid; ThreadData *td = new ThreadData(sockfd, this); pthread_create(&tid, nullptr, ThreadRun, td); } } static std::string ReadHtmlContent(const std::string &htmlpath) { // 坑 std::ifstream in(htmlpath, std::ios::binary); if(!in.is_open()) return ""; in.seekg(0, std::ios_base::end); auto len = in.tellg(); in.seekg(0, std::ios_base::beg); std::string content; content.resize(len); in.read((char*)content.c_str(), content.size()); //std::string content; //std::string line; //while(std::getline(in, line)) //{ // content += line; //} in.close(); return content; } std::string SuffixToDesc(const std::string &suffix) { auto iter = content_type.find(suffix); if(iter == content_type.end()) return content_type[".html"]; else return content_type[suffix]; } void HandlerHttp(int sockfd) { char buffer[10240]; ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bug if (n > 0) { buffer[n] = 0; std::cout << buffer << std::endl; // 假设我们读取到的就是一个完整的,独立的http 请求 HttpRequest req; req.Deserialize(buffer); req.Parse(); req.DebugPrint(); //std::string path = wwwroot; //path += url; // wwwroot/a/a/b/index.html // 返回响应的过程 std::string text; bool ok = true; text = ReadHtmlContent(req.file_path); // 失败? if(text.empty()) { ok = false; std::string err_html = wwwroot; err_html += "/"; err_html += "err.html"; text = ReadHtmlContent(err_html); } std::string response_line; if(ok) response_line = "HTTP/1.0 200 OK\r\n"; else response_line = "HTTP/1.0 404 Not Found\r\n"; //response_line = "HTTP/1.0 302 Found\r\n"; std::string response_header = "Content-Length: "; response_header += std::to_string(text.size()); // Content-Length: 11 response_header += "\r\n"; response_header += "Content-Type: "; response_header += SuffixToDesc(req.suffix); response_header += "\r\n"; response_header += "Set-Cookie: name=haha&&passwd=12345"; response_header += "\r\n"; //response_header += "Location: https://www.qq.com\r\n"; std::string blank_line = "\r\n"; // \n std::string response = response_line; response += response_header; response += blank_line; response += text; send(sockfd, response.c_str(), response.size(), 0); } close(sockfd); } static void *ThreadRun(void *args) { pthread_detach(pthread_self()); ThreadData *td = static_cast<ThreadData *>(args); td->svr->HandlerHttp(td->sockfd); delete td; return nullptr; } ~HttpServer() { } private: Sock _listensock; uint16_t _port; std::unordered_map<std::string, std::string> content_type; };
复制
HttpServer.cpp
#include "HttpServer.hpp" #include <iostream> #include <memory> #include <pthread.h> #include "Log.hpp" using namespace std; int main(int argc, char *argv[]) { if(argc != 2) { exit(1); } uint16_t port = std::stoi(argv[1]); // HttpServer *svr = new HttpServer(); // std::unique<HttpServer> svr(new HttpServer()); std::unique_ptr<HttpServer> svr(new HttpServer(port)); svr->Start(); return 0; }
复制
Socket.hpp
#pragma once #include <iostream> #include <string> #include <unistd.h> #include <cstring> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include "Log.hpp" enum { SocketErr = 2, BindErr, ListenErr, }; // TODO const int backlog = 10; class Sock { public: Sock() { } ~Sock() { } public: void Socket() { sockfd_ = socket(AF_INET, SOCK_STREAM, 0); if (sockfd_ < 0) { lg(Fatal, "socker error, %s: %d", strerror(errno), errno); exit(SocketErr); } int opt = 1; setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); } void Bind(uint16_t port) { struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(port); local.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0) { lg(Fatal, "bind error, %s: %d", strerror(errno), errno); exit(BindErr); } } void Listen() { if (listen(sockfd_, backlog) < 0) { lg(Fatal, "listen error, %s: %d", strerror(errno), errno); exit(ListenErr); } } int Accept(std::string *clientip, uint16_t *clientport) { struct sockaddr_in peer; socklen_t len = sizeof(peer); int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len); if(newfd < 0) { lg(Warning, "accept error, %s: %d", strerror(errno), errno); return -1; } char ipstr[64]; inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr)); *clientip = ipstr; *clientport = ntohs(peer.sin_port); return newfd; } bool Connect(const std::string &ip, const uint16_t &port) { struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); peer.sin_family = AF_INET; peer.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr)); int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer)); if(n == -1) { std::cerr << "connect to " << ip << ":" << port << " error" << std::endl; return false; } return true; } void Close() { close(sockfd_); } int Fd() { return sockfd_; } private: int sockfd_; };
复制
log.hpp
#pragma once #include <iostream> #include <time.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #define SIZE 1024 #define Info 0 #define Debug 1 #define Warning 2 #define Error 3 #define Fatal 4 #define Screen 1 #define Onefile 2 #define Classfile 3 #define LogFile "log.txt" class Log { public: Log() { printMethod = Screen; path = "./log/"; } void Enable(int method) { printMethod = method; } std::string levelToString(int level) { switch (level) { case Info: return "Info"; case Debug: return "Debug"; case Warning: return "Warning"; case Error: return "Error"; case Fatal: return "Fatal"; default: return "None"; } } // void logmessage(int level, const char *format, ...) // { // time_t t = time(nullptr); // struct tm *ctime = localtime(&t); // char leftbuffer[SIZE]; // snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), // ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, // ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // // va_list s; // // va_start(s, format); // char rightbuffer[SIZE]; // vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); // // va_end(s); // // 格式:默认部分+自定义部分 // char logtxt[SIZE * 2]; // snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer); // // printf("%s", logtxt); // 暂时打印 // printLog(level, logtxt); // } void printLog(int level, const std::string &logtxt) { switch (printMethod) { case Screen: std::cout << logtxt << std::endl; break; case Onefile: printOneFile(LogFile, logtxt); break; case Classfile: printClassFile(level, logtxt); break; default: break; } } void printOneFile(const std::string &logname, const std::string &logtxt) { std::string _logname = path + logname; int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt" if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printClassFile(int level, const std::string &logtxt) { std::string filename = LogFile; filename += "."; filename += levelToString(level); // "log.txt.Debug/Warning/Fatal" printOneFile(filename, logtxt); } ~Log() { } void operator()(int level, const char *format, ...) { time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[SIZE]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); va_list s; va_start(s, format); char rightbuffer[SIZE]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 格式:默认部分+自定义部分 char logtxt[SIZE * 2]; snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer); // printf("%s", logtxt); // 暂时打印 printLog(level, logtxt); } private: int printMethod; std::string path; }; Log lg; // int sum(int n, ...) // { // va_list s; // char* // va_start(s, n); // int sum = 0; // while(n) // { // sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123); // n--; // } // va_end(s); //s = NULL // return sum; // }
复制
Makefile
HttpServer:HttpServer.cc g++ -o $@ $^ -std=c++11 -lpthread .PHONY:clean clean: rm -f HttpServer
复制
Web根目录
问题1:公司里面是怎么去搭建服务器的???
-——>一般来说公司搭建服务器用的是现成的方案,就算要搭建新的框架,也不会从0开始,大部分情况下并不需要太高深的算法,而平时公司考察笔试单纯是你为了筛选出代码能力很强的人,但是具体能不能筛选出他们想要的也难说,但是总之不会太差。
问题2:为什么浏览器的种类有这么多??
——>08-12年最大的公司是百度,因为当时还没有什么app,所以大家要上网基本上都需要用百度搜索,他的流量很大,但是你想使用百度搜索的前提是你必须要有一个浏览器,而浏览器就意味着流量的入口,所以很多公司都有动力去做这个,比如windows除了内置有自己的office办公软件,还会内置一款自带的IE浏览器。但是国内大部分浏览器使用的都是clone内核,而微软和谷歌有自己的浏览器!