vim常用模式 命令模式 解释:打开vim之后默认进入的模式,进入到vim最强大的命令模式 切换方法:可从任意模式通过ESC键切换为命令模式 插入模式 解释:类似windows记事本 切换方法:只能从命令模式,通过(i,a,I,A)等命令进入插入模式 可视模式 解释:使用光标选取一片区域的文本内容,之后可通过命令来操作 切换方法:只能从命令模式,通过(ctrl+v,v)等命令进入可视模式 命令模式进入到插入模式 命令 作用 i 插入到光标前面 I 插入到光标所在行最前面 a 插入到光标后面 A 插入到光标所在行最后面 o 在光标所在行下面新开一行 O 在光标所在行上面新开一行 退出vim命令模式 命令 作用 :w 保存 :wq 保存退出 :x 保存退出 :q 退出(已保存) :q! 不保存退出 移动光标命令 命令 作用 h,j,k,l 光标移动:h 左,j 下,k 右,l 上 ctrl+f 上一页 ctrl+b 下一页 w,e 跳到单词的后面 b,B 跳到单词的前面 gg 跳到第一行的开始 G 跳到最后一行的开始 复制粘贴命令 命令 作用 dd 剪切一行 3dd 剪切3行,同理4dd,5dd dw 删除一个单词 x 删除光标所在的字符 yy 复制一行 3yy 复制3行,同理4yy,5yy p 粘贴内容到光标的下一行 P 粘贴内容到光标的上一行

2021年7月10日 3Comments 84Browse 3Like agedcat_xuanzai Read more

先进入一个本地的git仓库。执行下面命令,创建一个readme.txt文件。 echo -e "Git is a version control system.\nGit is free software." >> readme.txt 并且用下面命令查看效果: ls cat readme.txt 将文件提交到暂存区。 git add readme.txt 将暂存区的更改提交到本地版本库。 git commit -m "wrote a readme file.txt" 查看创建的提交的更改信息,通过使用 git log 命令查询。 git log 版本之间的diff 查看简单的diff结果,可以加上--stat参数,执行命令如下: git diff --stat 比较上次提交commit和上上次提交的差别时,执行如下命令: git diff HEAD^ HEAD 比较两个历史版本之间的差异时,命令如下: git diff SHA1 SHA2 两个版本的SHA可以通过git log命令查出。 回滚 首先修改readme.txt文件,并且提交修改: cat>readme.txt<<_ENDL heredoc> Git is a distributed version control system. heredoc> Git is free software. heredoc> _ENDL 提交修改 git add readme.txt git commit -m "add distributed" cat > readme.txt << _ENDL heredoc> Git is a distributed version control system. heredoc> Git is free software distributed under the GPL. heredoc> _ENDL 再次提交修改 git add readme.txt git commit -m "append GPL" 然后用git log来查看版本历史信息; 使用git reset命令,将当前版本readme.txt回退到上一个版本add distributed: git reset --hard HEAD^ 完成回滚。 还可以继续回退到上一个版本wrote a readme file; 如果现在想回到append GPL版本时,需要找到append GPL版本的commit id,然后通过指定回到未来的某个版本来返回: git reset --hard SHA git reset --hard 034c000c511 创建与合并本地分支 创建dev分支,然后切换到dev分支: git checkout -b dev 这条命令相当于: git branch dev git checkout dev 然后通过下面命令来查看切换分支后的状态: git branch 在dev分支上对readme.txt文件做一个修改,然后提交: cat > readme.txt << _ENDL heredoc> Creating a newbranch is quick. heredoc> _ENDL git add readme.txt git commit readme.txt -m "add creating branch" 然后切换回master分支: git checkout master 然后通过下面命令来查看切换分支后的状态: git branch 切换回master分支后然后查看readme.txt文件的内容,发现内容发生了改变: cat readme.txt 然后将dev分支的修改内容合并到master分支上: git merge dev 合并完成后删除dev分支: git branch -d dev 查看是否删除成功: git branch

2021年7月10日 0Comments 68Browse 1Like agedcat_xuanzai Read more

