muduo的http库剖析.doc_第1页
muduo的http库剖析.doc_第2页
muduo的http库剖析.doc_第3页
muduo的http库剖析.doc_第4页
muduo的http库剖析.doc_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

muduo的http库剖析一:http协议 首先看一下http request:request line + header + body (header分为普通报头,请求报头与实体报头)header与body之间有一空行(CRLF) 请求方法有:GET、POST、HEAD、PUT、DELETE等 协议版本:1.0、1.1 详细知识参见这篇博客:http必知必会 或者有关面试的http知识见这篇博客:http面试必知必会 http断点续传原理:http断点续传原理二:muduo库http测试以及抓包 muduo库的http库自带了一个http_server的测试用例,即muduo/net/http/tests/HttpServer.cc,我们将它拿来测试一下,并用wireshark抓包分析http协议。 编译时需要加-lmudo_base -lmuduo_net -lmuduo_http -lpthread选项。在这里就不上代码了,后面会有分析,我们主要先抓包分析协议。 虚拟机运行该测试用例,使用tcpdump输入: tcpdump -Xvvenn -i eth1 tcp20:2=0x4745 or tcp20:2=0x4854 -s 80 -w tcpdump.cap 开始捕获,浏览器输入虚拟机地址和端口号,然后收到响应,截图如下: 我们将保存的.cap文件使用windows的wireshark打开,关于wireshark怎么分析可以参考:Wireshark数据抓包教程之认识捕获分析数据包 如图,我们不管别的,点击上方选项:分析-追踪流-TCP流就可以得到: 这样我们就可以窥探所有的http数据报内容了,对比第一张图网页中显示的内容,我们可以总结一下: 先看GET请求:使用HTTP 1.1版本。服务器地址47,端口8000。使用keep-alive长链接。Cache-Control是用来控制的,常见的取值有private、no-cache、max_age等,如果指定max-age值,那么在此值的时间内就不会重新访问服务器,数据由缓存直接返回,单位为秒。 常用请求头:Accept:浏览器可接受的媒体类型(MIME)类型Accept-Language:浏览器所希望的语言种类Accept-Encoding:浏览器能够解码的方法,如,gzip,deflate等User-Agent:告诉HTTP服务器,客户端使用的操作系统和浏览器的版本和名称Connection:表示是否需要持久连接,Keep-Alive表示长连接,close表示短连接 再看HTTP响应包:status line+header+body(header分为普通报头,响应报头与实体报头),header与body之间有一空行(CRLF)。 状态响应码:1xx 提示信息-表示请求已被成功接收,继续处理2xx 成功-表示请求已被成功接收,理解,接受3xx 重定向-要完成请求必须进行更进一步的处理4xx 客户端错误-请求有语法错误或请求无法实现5xx 服务器端错误-服务器执行一个有效请求失败三:muduo的http库剖析 muduo的http库一共有四个类,HttpRequest(http请求类封装)、HttpResponse(http响应类封装)、HttpContext(http协议解析类)、HttpServer(http服务器封装)。在这里我们就拿出上文中例子的代码来剖析muduo的http库,实例代码:cpp view plain copy print?在CODE上查看代码片派生到我的代码片#include #include #include #include #include #include #include using namespace muduo; using namespace muduo:net; extern char favicon555; bool benchmark = false; /响应回调函数 void onRequest(const HttpRequest& req, HttpResponse* resp) std:cout Headers req.methodString() req.path() std:endl; if (!benchmark) /如果为真,打印头部 const std:map& headers = req.headers(); for (std:map:const_iterator it = headers.begin(); it != headers.end(); +it) std:cout first : second setStatusCode(HttpResponse:k200Ok); /状态码200 resp-setStatusMessage(OK); /ok resp-setContentType(text/html); /html文本 resp-addHeader(Server, Muduo); /增加头部 string now = Timestamp:now().toFormattedString(); /生成时间戳 resp-setBody(This is title /标题 HelloNow is + now + /内容 ); else if (req.path() = /favicon.ico) /如果访问/favicon.ico路径,响应发送一张图片 resp-setStatusCode(HttpResponse:k200Ok); resp-setStatusMessage(OK); resp-setContentType(image/png); resp-setBody(string(favicon, sizeof favicon); else if (req.path() = /hello) /访问“/hello路径,省略 . else resp-setStatusCode(HttpResponse:k404NotFound); /如果都不是,出现我们没有设置的路径,那么返回404,找不到 resp-setStatusMessage(Not Found); resp-setCloseConnection(true); /断开连接 int main(int argc, char* argv) int numThreads = 0; if (argc 1) benchmark = true; /用来标志服务器是否打印header信息 Logger:setLogLevel(Logger:WARN); numThreads = atoi(argv1); /多线程数,如果为0,则不启动多线程 EventLoop loop; HttpServer server(&loop, InetAddress(8000), dummy); /HttpServer类,客端可以以基于对象的方式把它包含起来 server.setHttpCallback(onRequest); /设置响应回调 server.setThreadNum(numThreads); /可能启动多线程 server.start(); /启动 loop.loop(); /循环等待 /这是一个图片数组,省略了一部分 char favicon555 = x89, P, N, G, xD, xA, x1A, xA, . 上述程序就是前文中的程序,结果前文中使用wireshark已经分析过了。我们来过一下流程,首先客端创建一个HttpServer对象,它的成员是这样的:cpp view plain copy print?在CODE上查看代码片派生到我的代码片class HttpServer : boost:noncopyable public: typedef boost:function HttpCallback; HttpServer(EventLoop* loop, const InetAddress& listenAddr, const string& name, TcpServer:Option option = TcpServer:kNoReusePort); HttpServer(); / force out-line dtor, for scoped_ptr members. EventLoop* getLoop() const return server_.getLoop(); / Not thread safe, callback be registered before calling start(). void setHttpCallback(const HttpCallback& cb); void setThreadNum(int numThreads); void start(); private: void onConnection(const TcpConnectionPtr& conn); void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime); void onRequest(const TcpConnectionPtr&, const HttpRequest&); TcpServer server_; /http服务器也是一个Tcp服务器,所以包含一个TcpServer HttpCallback httpCallback_; /在处理http请求时(即调用onRequest)的过程中回调此函数,对请求进行具体的处理。 ; 作为一个暴露给外部客端的类,HttpServer就是一个服务器的封装,由于http协议底层使用tcp协议,所以它包含了一个TcpServer。并且向客户提供了一个回调函数的接口,当服务器收到http请求时,调用客户端的处理函数进行处理,该函数必须在start()之前设定。 HttpServer支持多线程,也可以使用单线程,同样setThreadNum()函数必须在start()函数调用之前被调用。 HttpServer的构造函数是这样的:cpp view plain copy print?在CODE上查看代码片派生到我的代码片HttpServer:HttpServer(EventLoop* loop, const InetAddress& listenAddr, const string& name, TcpServer:Option option) : server_(loop, listenAddr, name, option), httpCallback_(detail:defaultHttpCallback) /连接到来回调该函数 server_.setConnectionCallback( boost:bind(&HttpServer:onConnection, this, _1); /消息到来回调该函数 server_.setMessageCallback( boost:bind(&HttpServer:onMessage, this, _1, _2, _3); 初始化TcpServer,并将HttpServer的回调函数传给TcpServer。主要有两个函数:其一是onConenction()函数:cpp view plain copy print?在CODE上查看代码片派生到我的代码片void HttpServer:onConnection(const TcpConnectionPtr& conn) if (conn-connected() /构造一个http上下文对象,用来解析http请求 conn-setContext(HttpContext(); /TcpConnection和一个HttpContext绑定,利用boost:any 该函数为一个新的TcpConnection绑定一个HttpContext对象,使用的是TcpConnection中的boost:any,绑定之后,HttpContext就相当于TcpConnection的成员了,TcpConection在MessageCallback中就可以随意的使用HttpContext对象了。那么,HttpContext类是干什么的? 来看一下它的类:cpp view plain copy print?在CODE上查看代码片派生到我的代码片class HttpContext : public muduo:copyable public: enum HttpRequestParseState /解析请求状态的枚举常量 kExpectRequestLine, /当前正处于解析请求行的状态 kExpectHeaders, /当前正处于解析请求头部的状态 kExpectBody, /当前正处于解析请求实体的状态 kGotAll, /解析完毕 ; HttpContext() : state_(kExpectRequestLine) /初始状态,期望收到一个请求行 / default copy-ctor, dtor and assignment are fine / return false if any error bool parseRequest(Buffer* buf, Timestamp receiveTime); bool gotAll() const return state_ = kGotAll; /重置HttpContext状态,异常安全 void reset() state_ = kExpectRequestLine; HttpRequest dummy; /构造一个临时空HttpRequest对象,和当前的成员HttpRequest对象交换置空,然后临时对象析构 request_.swap(dummy); const HttpRequest& request() const return request_; HttpRequest& request() return request_; private: bool processRequestLine(const char* begin, const char* end); HttpRequestParseState state_; /请求的解析状态 HttpRequest request_; /http请求类 ; 它就是一个http的上下文对象,实际上是一个状态机。针对http包进行处理,我们可以看出它在处理过程中在四种状态间切换:kExpectRequestLine, kExpectHeaders, kExpectBody, kGotAll, 未收到http包是期待处理请求行,处理请求行之后处理头部,然后体部,最后gotall完成,不过中间可能会出错,到达不了这一步,会有相应的处理方案。它的函数待会再分析,因为它是HttpServer绑定的onMessage()回调函数会用到其二是onMessage()函数:cpp view plain copy print?在CODE上查看代码片派生到我的代码片void HttpServer:onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) /取出请求,mutable可以改变 HttpContext* context = boost:any_cast(conn-getMutableContext(); /调用context的parseRequest解析请求,返回bool是否请求成功 if (!context-parseRequest(buf, receiveTime) conn-send(HTTP/1.1 400 Bad Requestrnrn); /失败,发送400 conn-shutdown(); /关闭连接 if (context-gotAll() /请求成功 /调用onRequest onRequest(conn, context-request(); /一旦请求处理完毕,重置context,因为HttpContext和TcpConnection绑定了,我们需要解绑重复使用。 context-reset(); 没错,当TcpConnection中所拥有的连接有消息到来时,会调用它的messageCallback(应该是这名字,不过就是这个意思)函数,其实就是调用HttpServer的onMessage()函数。我们知道,我们之前在onConnection()函数中把HttpContext利用boost:any绑定给了TcpConnection,在onMessage()函数中我们就可以对TcpConnection使用HttpContext类来解析数据包了。onMessage()函数首先调用HttpContext的parserRequset()函数解析请求,判断请求是否合法,进而选择关闭连接,或者处理请求。HttpContex类的parserRequset()函数是这样实现的,利用状态机编程:cpp view plain copy print?在CODE上查看代码片派生到我的代码片bool HttpContext:parseRequest(Buffer* buf, Testamp receiveTime) bool ok = true; bool hasMore = true; while (hasMore) /初始状态是处于解析请求行的状态,下一次循环不是该状态就不会进入,一般只进入一次 if (state_ = kExpectRequestLine) /首先查找rn,就会到GET / HTTP/1.1的请求行末尾 const char* crlf = buf-findCRLF(); if (crlf) /解析请求行 ok = processRequestLine(buf-peek(), crlf); if (ok) /如果成功,设置请求行事件 /设置请求时间 request_.setReceiveTime(receiveTime); /将请求行从buf中取回,包括rn buf-retrieveUntil(crlf + 2); state_ = kExpectHeaders; /将Httpontext状态改为KexpectHeaders状态 else hasMore = false; else hasMore = false; else if (state_ = kExpectHeaders) /处于Header状态 const char* crlf = buf-findCRLF(); if (crlf) const char* colon = std:find(buf-peek(), crlf, :); /查找: if (colon != crlf) /找到添加头部,加到map容器 request_.addHeader(buf-peek(), colon, crlf); else / empty line, end of header / FIXME: state_ = kGotAll; /一旦请求完毕,再也找不到:了,状态改为gotall状态,循环退出 hasMore = false; buf-retrieveUntil(crlf + 2); /请求完毕也把crlf取回 else hasMore = false; else if (state_ = kExpectBody) / FIXME: return ok; 它先是解析请求行,如果有错has_more为false会跳出循环,后面代码解析所有的头部数据,有错也会跳出循环。解析请求行是这样的:cpp view plain copy print?在CODE上查看代码片派生到我的代码片bool HttpContext:processRequestLine(const char* begin, const char* end) bool succeed = false; const char* start = begin; const char* space = std:find(start, end, ); /查找空格 /格式 : GET / HTTP/1.1 /找到GET并设置请求方法 if (space != end & request_.setMethod(start, space) start = space+1; space = std:find(start, end, ); /再次查找 if (space != end) /找到 const char* question = std:find(start, space, ?); if (question != space) /找到了?,说明有请求参数 /设置路径 request_.setPath(start, question); /设置请求参数 request_.setQuery(question, space); else /没有找到只设置路径 request_.setPath(start, space); start = space+1; /查找有没有HTTP/1. succeed = end-start = 8 & std:equal(start, end-1, HTTP/1.); if (succeed) /如果成功,判断是采用HTTP/1.1还是HTTP/1.0 if (*(end-1) = 1) request_.setVersion(HttpRequest:kHttp11); else if (*(end-1) = 0) request_.setVersion(HttpRequest:kHttp10); else succeed = false; /请求行失败 return succeed; 好的,请求行一旦解析完毕,如果失败会关闭连接,成功的化onMessage()函数会继续进行,开始执行onRequest()函数处理请求:cpp view plain copy print?在CODE上查看代码片派生到我的代码片void HttpServer:onRequest(const TcpConnectionPtr& conn, const HttpRequest& req) /取出头部 const string& connection = req.getHeader(Connection); / 如果connection为close或者1.0版本不支持keep-alive,标志着我们处理完请求要关闭连接 bool close = connection = close | (req.getVersion() = HttpRequest:kHttp10 & connection != Keep-Alive); /使用close构造一个HttpResponse对象,该对象可以通过方法.closeConnection()判断是否关闭连接 HttpResponse response(close); /typedef boost:function HttpCallback; /执行用户注册的回调函数 httpCallback_(req, &response); Buffer buf; /用户处理后的信息,追加到缓冲区 response.appendToBuffer(&buf); conn-send(&buf); /发送数据 if (response.closeConnection() /如果关闭 conn-shutdown(); /关了它 这里有又一个新的HttpResponse对象,这个类只有一个.h文件,我们来看一下:cpp view plain copy print?在CODE上查看代码片派生到我的代码片class HttpRequest : public muduo:copyable public: enum Method /请求方法 kInvalid, kGet, kPost, kHead, kPut, kDelete ; enum Version /协议版本 kUnknown, kHttp10, kHttp11 ; HttpRequest() : method_(kInvalid), version_(kUnknown) /设置版本 void setVersion(Version v) version_ = v; Version getVersion() const return version_; /设置方法, bool setMethod(const char* start, const char* end) assert(method_ = kInvalid); /使用字符串首尾构造string,不包括尾部,如char *s=123, string s=(s,s+3),则s输出为123 string m(start, end); if (m = GET) method_ = kGet; else if (m = POST) method_ = kPost; else if (m = HEAD) method_ = kHead; else if (m = PUT) method_ = kPut; else if (m = DELETE) method_ = kDelete; else method_ = kInvalid; return method_ != kInvalid; /返回请求方法 Method method() const return method_; /请求方法转换成字符串 const char* methodString() const const char* result = UNKNOWN; switch(method_) case kGet: result = GET; break; case kPost: result = POST; break; case kHead: result = HEAD; break; case kPut: result = PUT; break; case kDelete: result = DELETE; break; default: break; return result; /设置路径 void setPath(const char* start, const char* end) path_.assign(start, end); const string& path() const return path_; / void setQuery(const char* start, const char* end) query_.assign(start, end); const string& query() const return query_; /设置接收时间 void setReceiveTime(Timestamp t) receiveTime_ = t; Timestamp receiveTime() const return receiveTime_; /添加头部信息,客户传来一个字符串,我们把它转化成field: value的形式 void addHeader(const char* start, const char* colon, const char* end) string field(start, colon); /header域 +colon; /去除左空格 while (colon end & isspace(*colon) +colon; string value(colon, end); /heade值 /去除右空格,如果右边有空格会一直resize-1 while (!value.empty() & isspace(valuevalue.size()-1) value.resize(value.size()-1); /std:map headers_; headers_field = value; /根据头域返回值 string getHeader(const string& field) const string result; std:map:const_iterator it = headers_.find(field); if (it != headers_.end() result = it-second; return result; /返回头部 const std:map& headers() const return headers_; /交换 void swap(HttpRequest& that) std:swap(method_, that.method_); path_.swap(that.path_); query_.swap(that.query_); receiveTime_.swap(that.receiveTime_); headers_.swap(

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论