安装和配置Git 安装Git sudo apt-get install git 配置Git 配置提交代码的提交人信息,全局生效 git config --global user.name "Your Name" git config --global user.email "email@example.com" 配置完后可以使用 git config --list 查看配置信息 创建本地版本库 git init Git 工作区、暂存区和版本库 Git本地有四个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)、git仓库(Remote Directory)。文件在这四个区域之间的转换关系如下: Workspace: 工作区,就是你平时存放项目代码的地方 Index / Stage: 暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息,一般存放在 .git/index 文件中 Repository: 仓库区(或版本库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本 Remote: 远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换 Git常用基本操作

2021年7月10日 0Comments 62Browse 0Like agedcat_xuanzai Read more

用C++实现的高性能WEB服务器,经过webbenchh压力测试可以实现上万的QPS 项目地址:https://github.com/Aged-cat/WebServer 功能 利用IO复用技术Epoll与线程池实现多线程的Reactor高并发模型; 利用正则与状态机解析HTTP请求报文,实现处理静态资源的请求; 利用标准库容器封装char,实现自动增长的缓冲区; 基于堆结构实现的定时器,关闭超时的非活动连接; 改进了线程池的实现,QPS提升了45%+; 项目详解 WebServer项目——buffer详解 WebServer项目——epoller详解 WebServer项目——timer详解 WebServer项目——threadpool详解 WebServer项目——HTTPconnection详解 WebServer项目——HTTPrequest详解 WebServer项目——HTTPresponse详解 WebServer项目——webserver详解 环境要求 Linux C++11 项目启动 mkdir bin make ./bin/myserver 压力测试 ./webbench-1.5/webbench -c 100 -t 10 http://ip:port/ ./webbench-1.5/webbench -c 1000 -t 10 http://ip:port/ ./webbench-1.5/webbench -c 5000 -t 10 http://ip:port/ ./webbench-1.5/webbench -c 10000 -t 10 http://ip:port/ 测试环境: Ubuntu:20 cpu:i7-4790 内存:16G 性能表现 与markparticle的C++服务器做一个比较(表格中的为QPS的值): 10 100 1000 10000 old 8929 9126 9209 155 new 11478 13578 13375 106 性能提升了45% 致谢 @markparticle

2021年6月6日 1Comments 656Browse 5Like agedcat_xuanzai Read more

webserver介绍 这个webserver类是对整个web服务器的抽象,基于HTTPconnection类、timer类、epoller类、threadpool类实现一个完整的高性能web服务器的所有功能。 需要满足的功能有: 初始化服务器,为HTTP的连接做好准备; 处理每一个HTTP连接; 用定时器给每一个HTTP连接定时,并且处理掉过期的连接; 运用IO多路复用技术提升IO性能; 运用线程池技术提升服务器性能; webserver的逻辑 首先是进行服务器的初始化,进行各种参数设置。其中包括了事件模式的初始化、socket连接的建立过程,主要用到了以下两个函数: bool initSocket_(); void initEventMode_(int trigMode); 在初始化socket的过程中,将listenFd_描述符也加入epoll进行监视。这样的话,当监听的listenFd(socketFd)有新连接的时候,就会发来一个可读信号。同时,也将监听socket的行为(是否有新的连接)和监听每一个HTTP连接的行为(已经建立的连接有无IO请求)统一起来了,都抽象为了读写两个操作。所以我们就可以每一次直接处理所有的epoll就绪事件,在过程中对有新连接请求的情况单独分出来处理就可以了。 接下来开始处理HTTP连接。 最开始当然先清理过期的连接,并且得到下一次处理的时间。这个首先就需要在这个类中有一个TimerManager类型指针或者对象,用它调用getNextHandle()函数: timeMS=timer_->getNextHandle(); 然后得到所有已经就绪的事件,用一个循环处理所有的epoll就绪事件。在过程中需要分两种类型:收到新的HTTP请求和其他的读写请求。 收到新的HTTP请求的情况 在fd==listenFd_的时候,也就是收到新的HTTP请求的时候,需要调用函数: void handleListen_(); 这个函数中会得到新的描述符,然后需要将新的描述符和新的描述符对应的连接记录下来,调用下述函数即可: void addClientConnection(int fd, sockaddr_in addr); 已经建立连接的HTTP发来IO请求的情况 这种情况下,必然需要提供读写的处理,用下述两个函数完成: void handleWrite_(HTTPconnection* client); void handleRead_(HTTPconnection* client); 但是为了提高性能,使用了线程池,所以这两个函数就是将读写的底层实现函数加入到线程池,两个读写的底层实现函数为: void onRead_(HTTPconnection* client); void onWrite_(HTTPconnection* client); epoll使用的是边缘触发ET,所以在读结束的时候根据需要改变epoll的事件。 边缘触发 1. 对于读操作 (1)当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。 (2)当有新数据到达时,即缓冲区中的待读数据变多的时候。 (3)当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。 2. 对于写操作 (1)当缓冲区由不可写变为可写时。 (2)当有旧数据被发送走,即缓冲区中的内容变少的时候。 (3)当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。 用函数: void onProcess_(HTTPconnection* client); 来完成。 当一切都完成的时候,还需要一个关闭服务器的函数: void closeConn_(HTTPconnection* client); WebServer的实现 class WebServer { public: WebServer(int port,int trigMode,int timeoutMS,bool optLinger,int threadNum); ~WebServer(); void Start(); //一切的开始 private: //对服务端的socket进行设置,最后可以得到listenFd bool initSocket_(); void initEventMode_(int trigMode); void addClientConnection(int fd, sockaddr_in addr); //添加一个HTTP连接 void closeConn_(HTTPconnection* client); //关闭一个HTTP连接 void handleListen_(); void handleWrite_(HTTPconnection* client); void handleRead_(HTTPconnection* client); void onRead_(HTTPconnection* client); void onWrite_(HTTPconnection* client); void onProcess_(HTTPconnection* client); void sendError_(int fd, const char* info); void extentTime_(HTTPconnection* client); static const int MAX_FD = 65536; static int setFdNonblock(int fd); int port_; int timeoutMS_; /* 毫秒MS,定时器的默认过期时间 */ bool isClose_; int listenFd_; bool openLinger_; char* srcDir_;//需要获取的路径 uint32_t listenEvent_; uint32_t connectionEvent_; std::unique_ptr<TimerManager>timer_; std::unique_ptr<ThreadPool> threadpool_; std::unique_ptr<Epoller> epoller_; std::unordered_map<int, HTTPconnection> users_; };

2021年6月6日 0Comments 198Browse 1Like agedcat_xuanzai Read more

定时器的介绍 为了提高Web服务器的效率,我们考虑给每一个HTTP连接加一个定时器。 定时器给每一个HTTP连接设置一个过期时间,然后我们定时清理超过过期时间的连接,会减少服务器的无效资源的耗费,提高服务器的运行效率。 我们还需要考虑一下如何管理和组织这些定时器。设置定时器的主要目的是为了清理过期连接,为了方便找到过期连接,首先考虑使用优先队列,按过期时间排序,让过期的排在前面就可以了。但是这样的话,虽然处理过期连接方便了,当时没法更新一个连接的过期时间。 最后,选择一个折中的方法。用vector容器存储定时器,然后在这之上实现堆结构,这样各个操作的代价就得到了相对均衡。 定时器的组成 定时器结点 为了实现定时器的功能,我们首先需要辨别每一个HTTP连接,每一个HTTP连接会有一个独有的描述符(socket),我们可以用这个描述符来标记这个连接,记为id。同时,我们还需要设置每一个HTTP连接的过期时间。 为了后面处理过期连接的方便,我们给每一个定时器里面放置一个回调函数,用来关闭过期连接。 为了便于定时器结点的比较,主要是后续堆结构的实现方便,我们还需要重载比较运算符。 class TimerNode{ public: int id; //用来标记定时器 TimeStamp expire; //设置过期时间 TimeoutCallBack cb; //设置一个回调函数用来方便删除定时器时将对应的HTTP连接关闭 //需要的功能可以自己设定 bool operator<(const TimerNode& t) { return expire<t.expire; } }; 定时器的管理 我们定义一个类 TimerManager来管理定时器。然后考虑一下这儿结构的构成。 实现堆结构必须的组件 首先是用来存储定时器的存储实体,用vector就可以: std::vector<TimerNode>heap_; 然后在vector的基础上实现堆结构还需要下列方法: void addTimer(int id,int timeout,const TimeoutCallBack& cb);//添加一个定时器 void del_(size_t i);//删除指定定时器 void siftup_(size_t i);//向上调整 bool siftdown_(size_t index,size_t n);//向下调整 void swapNode_(size_t i,size_t j);//交换两个结点位置 对于堆结构,主要就是往堆里添加一个结点和删除一个结点,对应的就是addTimer方法和del_方法,但是实现这两个方法同时还需要实现交换两个结点的 swapNode_方法和向上调整向下调整的方法。 提供给外界的接口 主要包括下面的方法: void addTimer(int id,int timeout,const TimeoutCallBack& cb); //处理过期的定时器 void handle_expired_event(); //下一次处理过期定时器的时间 int getNextHandle(); void update(int id,int timeout); //删除制定id节点,并且用指针触发处理函数 void work(int id); 添加定时器的方法也需要暴露给上层,还有就是最主要的处理过期连接的方法handle_expired_event,以及获取下一次处理的时间的方法int getNextHandle。还有就是在HTTP连接的处理过程中需要的对某一个连接对应定时器的过期时间做出改变所需要的update方法和处理过期时间过程中需要调用的work方法。 构造和析构函数 构造的时候初始化一下vector,析构的时候清理vector。 还可能有一些在实现这些方法的过程中提取出来的一个子模块所构成的方法。 timer总览 class TimerNode{ public: int id; //用来标记定时器 TimeStamp expire; //设置过期时间 TimeoutCallBack cb; //设置一个回调函数用来方便删除定时器时将对应的HTTP连接关闭 //需要的功能可以自己设定 bool operator<(const TimerNode& t) { return expire<t.expire; } }; class TimerManager{ public: TimerManager() {heap_.reserve(64);} ~TimerManager() {clear();} //设置定时器 void addTimer(int id,int timeout,const TimeoutCallBack& cb); //处理过期的定时器 void handle_expired_event(); //下一次处理过期定时器的时间 int getNextHandle(); void update(int id,int timeout); //删除制定id节点,并且用指针触发处理函数 void work(int id); void pop(); void clear(); private: void del_(size_t i); void siftup_(size_t i); bool siftdown_(size_t index,size_t n); void swapNode_(size_t i,size_t j); std::vector<TimerNode>heap_; std::unordered_map<int,size_t>ref_;//映射一个fd对应的定时器在heap_中的位置 };

2021年6月6日 0Comments 182Browse 0Like agedcat_xuanzai Read more

HTTPresponse简介 这个类和HTTPrequest相反,是给相应的连接生成相应报文的。HTTPrequest是解析请求行,请求头和数据体,那么HTTPresponse就是生成请求行,请求头和数据体。 HTTPresponse的组成 所需变量和自定义的数据结构 首先,我们需要一个变量code_来代表HTTP的状态。 在HTTPrequest中解析到的路径信息是相对路径,我们还需要补全,所以需要一个变量path_代表解析得到的路径,一个变量srcDir_表示根目录,除此之外,我们还需要一个哈希表提供4XX状态码到响应文件路径的映射。 我们在实现所需函数的过程中,需要知道HTTP连接是否处于KeepAlive状态,所以用一个变量isKeepAlive_表示。 由于使用了共享内存,所以也需要变量和数据结构指示相关信息: char* mmFile_; struct stat mmFileStat_; 所以,总结如下: int code_; bool isKeepAlive_; std::string path_; std::string srcDir_; char* mmFile_; struct stat mmFileStat_; static const std::unordered_map<std::string, std::string> SUFFIX_TYPE; static const std::unordered_map<int, std::string> CODE_STATUS; static const std::unordered_map<int, std::string> CODE_PATH; 其中,哈希表SUFFIX_TYPE表示后缀名到文件类型的映射关系,哈希表CODE_STATUS表示状态码到相应状态(字符串类型)的映射。 构造函数和析构函数 这个类中的构造函数和析构函数就是默认的,不需要做什么操作。虽然会有初始化的函数,但是不需要在这里初始化,因为需要初始化srcDir在这里没法获取。 生成响应报文函数 这个类的主要部分就是就是生成相应报文,也就是生成请求行,请求头和数据体,分别对应以下函数: void addStateLine_(Buffer& buffer); void addResponseHeader_(Buffer& buffer); void addResponseContent_(Buffer& buffer); 在设计的时候,对于4XX的状态码是分开考虑的,这部分由函数: void errorHTML_(); 实现。 在添加请求头的时候,我们需要得到文件类型信息,这个由函数: std::string getFileType_(); 实现。 在添加数据体的函数中,如果所请求的文件打不开,我们需要返回相应的错误信息,这个功能由函数: void errorContent(Buffer& buffer,std::string message); 实现。 最后,生成响应报文的主函数为: void makeResponse(Buffer& buffer); 暴露给外界的接口 返回状态码的函数: int code() const {return code_;} 返回文件信息的函数: char* file(); size_t fileLen() const; 其他函数 初始化函数: void init(const std::string& srcDir,std::string& path,bool isKeepAlive=false,int code=-1); 共享内存的扫尾函数: void unmapFile_(); HTTPresponse的实现 class HTTPresponse { public: HTTPresponse(); ~HTTPresponse(); void init(const std::string& srcDir,std::string& path,bool isKeepAlive=false,int code=-1); void makeResponse(Buffer& buffer); void unmapFile_(); char* file(); size_t fileLen() const; void errorContent(Buffer& buffer,std::string message); int code() const {return code_;} private: void addStateLine_(Buffer& buffer); void addResponseHeader_(Buffer& buffer); void addResponseContent_(Buffer& buffer); void errorHTML_(); std::string getFileType_(); int code_; bool isKeepAlive_; std::string path_; std::string srcDir_; char* mmFile_; struct stat mmFileStat_; static const std::unordered_map<std::string, std::string> SUFFIX_TYPE; static const std::unordered_map<int, std::string> CODE_STATUS; static const std::unordered_map<int, std::string> CODE_PATH; };

2021年6月6日 0Comments 84Browse 0Like agedcat_xuanzai Read more

HTTPrequest简介 这个类主要的功能是解析HTTP的请求信息。 HTTP的请求包括:请求行(request line)、请求头部(header)、空行 和 请求数据 四个部分组成。 抓包的request结构如下: GET /mix/76.html?name=kelvin&password=123456 HTTP/1.1 Host: www.fishbay.cn Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 1.请求行 GET为请求类型,/mix/76.html?name=kelvin&password=123456为要访问的资源,HTTP/1.1是协议版本 2.请求头部 从第二行起为请求头部,Host指出请求的目的地(主机域名);User-Agent是客户端的信息,它是检测浏览器类型的重要信息,由浏览器定义,并且在每个请求中自动发送。 3.空行 请求头后面必须有一个空行 4.请求数据 请求的数据也叫请求体,可以添加任意的其它数据。这个例子的请求体为空。 由上述的简单描述可以看出来,我们的主要任务就是解析传进来的buffer里面的请求信息,也就是把各个有用信息分割开来。 HTTPrequest的组成 所需变量和自定义数据结构 首先就是各个有意义的字段变量,比如必须用字符串类型的变量存储HTTP方式、路径、版本和数据体,以及需要用两个哈希表分别存储请求头和post已经解析出来的信息。 为了访问方便,先用一个集合存储了默认的网页名称。除此之外,还需要一个用来实现自动机的state变量。 PARSE_STATE state_; std::string method_,path_,version_,body_; std::unordered_map<std::string,std::string>header_; std::unordered_map<std::string,std::string>post_; static const std::unordered_set<std::string>DEFAULT_HTML; 构造和析构函数 这个类的析构函数默认就可以了,但是构造的时候需要初始化上述的变量,这些可以用一个方法init汇总起来: void init(); 主要的解析函数 主要的HTTP请求信息的解析可以用下述的有限状态自动机实现: 主流程由函数parse完成: bool parse(Buffer& buff); //解析HTTP请求 这个函数在实现过程中,根据HTTP请求报文的格式,用"\r\n"作为分隔符将报文分割成行,然后运用自动机来进行解析。在这个过程中,需要分别实现解析请求行、请求头和数据体的函数: bool parseRequestLine_(const std::string& line);//解析请求行 void parseRequestHeader_(const std::string& line); //解析请求头 void parseDataBody_(const std::string& line); //解析数据体 在解析请求行的时候,会解析出路径信息,之后还需要对路径信息做一个处理: void parsePath_(); 在处理数据体的时候,如果格式是post,那么还需要解析post报文,用函数parsePost实现: void parsePost_(); 暴露给外界的接口 这个类最后需要向上层提供解析后的信息,所以需要各种获取HTTP信息的接口。 获取路径、HTTP方式、版本的函数如下: std::string path() const; std::string& path(); std::string method() const; std::string version() const; 以及在post方式下获取信息的接口: std::string getPost(const std::string& key) const; std::string getPost(const char* key) const; 获取HTTP连接是否KeepAlive的函数: bool isKeepAlive() const; 其他方法函数 还有一些其他的方法函数,比如转换Hex格式的函数: static int convertHex(char ch); HTTPrequest的实现 class HTTPrequest { public: enum PARSE_STATE{ REQUEST_LINE, HEADERS, BODY, FINISH, }; HTTPrequest() {init();}; ~HTTPrequest()=default; void init(); bool parse(Buffer& buff); //解析HTTP请求 //获取HTTP信息 std::string path() const; std::string& path(); std::string method() const; std::string version() const; std::string getPost(const std::string& key) const; std::string getPost(const char* key) const; bool isKeepAlive() const; private: bool parseRequestLine_(const std::string& line);//解析请求行 void parseRequestHeader_(const std::string& line); //解析请求头 void parseDataBody_(const std::string& line); //解析数据体 void parsePath_(); void parsePost_(); static int convertHex(char ch); PARSE_STATE state_; std::string method_,path_,version_,body_; std::unordered_map<std::string,std::string>header_; std::unordered_map<std::string,std::string>post_; static const std::unordered_set<std::string>DEFAULT_HTML; };

2021年6月6日 0Comments 116Browse 0Like agedcat_xuanzai Read more

HTTPconnection简介 这个类就是对一个HTTP连接的抽象,负责对一个HTTP请求的解析和回复,以及提供读写的接口。 这个读写接口的底层就是靠buffer缓冲区来实现的,这个缓冲区提供了读写的接口。但是,写借口照样用了分散写的方法实现。然后就是对从socket连接中读取的数据进行解析,以及对请求做出响应。这部分的实现主要依赖于HTTPrequest和HTTPresponse来完成。 HTTPconnection组成 其中的构造函数和析构函数略去不谈,缺省就可以。 所需变量和自定义的数据结构 对于一个HTTP连接而言,我们需要用变量fd_唯一地标记它,用isClose_表示它是否需要关闭这个连接,已备后续关闭连接的函数的判断。一个HTTP连接还需要读写数据,所以给每一个HTTP连接定义一个读缓冲区和一个写缓冲区。在解析请求和响应请求的时候,我们借助HTTPrequest和HTTPresponse完成,所以也需要各种定义一个这两种变量。 总结一下,如下所示: int fd_; //HTTP连接对应的描述符 bool isClose_; //标记是否关闭连接 Buffer readBuffer_; //读缓冲区 Buffer writeBuffer_; //写缓冲区 HTTPrequest request_; HTTPresponse response_; 还需要一些其他的变量。比如响应请求的时候需要当前目录的路径这个值的传入,我们在这里需要定义它;比如,如果我们需要统计HTTP连接的个数,也就是用户的个数,最准确的方法就是在HTTP连接初始化和关闭的时候更改数据,在上层初始化它。 static const char* srcDir; static std::atomic<int>userCount; 初始化和关闭连接的接口 这两个函数顾名思义就是用来初始化连接和关闭连接的时候使用的: void initHTTPConn(int socketFd,const sockaddr_in& addr); void closeHTTPConn(); 读写接口 这个也很容易理解,每个HTTP连接都需要读写数据,这两个就是定义的读写接口: ssize_t readBuffer(int* saveErrno); ssize_t writeBuffer(int* saveErrno); 获取HTTP连接信息的函数 这个类也需要提供一些暴露给外界的接口,用于获取当前HTTP连接的信息: const char* getIP() const;//获取IP信息 int getPort() const;//获取IP信息 int getFd() const;//获取HTTP连接的描述符,也就是唯一标志 sockaddr_in getAddr() const; int writeBytes();//获取已经写入的数据长度 bool isKeepAlive() const;//获取这个HTTP连接KeepAlive的状态 主流程 这个类的主要函数就是用于完成解析请求和响应请求的整体逻辑,这些都在主函数中实现: bool handleHTTPConn(); HTTPconnection的实现 class HTTPconnection{ public: HTTPconnection(); ~HTTPconnection(); void initHTTPConn(int socketFd,const sockaddr_in& addr); //每个连接中定义的对缓冲区的读写接口 ssize_t readBuffer(int* saveErrno); ssize_t writeBuffer(int* saveErrno); //关闭HTTP连接的接口 void closeHTTPConn(); //定义处理该HTTP连接的接口,主要分为request的解析和response的生成 bool handleHTTPConn(); //其他方法 const char* getIP() const; int getPort() const; int getFd() const; sockaddr_in getAddr() const; int writeBytes() { return iov_[1].iov_len+iov_[0].iov_len; } bool isKeepAlive() const { return request_.isKeepAlive(); } static bool isET; static const char* srcDir; static std::atomic<int>userCount; private: int fd_; //HTTP连接对应的描述符 struct sockaddr_in addr_; bool isClose_; //标记是否关闭连接 int iovCnt_; struct iovec iov_[2]; Buffer readBuffer_; //读缓冲区 Buffer writeBuffer_; //写缓冲区 HTTPrequest request_; HTTPresponse response_; };

2021年6月6日 0Comments 158Browse 0Like agedcat_xuanzai Read more

epoller的简介 web服务器需要与客户端之间发生大量的IO操作,这也是性能的瓶颈之一。在这个项目中,我们用IO多路复用技术中的epoll来尽可能地提高一下性能。 epoll区别于select和poll,不需要每次轮询整个描述符集合来查找哪个描述符对应的IO已经做好准备了,epoll采用事件驱动的方式,当有事件准备就绪后就会一次返回已经做好准备的所有描述符集合。 epoll提供的程序接口有: int epoll_create(int size); 在内核中创建epoll实例并返回一个epoll文件描述符。 在最初的实现中,调用者通过 size 参数告知内核需要监听的文件描述符数量。如果监听的文件描述符数量超过 size, 则内核会自动扩容。而现在 size 已经没有这种语义了,但是调用者调用时 size 依然必须大于 0,以保证后向兼容性。 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 向 epfd 对应的内核epoll 实例添加、修改或删除对 fd 上事件 event 的监听。op 可以为 EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL 分别对应的是添加新的事件,修改文件描述符上监听的事件类型,从实例上删除一个事件。如果 event 的 events 属性设置了 EPOLLET flag,那么监听该事件的方式是边缘触发。 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 当 timeout 为 0 时,epoll_wait 永远会立即返回。而 timeout 为 -1 时,epoll_wait 会一直阻塞直到任一已注册的事件变为就绪。当 timeout 为一正整数时,epoll 会阻塞直到计时 timeout 毫秒终了或已注册的事件变为就绪。因为内核调度延迟,阻塞的时间可能会略微超过 timeout 毫秒。 这个类抽象了epoll的操作,为上层的需求提供支撑。 epoller的组成 所需的变量和自定义的数据结构 我们需要用一个变量epollerFd_唯一地标记这个epoll,同时还需要一个vector来存储需要返回的准备就绪的事件。 构造函数与析构函数 析构函数默认即可,构造函数需要调用epoll_create函数来对epoll进行初始化。 对单个fd的操作 主要涉及的是将某个fd添加进epoll进行监控,将某个fd移除epoll取消监控和改变某个fd对应的事件,这些都是通过调用epoll_ctl方法实现的: //将描述符fd加入epoll监控 bool addFd(int fd,uint32_t events); //修改描述符fd对应的事件 bool modFd(int fd,uint32_t events); //将描述符fd移除epoll的监控 bool delFd(int fd); 返回准备就绪的fd集合 这部分功能已经由epoll_wait方法实现了,也只是需要在这基础上封装一下就可以了: int wait(int timewait = -1); 返回就绪fd的信息 主要就是提供对外接口,获取就绪IO的描述符和事件: //获取fd的函数 int getEventFd(size_t i) const; //获取events的函数 uint32_t getEvents(size_t i) const; 这部分主要在服务器需要处理就绪IO事件的时候需要调用。 epoller的实现 class Epoller{ public: explicit Epoller(int maxEvent=1024); ~Epoller(); //将描述符fd加入epoll监控 bool addFd(int fd,uint32_t events); //修改描述符fd对应的事件 bool modFd(int fd,uint32_t events); //将描述符fd移除epoll的监控 bool delFd(int fd); //用于返回监控的结果,成功时返回就绪的文件描述符的个数 int wait(int timewait = -1); //获取fd的函数 int getEventFd(size_t i) const; //获取events的函数 uint32_t getEvents(size_t i) const; private: int epollerFd_;//这是标志epoll的描述符 std::vector<struct epoll_event>events_; //就绪的事件 };

2021年6月6日 0Comments 148Browse 0Like agedcat_xuanzai Read more
1234510