Repository: linyacool/WebServer Branch: master Commit: 7e18602f15c5 Files: 190 Total size: 453.7 KB Directory structure: gitextract_ftjuqrgs/ ├── .clang-format ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── WebBench/ │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── bin/ │ │ └── webbench │ ├── build.sh │ ├── debian/ │ │ ├── changelog │ │ ├── control │ │ ├── copyright │ │ ├── dirs │ │ └── rules │ ├── man/ │ │ └── man1/ │ │ └── webbench.1 │ ├── share/ │ │ └── doc/ │ │ └── webbench/ │ │ ├── changelog │ │ └── copyright │ ├── socket.c │ ├── tags │ ├── test.sh │ ├── webbench │ ├── webbench.1 │ └── webbench.c ├── WebServer/ │ ├── CMakeLists.txt │ ├── Channel.cpp │ ├── Channel.h │ ├── Epoll.cpp │ ├── Epoll.h │ ├── EventLoop.cpp │ ├── EventLoop.h │ ├── EventLoopThread.cpp │ ├── EventLoopThread.h │ ├── EventLoopThreadPool.cpp │ ├── EventLoopThreadPool.h │ ├── HttpData.cpp │ ├── HttpData.h │ ├── Main.cpp │ ├── Makefile │ ├── Makefile.bak │ ├── Server.cpp │ ├── Server.h │ ├── ThreadPool.cpp │ ├── ThreadPool.h │ ├── Timer.cpp │ ├── Timer.h │ ├── Util.cpp │ ├── Util.h │ ├── base/ │ │ ├── AsyncLogging.cpp │ │ ├── AsyncLogging.h │ │ ├── CMakeLists.txt │ │ ├── Condition.h │ │ ├── CountDownLatch.cpp │ │ ├── CountDownLatch.h │ │ ├── CurrentThread.h │ │ ├── FileUtil.cpp │ │ ├── FileUtil.h │ │ ├── LogFile.cpp │ │ ├── LogFile.h │ │ ├── LogStream.cpp │ │ ├── LogStream.h │ │ ├── Logging.cpp │ │ ├── Logging.h │ │ ├── Log的设计.txt │ │ ├── MutexLock.h │ │ ├── Thread.cpp │ │ ├── Thread.h │ │ ├── noncopyable.h │ │ └── tests/ │ │ ├── CMakeLists.txt │ │ └── LoggingTest.cpp │ ├── config.h │ └── tests/ │ ├── CMakeLists.txt │ └── HTTPClient.cpp ├── build.sh ├── old_version/ │ ├── old_version_0.1/ │ │ ├── Makefile │ │ ├── Makefile1 │ │ ├── epoll.cpp │ │ ├── epoll.h │ │ ├── improvement.txt │ │ ├── index.html │ │ ├── main.cpp │ │ ├── requestData.cpp │ │ ├── requestData.h │ │ ├── threadpool.cpp │ │ ├── threadpool.h │ │ ├── util.cpp │ │ └── util.h │ ├── old_version_0.2/ │ │ ├── Makefile │ │ ├── Makefile1 │ │ ├── epoll.cpp │ │ ├── epoll.h │ │ ├── improvement.txt │ │ ├── index.html │ │ ├── main.cpp │ │ ├── requestData.cpp │ │ ├── requestData.h │ │ ├── threadpool.cpp │ │ ├── threadpool.h │ │ ├── util.cpp │ │ └── util.h │ ├── old_version_0.3/ │ │ ├── Makefile │ │ ├── Makefile1 │ │ ├── config.h │ │ ├── epoll.cpp │ │ ├── epoll.h │ │ ├── improvement.txt │ │ ├── index.html │ │ ├── main.cpp │ │ ├── requestData.cpp │ │ ├── requestData.h │ │ ├── threadpool.cpp │ │ ├── threadpool.h │ │ ├── util.cpp │ │ └── util.h │ ├── old_version_0.4/ │ │ ├── Makefile │ │ ├── Makefile1 │ │ ├── base/ │ │ │ ├── mutexLock.hpp │ │ │ └── nocopyable.hpp │ │ ├── config.h │ │ ├── epoll.cpp │ │ ├── epoll.h │ │ ├── improvement.txt │ │ ├── index.html │ │ ├── main.cpp │ │ ├── requestData.cpp │ │ ├── requestData.h │ │ ├── threadpool.cpp │ │ ├── threadpool.h │ │ ├── timer.cpp │ │ ├── timer.h │ │ ├── util.cpp │ │ └── util.h │ ├── old_version_0.5/ │ │ ├── Makefile │ │ ├── Makefile1 │ │ ├── base/ │ │ │ ├── condition.hpp │ │ │ ├── mutexLock.hpp │ │ │ └── nocopyable.hpp │ │ ├── config.h │ │ ├── epoll.cpp │ │ ├── epoll.h │ │ ├── index.html │ │ ├── main.cpp │ │ ├── requestData.cpp │ │ ├── requestData.h │ │ ├── threadpool.cpp │ │ ├── threadpool.h │ │ ├── timer.cpp │ │ ├── timer.h │ │ ├── util.cpp │ │ └── util.h │ └── old_version_0.6/ │ ├── AsyncLogging.cpp │ ├── AsyncLogging.h │ ├── Condition.h │ ├── CountDownLatch.cpp │ ├── CountDownLatch.h │ ├── CurrentThread.hpp │ ├── FileUtil.cpp │ ├── FileUtil.h │ ├── LogFile.cpp │ ├── LogFile.h │ ├── LogStream.cpp │ ├── LogStream.h │ ├── Logging.cpp │ ├── Logging.h │ ├── Makefile │ ├── Makefile1 │ ├── MutexLock.h │ ├── Thread.cpp │ ├── Thread.h │ ├── config.h │ ├── epoll.cpp │ ├── epoll.h │ ├── index.html │ ├── main.cpp │ ├── noncopyable.h │ ├── requestData.cpp │ ├── requestData.h │ ├── threadpool.cpp │ ├── threadpool.h │ ├── timer.cpp │ ├── timer.h │ ├── util.cpp │ └── util.h ├── 并发模型.md ├── 测试及改进.md ├── 版本历史.md ├── 连接的维护.md ├── 遇到的困难.md └── 项目目的.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ Language: Cpp BasedOnStyle: Google ================================================ FILE: .gitignore ================================================ # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # Otheres core .vscode ================================================ FILE: .travis.yml ================================================ language: cpp sudo: required dist: trusty compiler: - g++ os: - linux install: - sudo apt-get install cmake env: - BUILD_TYPE=debug - BUILD_TYPE=release script: - ./build.sh ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8) project(WebServer CXX) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") endif() set(CXX_FLAGS -g -Wall -std=c++11 -D_PTHREADS -Wno-unused-parameter ) set(CMAKE_CXX_COMPILER "g++") set(CMAKE_CXX_FLAGS_DEBUG "-O0") set(CMAKE_CXX_FLAGS_RELEASE "-O0") string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CXX_FLAGS}") string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE) message(STATUS "CXX_FLAGS = " ${CMAKE_CXX_FLAGS} " " ${CMAKE_CXX_FLAGS_${BUILD_TYPE}}) add_subdirectory(WebServer) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 林亚 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # A C++ High Performance Web Server [![Build Status](https://travis-ci.org/linyacool/WebServer.svg?branch=master)](https://travis-ci.org/linyacool/WebServer) [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/MIT) ## Introduction 本项目为C++11编写的Web服务器,解析了get、head请求,可处理静态资源,支持HTTP长连接,支持管线化请求,并实现了异步日志,记录服务器运行状态。 | Part Ⅰ | Part Ⅱ | Part Ⅲ | Part Ⅳ | Part Ⅴ | Part Ⅵ | | :--------: | :---------: | :---------: | :---------: | :---------: | :---------: | | [并发模型](https://github.com/linyacool/WebServer/blob/master/并发模型.md)|[连接的维护](https://github.com/linyacool/WebServer/blob/master/连接的维护.md)|[版本历史](https://github.com/linyacool/WebServer/blob/master/%E7%89%88%E6%9C%AC%E5%8E%86%E5%8F%B2.md) | [测试及改进](https://github.com/linyacool/WebServer/blob/master/测试及改进.md) | [项目目的](https://github.com/linyacool/WebServer/blob/master/%E9%A1%B9%E7%9B%AE%E7%9B%AE%E7%9A%84.md) | [面试问题](https://github.com/linyacool/WebServer/blob/master/%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98.md) ## Envoirment * OS: Ubuntu 14.04 * Complier: g++ 4.8 ## Build ./build.sh ## Usage ./WebServer [-t thread_numbers] [-p port] [-l log_file_path(should begin with '/')] ## Technical points * 使用Epoll边沿触发的IO多路复用技术,非阻塞IO,使用Reactor模式 * 使用多线程充分利用多核CPU,并使用线程池避免线程频繁创建销毁的开销 * 使用基于小根堆的定时器关闭超时请求 * 主线程只负责accept请求,并以Round Robin的方式分发给其它IO线程(兼计算线程),锁的争用只会出现在主线程和某一特定线程中 * 使用eventfd实现了线程的异步唤醒 * 使用双缓冲区技术实现了简单的异步日志系统 * 为减少内存泄漏的可能,使用智能指针等RAII机制 * 使用状态机解析了HTTP请求,支持管线化 * 支持优雅关闭连接   ## Model 并发模型为Reactor+非阻塞IO+线程池,新连接Round Robin分配,详细介绍请参考[并发模型](https://github.com/linyacool/WebServer/blob/master/并发模型.md) ![并发模型](https://github.com/linyacool/WebServer/blob/master/datum/model.png) ## 代码统计 ![cloc](https://github.com/linyacool/WebServer/blob/master/datum/cloc.png) ## Others 除了项目基本的代码,进服务器进行压测时,对开源测试工具Webbench增加了Keep-Alive选项和测试功能: 改写后的[Webbench](https://github.com/linyacool/WebBench) ================================================ FILE: WebBench/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: WebBench/Makefile ================================================ CFLAGS?= -Wall -ggdb -W -O CC?= gcc LIBS?= LDFLAGS?= PREFIX?= /usr/local/webbench VERSION=1.5 TMPDIR=/tmp/webbench-$(VERSION) all: webbench tags tags: *.c -ctags *.c install: webbench install -d $(DESTDIR)$(PREFIX)/bin install -s webbench $(DESTDIR)$(PREFIX)/bin ln -sf $(DESTDIR)$(PREFIX)/bin/webbench $(DESTDIR)/usr/local/bin/webbench install -d $(DESTDIR)/usr/local/man/man1 install -d $(DESTDIR)$(PREFIX)/man/man1 install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 ln -sf $(DESTDIR)$(PREFIX)/man/man1/webbench.1 $(DESTDIR)/usr/local/man/man1/webbench.1 install -d $(DESTDIR)$(PREFIX)/share/doc/webbench install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench webbench: webbench.o Makefile $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) clean: -rm -f *.o webbench *~ core *.core tags tar: clean -debian/rules clean rm -rf $(TMPDIR) install -d $(TMPDIR) cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) install -d $(TMPDIR)/debian -cp -p debian/* $(TMPDIR)/debian ln -sf debian/copyright $(TMPDIR)/COPYRIGHT ln -sf debian/changelog $(TMPDIR)/ChangeLog -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) webbench.o: webbench.c socket.c Makefile .PHONY: clean install all tar ================================================ FILE: WebBench/README.md ================================================ # WebBench Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力。 ## 依赖 ctags ## 使用: sudo make && sudo make install PREFIX=your_path_to_webbench ## 命令行选项: | 短参 | 长参数 | 作用 | | ------------- |:-------------:| -----:| |-f |--force |不需要等待服务器响应 | |-r |--reload |发送重新加载请求 | |-t |--time |运行多长时间,单位:秒" | |-p |--proxy |使用代理服务器来发送请求 | |-c |--clients |创建多少个客户端,默认1个" | |-9 |--http09 |使用 HTTP/0.9 | |-1 |--http10 |使用 HTTP/1.0 协议 | |-2 |--http11 |使用 HTTP/1.1 协议 | | |--get |使用 GET请求方法 | | |--head |使用 HEAD请求方法 | | |--options |使用 OPTIONS请求方法 | | |--trace |使用 TRACE请求方法 | |-?/-h |--help |打印帮助信息 | |-V |--version |显示版本号 | ================================================ FILE: WebBench/build.sh ================================================ sudo make && sudo make install PREFIX=. ================================================ FILE: WebBench/debian/changelog ================================================ webbench (1.5) unstable; urgency=low * allow building with both Gnu and BSD make -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 webbench (1.4) unstable; urgency=low * check if url is not too long * report correct program version number * use yield() when waiting for test start * corrected error codes * check availability of test server first * do not abort test if first request failed * report when some childrens are dead. * use alarm, not time() for lower syscal use by bench * use mode 644 for installed doc * makefile cleaned for better freebsd ports integration -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 webbench (1.3) unstable; urgency=low * Build fixes for freeBSD * Default benchmark time 60 -> 30 * generate tar with subdirectory * added to freeBSD ports collection -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 webbench (1.2) unstable; urgency=low * Only debian-related bugfixes * Updated Debian/rules * Adapted to fit new directory system * moved from debstd to dh_* -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 webbench (1.1) unstable; urgency=medium * Program debianized * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) * added support for multiple HTTP versions (0.9 -- 1.1) * added long options * added multiple clients * wait for start of second before test * test time can be specified * better error checking when reading reply from server * FIX: tests was one second longer than expected -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 Local variables: mode: debian-changelog End: ================================================ FILE: WebBench/debian/control ================================================ Source: webbench Section: web Priority: extra Maintainer: Radim Kolar Build-Depends: debhelper (>> 3.0.0) Standards-Version: 3.5.2 Package: webbench Architecture: any Depends: ${shlibs:Depends} Description: Simple forking Web benchmark webbench is very simple program for benchmarking WWW or Proxy servers. Uses fork() for simulating multiple clients load. Can use HTTP 0.9 - 1.1 requests, but Keep-Alive connections are not supported. ================================================ FILE: WebBench/debian/copyright ================================================ Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 public domain code, created by Virginia Tech Computing Center. Copyright: GPL (see /usr/share/common-licenses/GPL) ================================================ FILE: WebBench/debian/dirs ================================================ usr/bin ================================================ FILE: WebBench/debian/rules ================================================ #!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # This is the debhelper compatability version to use. export DH_COMPAT=3 configure: configure-stamp configure-stamp: dh_testdir touch configure-stamp build: configure-stamp build-stamp build-stamp: dh_testdir $(MAKE) touch build-stamp clean: dh_testdir rm -f build-stamp configure-stamp # Add here commands to clean up after the build process. -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Add here commands to install the package into debian/webbench. $(MAKE) install DESTDIR=$(CURDIR)/debian/webbench # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installdocs dh_installman webbench.1 dh_installchangelogs dh_link dh_strip dh_compress dh_fixperms # dh_makeshlibs dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure ================================================ FILE: WebBench/man/man1/webbench.1 ================================================ -.TH WEBBENCH 1 "14 Jan 2004" -.\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection -.\" other parms are allowed: see man(7), man(1) -.SH NAME -webbench \- simple forking web benchmark -.SH SYNOPSIS -.B webbench -.I "[options] URL" -.br -.SH "AUTHOR" -This program and manual page was written by Radim Kolar, -for the -.B Supreme Personality of Godhead -(but may be used by others). -.SH "DESCRIPTION" -.B webbench -is simple program for benchmarking HTTP servers or any -other servers, which can be accessed via HTTP proxy. Unlike others -benchmarks, -.B webbench -uses multiple processes for simulating traffic -generated by multiple users. This allows better operating -on SMP systems and on systems with slow or buggy implementation -of select(). -.SH OPTIONS -The programs follow the usual GNU command line syntax, with long -options starting with two dashes (`-'). -A summary of options are included below. -.TP -.B \-?, \-h, \-\-help -Show summary of options. -.TP -.B \-v, \-\-version -Show version of program. -.TP -.B \-f, \-\-force -Do not wait for any response from server. Close connection after -request is send. This option produce quite a good denial of service -attack. -.TP -.B \-9, \-\-http09 -Use HTTP/0.9 protocol, if possible. -.TP -.B \-1, \-\-http10 -Use HTTP/1.0 protocol, if possible. -.TP -.B \-2, \-\-http11 -Use HTTP/1.1 protocol (without -.I Keep-Alive -), if possible. -.TP -.B \-r, \-\-reload -Forces proxy to reload document. If proxy is not -set, option has no effect. -.TP -.B \-t, \-\-time -Run benchmark for -.I -seconds. Default value is 30. -.TP -.B \-p, \-\-proxy -Send request via proxy server. Needed for supporting others protocols -than HTTP. -.TP -.B \-\-get -Use GET request method. -.TP -.B \-\-head -Use HEAD request method. -.TP -.B \-\-options -Use OPTIONS request method. -.TP -.B \-\-trace -Use TRACE request method. -.TP -.B \-c, \-\-clients -Use -.I -multiple clients for benchmark. Default value -is 1. -.SH "EXIT STATUS" -.TP -0 - sucess -.TP -1 - benchmark failed, can not connect to server -.TP -2 - bad command line argument(s) -.TP -3 - internal error, i.e. fork failed -.SH "TODO" -Include support for using -.I Keep-Alive -HTTP/1.1 connections. -.SH "COPYING" -Webbench is distributed under GPL. Copyright 1997-2004 -Radim Kolar (hsn@netmag.cz). -UNIX sockets code taken from popclient 1.5 4/1/94 -public domain code, created by Virginia Tech Computing Center. -.BR -This man page is public domain. ================================================ FILE: WebBench/share/doc/webbench/changelog ================================================ webbench (1.5) unstable; urgency=low * allow building with both Gnu and BSD make -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 webbench (1.4) unstable; urgency=low * check if url is not too long * report correct program version number * use yield() when waiting for test start * corrected error codes * check availability of test server first * do not abort test if first request failed * report when some childrens are dead. * use alarm, not time() for lower syscal use by bench * use mode 644 for installed doc * makefile cleaned for better freebsd ports integration -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 webbench (1.3) unstable; urgency=low * Build fixes for freeBSD * Default benchmark time 60 -> 30 * generate tar with subdirectory * added to freeBSD ports collection -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 webbench (1.2) unstable; urgency=low * Only debian-related bugfixes * Updated Debian/rules * Adapted to fit new directory system * moved from debstd to dh_* -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 webbench (1.1) unstable; urgency=medium * Program debianized * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) * added support for multiple HTTP versions (0.9 -- 1.1) * added long options * added multiple clients * wait for start of second before test * test time can be specified * better error checking when reading reply from server * FIX: tests was one second longer than expected -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 Local variables: mode: debian-changelog End: ================================================ FILE: WebBench/share/doc/webbench/copyright ================================================ Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 public domain code, created by Virginia Tech Computing Center. Copyright: GPL (see /usr/share/common-licenses/GPL) ================================================ FILE: WebBench/socket.c ================================================ /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ * * This module has been modified by Radim Kolar for OS/2 emx */ /*********************************************************************** module: socket.c program: popclient SCCS ID: @(#)socket.c 1.5 4/1/94 programmer: Virginia Tech Computing Center compiler: DEC RISC C compiler (Ultrix 4.1) environment: DEC Ultrix 4.3 description: UNIX sockets code. ***********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include int Socket(const char *host, int clientPort) { int sock; unsigned long inaddr; struct sockaddr_in ad; struct hostent *hp; memset(&ad, 0, sizeof(ad)); ad.sin_family = AF_INET; inaddr = inet_addr(host); if (inaddr != INADDR_NONE) memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); else { hp = gethostbyname(host); if (hp == NULL) return -1; memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); } ad.sin_port = htons(clientPort); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) return sock; // int optval = 1; // if(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) == -1) // return -1; if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) { close(sock); return -1; } return sock; } ================================================ FILE: WebBench/tags ================================================ !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ !_TAG_PROGRAM_NAME Exuberant Ctags // !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ !_TAG_PROGRAM_VERSION 5.9~svn20110310 // METHOD_GET webbench.c 38;" d file: METHOD_GET webbench1.c 38;" d file: METHOD_GET webbench2.c 38;" d file: METHOD_HEAD webbench.c 39;" d file: METHOD_HEAD webbench1.c 39;" d file: METHOD_HEAD webbench2.c 39;" d file: METHOD_OPTIONS webbench.c 40;" d file: METHOD_OPTIONS webbench1.c 40;" d file: METHOD_OPTIONS webbench2.c 40;" d file: METHOD_TRACE webbench.c 41;" d file: METHOD_TRACE webbench1.c 41;" d file: METHOD_TRACE webbench2.c 41;" d file: PROGRAM_VERSION webbench.c 42;" d file: PROGRAM_VERSION webbench1.c 42;" d file: PROGRAM_VERSION webbench2.c 42;" d file: REQUEST_SIZE webbench.c 56;" d file: REQUEST_SIZE webbench1.c 56;" d file: REQUEST_SIZE webbench2.c 56;" d file: Socket socket.c /^int Socket(const char *host, int clientPort)$/;" f alarm_handler webbench.c /^static void alarm_handler(int signal)$/;" f file: alarm_handler webbench1.c /^static void alarm_handler(int signal)$/;" f file: alarm_handler webbench2.c /^static void alarm_handler(int signal)$/;" f file: bench webbench.c /^static int bench(void)$/;" f file: bench webbench1.c /^static int bench(void)$/;" f file: bench webbench2.c /^static int bench(void)$/;" f file: benchcore webbench.c /^void benchcore(const char *host,const int port,const char *req)$/;" f benchcore webbench1.c /^void benchcore(const char *host,const int port,const char *req)$/;" f benchcore webbench2.c /^void benchcore(const char *host,const int port,const char *req)$/;" f benchtime webbench.c /^int benchtime=30;$/;" v benchtime webbench1.c /^int benchtime=30;$/;" v benchtime webbench2.c /^int benchtime=30;$/;" v build_request webbench.c /^void build_request(const char *url)$/;" f build_request webbench1.c /^void build_request(const char *url)$/;" f build_request webbench2.c /^void build_request(const char *url)$/;" f bytes webbench.c /^int bytes=0;$/;" v bytes webbench1.c /^int bytes=0;$/;" v bytes webbench2.c /^int bytes=0;$/;" v clients webbench.c /^int clients=1;$/;" v clients webbench1.c /^int clients=1;$/;" v clients webbench2.c /^int clients=1;$/;" v failed webbench.c /^int failed=0;$/;" v failed webbench1.c /^int failed=0;$/;" v failed webbench2.c /^int failed=0;$/;" v force webbench.c /^int force=0;$/;" v force webbench1.c /^int force=0;$/;" v force webbench2.c /^int force=0;$/;" v force_reload webbench.c /^int force_reload=0;$/;" v force_reload webbench1.c /^int force_reload=0;$/;" v force_reload webbench2.c /^int force_reload=0;$/;" v host webbench.c /^char host[MAXHOSTNAMELEN];$/;" v host webbench1.c /^char host[MAXHOSTNAMELEN];$/;" v host webbench2.c /^char host[MAXHOSTNAMELEN];$/;" v http10 webbench.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v http10 webbench1.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v http10 webbench2.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v keep_alive webbench.c /^bool keep_alive = false;$/;" v keep_alive webbench1.c /^bool keep_alive = false;$/;" v keep_alive webbench2.c /^bool keep_alive = false;$/;" v long_options webbench.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: long_options webbench1.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: long_options webbench2.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: main webbench.c /^int main(int argc, char *argv[])$/;" f main webbench1.c /^int main(int argc, char *argv[])$/;" f main webbench2.c /^int main(int argc, char *argv[])$/;" f method webbench.c /^int method=METHOD_GET;$/;" v method webbench1.c /^int method=METHOD_GET;$/;" v method webbench2.c /^int method=METHOD_GET;$/;" v mypipe webbench.c /^int mypipe[2];$/;" v mypipe webbench1.c /^int mypipe[2];$/;" v mypipe webbench2.c /^int mypipe[2];$/;" v proxyhost webbench.c /^char *proxyhost=NULL;$/;" v proxyhost webbench1.c /^char *proxyhost=NULL;$/;" v proxyhost webbench2.c /^char *proxyhost=NULL;$/;" v proxyport webbench.c /^int proxyport=80;$/;" v proxyport webbench1.c /^int proxyport=80;$/;" v proxyport webbench2.c /^int proxyport=80;$/;" v request webbench.c /^char request[REQUEST_SIZE];$/;" v request webbench1.c /^char request[REQUEST_SIZE];$/;" v request webbench2.c /^char request[REQUEST_SIZE];$/;" v speed webbench.c /^int speed=0;$/;" v speed webbench1.c /^int speed=0;$/;" v speed webbench2.c /^int speed=0;$/;" v timerexpired webbench.c /^volatile int timerexpired=0;$/;" v timerexpired webbench1.c /^volatile int timerexpired=0;$/;" v timerexpired webbench2.c /^volatile int timerexpired=0;$/;" v usage webbench.c /^static void usage(void)$/;" f file: usage webbench1.c /^static void usage(void)$/;" f file: usage webbench2.c /^static void usage(void)$/;" f file: ================================================ FILE: WebBench/test.sh ================================================ ./bin/webbench -t 60 -c 1000 -2 --get http://127.0.0.1:80/hello ================================================ FILE: WebBench/webbench.1 ================================================ -.TH WEBBENCH 1 "14 Jan 2004" -.\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection -.\" other parms are allowed: see man(7), man(1) -.SH NAME -webbench \- simple forking web benchmark -.SH SYNOPSIS -.B webbench -.I "[options] URL" -.br -.SH "AUTHOR" -This program and manual page was written by Radim Kolar, -for the -.B Supreme Personality of Godhead -(but may be used by others). -.SH "DESCRIPTION" -.B webbench -is simple program for benchmarking HTTP servers or any -other servers, which can be accessed via HTTP proxy. Unlike others -benchmarks, -.B webbench -uses multiple processes for simulating traffic -generated by multiple users. This allows better operating -on SMP systems and on systems with slow or buggy implementation -of select(). -.SH OPTIONS -The programs follow the usual GNU command line syntax, with long -options starting with two dashes (`-'). -A summary of options are included below. -.TP -.B \-?, \-h, \-\-help -Show summary of options. -.TP -.B \-v, \-\-version -Show version of program. -.TP -.B \-f, \-\-force -Do not wait for any response from server. Close connection after -request is send. This option produce quite a good denial of service -attack. -.TP -.B \-9, \-\-http09 -Use HTTP/0.9 protocol, if possible. -.TP -.B \-1, \-\-http10 -Use HTTP/1.0 protocol, if possible. -.TP -.B \-2, \-\-http11 -Use HTTP/1.1 protocol (without -.I Keep-Alive -), if possible. -.TP -.B \-r, \-\-reload -Forces proxy to reload document. If proxy is not -set, option has no effect. -.TP -.B \-t, \-\-time -Run benchmark for -.I -seconds. Default value is 30. -.TP -.B \-p, \-\-proxy -Send request via proxy server. Needed for supporting others protocols -than HTTP. -.TP -.B \-\-get -Use GET request method. -.TP -.B \-\-head -Use HEAD request method. -.TP -.B \-\-options -Use OPTIONS request method. -.TP -.B \-\-trace -Use TRACE request method. -.TP -.B \-c, \-\-clients -Use -.I -multiple clients for benchmark. Default value -is 1. -.SH "EXIT STATUS" -.TP -0 - sucess -.TP -1 - benchmark failed, can not connect to server -.TP -2 - bad command line argument(s) -.TP -3 - internal error, i.e. fork failed -.SH "TODO" -Include support for using -.I Keep-Alive -HTTP/1.1 connections. -.SH "COPYING" -Webbench is distributed under GPL. Copyright 1997-2004 -Radim Kolar (hsn@netmag.cz). -UNIX sockets code taken from popclient 1.5 4/1/94 -public domain code, created by Virginia Tech Computing Center. -.BR -This man page is public domain. ================================================ FILE: WebBench/webbench.c ================================================ /* * (C) Radim Kolar 1997-2004 * This is free software, see GNU Public License version 2 for * details. * * Simple forking WWW Server benchmark: * * Usage: * webbench --help * * Return codes: * 0 - sucess * 1 - benchmark failed (server is not on-line) * 2 - bad param * 3 - internal error, fork failed * */ #include "socket.c" #include #include #include #include #include #include #include #include #include /* values */ volatile int timerexpired=0; int speed=0; int failed=0; int bytes=0; /* globals */ int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ /* Allow: GET, HEAD, OPTIONS, TRACE */ #define METHOD_GET 0 #define METHOD_HEAD 1 #define METHOD_OPTIONS 2 #define METHOD_TRACE 3 #define PROGRAM_VERSION "1.5" int method=METHOD_GET; int clients=1; int force=0; int force_reload=0; int proxyport=80; char *proxyhost=NULL; int benchtime=30; bool keep_alive = false; /* internal */ int mypipe[2]; char host[MAXHOSTNAMELEN]; #define REQUEST_SIZE 2048 char request[REQUEST_SIZE]; static const struct option long_options[]= { {"force",no_argument,&force,1}, {"reload",no_argument,&force_reload,1}, {"time",required_argument,NULL,'t'}, {"help",no_argument,NULL,'?'}, {"http09",no_argument,NULL,'9'}, {"http10",no_argument,NULL,'1'}, {"http11",no_argument,NULL,'2'}, {"get",no_argument,&method,METHOD_GET}, {"head",no_argument,&method,METHOD_HEAD}, {"options",no_argument,&method,METHOD_OPTIONS}, {"trace",no_argument,&method,METHOD_TRACE}, {"version",no_argument,NULL,'V'}, {"proxy",required_argument,NULL,'p'}, {"clients",required_argument,NULL,'c'}, {NULL,0,NULL,0} }; /* prototypes */ static void benchcore(const char* host,const int port, const char *request); static int bench(void); static void build_request(const char *url); static void alarm_handler(int signal) { timerexpired=1; } static void usage(void) { fprintf(stderr, "webbench [option]... URL\n" " -f|--force Don't wait for reply from server.\n" " -r|--reload Send reload request - Pragma: no-cache.\n" " -t|--time Run benchmark for seconds. Default 30.\n" " -p|--proxy Use proxy server for request.\n" " -c|--clients Run HTTP clients at once. Default one.\n" " -k|--keep Keep-Alive\n" " -9|--http09 Use HTTP/0.9 style requests.\n" " -1|--http10 Use HTTP/1.0 protocol.\n" " -2|--http11 Use HTTP/1.1 protocol.\n" " --get Use GET request method.\n" " --head Use HEAD request method.\n" " --options Use OPTIONS request method.\n" " --trace Use TRACE request method.\n" " -?|-h|--help This information.\n" " -V|--version Display program version.\n" ); } int main(int argc, char *argv[]) { int opt=0; int options_index=0; char *tmp=NULL; if(argc==1) { usage(); return 2; } while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?hk",long_options,&options_index))!=EOF ) { switch(opt) { case 0 : break; case 'f': force=1;break; case 'r': force_reload=1;break; case '9': http10=0;break; case '1': http10=1;break; case '2': http10=2;break; case 'V': printf(PROGRAM_VERSION"\n");exit(0); case 't': benchtime=atoi(optarg);break; case 'k': keep_alive = true;break; case 'p': /* proxy server parsing server:port */ tmp=strrchr(optarg,':'); proxyhost=optarg; if(tmp==NULL) { break; } if(tmp==optarg) { fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); return 2; } if(tmp==optarg+strlen(optarg)-1) { fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); return 2; } *tmp='\0'; proxyport=atoi(tmp+1);break; case ':': case 'h': case '?': usage();return 2;break; case 'c': clients=atoi(optarg);break; } } if(optind==argc) { fprintf(stderr,"webbench: Missing URL!\n"); usage(); return 2; } if(clients==0) clients=1; if(benchtime==0) benchtime=30; /* Copyright */ fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" ); build_request(argv[optind]); // print request info ,do it in function build_request /*printf("Benchmarking: "); switch(method) { case METHOD_GET: default: printf("GET");break; case METHOD_OPTIONS: printf("OPTIONS");break; case METHOD_HEAD: printf("HEAD");break; case METHOD_TRACE: printf("TRACE");break; } printf(" %s",argv[optind]); switch(http10) { case 0: printf(" (using HTTP/0.9)");break; case 2: printf(" (using HTTP/1.1)");break; } printf("\n"); */ printf("Runing info: "); if(clients==1) printf("1 client"); else printf("%d clients",clients); printf(", running %d sec", benchtime); if(force) printf(", early socket close"); if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); if(force_reload) printf(", forcing reload"); printf(".\n"); return bench(); } void build_request(const char *url) { char tmp[10]; int i; //bzero(host,MAXHOSTNAMELEN); //bzero(request,REQUEST_SIZE); memset(host,0,MAXHOSTNAMELEN); memset(request,0,REQUEST_SIZE); if(force_reload && proxyhost!=NULL && http10<1) http10=1; if(method==METHOD_HEAD && http10<1) http10=1; if(method==METHOD_OPTIONS && http10<2) http10=2; if(method==METHOD_TRACE && http10<2) http10=2; switch(method) { default: case METHOD_GET: strcpy(request,"GET");break; case METHOD_HEAD: strcpy(request,"HEAD");break; case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; case METHOD_TRACE: strcpy(request,"TRACE");break; } strcat(request," "); if(NULL==strstr(url,"://")) { fprintf(stderr, "\n%s: is not a valid URL.\n",url); exit(2); } if(strlen(url)>1500) { fprintf(stderr,"URL is too long.\n"); exit(2); } if (0!=strncasecmp("http://",url,7)) { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); exit(2); } /* protocol/host delimiter */ i=strstr(url,"://")-url+3; if(strchr(url+i,'/')==NULL) { fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); exit(2); } if(proxyhost==NULL) { /* get port from hostname */ if(index(url+i,':')!=NULL && index(url+i,':')0) strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); if(proxyhost==NULL && http10>0) { strcat(request,"Host: "); strcat(request,host); strcat(request,"\r\n"); } if(force_reload && proxyhost!=NULL) { strcat(request,"Pragma: no-cache\r\n"); } if(http10>1) { if (!keep_alive) strcat(request,"Connection: close\r\n"); else strcat(request,"Connection: Keep-Alive\r\n"); } /* add empty line at end */ if(http10>0) strcat(request,"\r\n"); printf("\nRequest:\n%s\n",request); } /* vraci system rc error kod */ static int bench(void) { int i,j,k; pid_t pid=0; FILE *f; /* check avaibility of target server */ i=Socket(proxyhost==NULL?host:proxyhost,proxyport); if(i<0) { fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); return 1; } close(i); /* create pipe */ if(pipe(mypipe)) { perror("pipe failed."); return 3; } /* not needed, since we have alarm() in childrens */ /* wait 4 next system clock tick */ /* cas=time(NULL); while(time(NULL)==cas) sched_yield(); */ /* fork childs */ for(i=0;i0) { /* fprintf(stderr,"Correcting failed by signal\n"); */ failed--; } return; } if(s<0) { failed++;continue;} if(rlen!=write(s,req,rlen)) { failed++; close(s); while ((s = Socket(host,port)) == -1); continue; } if(force==0) { /* read all available data from socket */ while(1) { if(timerexpired) break; i=read(s,buf,1500); /* fprintf(stderr,"%d\n",i); */ if(i<0) { failed++; close(s); //while ((s = Socket(host,port)) == -1); goto nexttry1; } else if(i==0) break; else bytes+=i; // Supposed reveived bytes were less than 1500 //if (i < 1500) break; } } speed++; } } else { nexttry:while(1) { if(timerexpired) { if(failed>0) { /* fprintf(stderr,"Correcting failed by signal\n"); */ failed--; } return; } s=Socket(host,port); if(s<0) { failed++;continue;} if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} if(http10==0) if(shutdown(s,1)) { failed++;close(s);continue;} if(force==0) { /* read all available data from socket */ while(1) { if(timerexpired) break; i=read(s,buf,1500); if(i<0) { failed++; close(s); goto nexttry; } else if(i==0) break; else bytes+=i; } } if(close(s)) {failed++; continue;} speed++; } } } ================================================ FILE: WebServer/CMakeLists.txt ================================================ set(SRCS Channel.cpp Epoll.cpp EventLoop.cpp EventLoopThread.cpp EventLoopThreadPool.cpp HttpData.cpp Main.cpp Server.cpp #ThreadPool.cpp Timer.cpp Util.cpp ) include_directories(${PROJECT_SOURCE_DIR}/base) add_executable(WebServer ${SRCS}) target_link_libraries(WebServer libserver_base) add_subdirectory(base) add_subdirectory(tests) ================================================ FILE: WebServer/Channel.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Channel.h" #include #include #include #include #include "Epoll.h" #include "EventLoop.h" #include "Util.h" using namespace std; Channel::Channel(EventLoop *loop) : loop_(loop), events_(0), lastEvents_(0), fd_(0) {} Channel::Channel(EventLoop *loop, int fd) : loop_(loop), fd_(fd), events_(0), lastEvents_(0) {} Channel::~Channel() { // loop_->poller_->epoll_del(fd, events_); // close(fd_); } int Channel::getFd() { return fd_; } void Channel::setFd(int fd) { fd_ = fd; } void Channel::handleRead() { if (readHandler_) { readHandler_(); } } void Channel::handleWrite() { if (writeHandler_) { writeHandler_(); } } void Channel::handleConn() { if (connHandler_) { connHandler_(); } } ================================================ FILE: WebServer/Channel.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include #include #include "Timer.h" class EventLoop; class HttpData; class Channel { private: typedef std::function CallBack; EventLoop *loop_; int fd_; __uint32_t events_; __uint32_t revents_; __uint32_t lastEvents_; // 方便找到上层持有该Channel的对象 std::weak_ptr holder_; private: int parse_URI(); int parse_Headers(); int analysisRequest(); CallBack readHandler_; CallBack writeHandler_; CallBack errorHandler_; CallBack connHandler_; public: Channel(EventLoop *loop); Channel(EventLoop *loop, int fd); ~Channel(); int getFd(); void setFd(int fd); void setHolder(std::shared_ptr holder) { holder_ = holder; } std::shared_ptr getHolder() { std::shared_ptr ret(holder_.lock()); return ret; } void setReadHandler(CallBack &&readHandler) { readHandler_ = readHandler; } void setWriteHandler(CallBack &&writeHandler) { writeHandler_ = writeHandler; } void setErrorHandler(CallBack &&errorHandler) { errorHandler_ = errorHandler; } void setConnHandler(CallBack &&connHandler) { connHandler_ = connHandler; } void handleEvents() { events_ = 0; if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) { events_ = 0; return; } if (revents_ & EPOLLERR) { if (errorHandler_) errorHandler_(); events_ = 0; return; } if (revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) { handleRead(); } if (revents_ & EPOLLOUT) { handleWrite(); } handleConn(); } void handleRead(); void handleWrite(); void handleError(int fd, int err_num, std::string short_msg); void handleConn(); void setRevents(__uint32_t ev) { revents_ = ev; } void setEvents(__uint32_t ev) { events_ = ev; } __uint32_t &getEvents() { return events_; } bool EqualAndUpdateLastEvents() { bool ret = (lastEvents_ == events_); lastEvents_ = events_; return ret; } __uint32_t getLastEvents() { return lastEvents_; } }; typedef std::shared_ptr SP_Channel; ================================================ FILE: WebServer/Epoll.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Epoll.h" #include #include #include #include #include #include #include #include #include "Util.h" #include "base/Logging.h" #include #include using namespace std; const int EVENTSNUM = 4096; const int EPOLLWAIT_TIME = 10000; typedef shared_ptr SP_Channel; Epoll::Epoll() : epollFd_(epoll_create1(EPOLL_CLOEXEC)), events_(EVENTSNUM) { assert(epollFd_ > 0); } Epoll::~Epoll() {} // 注册新描述符 void Epoll::epoll_add(SP_Channel request, int timeout) { int fd = request->getFd(); if (timeout > 0) { add_timer(request, timeout); fd2http_[fd] = request->getHolder(); } struct epoll_event event; event.data.fd = fd; event.events = request->getEvents(); request->EqualAndUpdateLastEvents(); fd2chan_[fd] = request; if (epoll_ctl(epollFd_, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); fd2chan_[fd].reset(); } } // 修改描述符状态 void Epoll::epoll_mod(SP_Channel request, int timeout) { if (timeout > 0) add_timer(request, timeout); int fd = request->getFd(); if (!request->EqualAndUpdateLastEvents()) { struct epoll_event event; event.data.fd = fd; event.events = request->getEvents(); if (epoll_ctl(epollFd_, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); fd2chan_[fd].reset(); } } } // 从epoll中删除描述符 void Epoll::epoll_del(SP_Channel request) { int fd = request->getFd(); struct epoll_event event; event.data.fd = fd; event.events = request->getLastEvents(); // event.events = 0; // request->EqualAndUpdateLastEvents() if (epoll_ctl(epollFd_, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); } fd2chan_[fd].reset(); fd2http_[fd].reset(); } // 返回活跃事件数 std::vector Epoll::poll() { while (true) { int event_count = epoll_wait(epollFd_, &*events_.begin(), events_.size(), EPOLLWAIT_TIME); if (event_count < 0) perror("epoll wait error"); std::vector req_data = getEventsRequest(event_count); if (req_data.size() > 0) return req_data; } } void Epoll::handleExpired() { timerManager_.handleExpiredEvent(); } // 分发处理函数 std::vector Epoll::getEventsRequest(int events_num) { std::vector req_data; for (int i = 0; i < events_num; ++i) { // 获取有事件产生的描述符 int fd = events_[i].data.fd; SP_Channel cur_req = fd2chan_[fd]; if (cur_req) { cur_req->setRevents(events_[i].events); cur_req->setEvents(0); // 加入线程池之前将Timer和request分离 // cur_req->seperateTimer(); req_data.push_back(cur_req); } else { LOG << "SP cur_req is invalid"; } } return req_data; } void Epoll::add_timer(SP_Channel request_data, int timeout) { shared_ptr t = request_data->getHolder(); if (t) timerManager_.addTimer(t, timeout); else LOG << "timer add fail"; } ================================================ FILE: WebServer/Epoll.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include "Channel.h" #include "HttpData.h" #include "Timer.h" class Epoll { public: Epoll(); ~Epoll(); void epoll_add(SP_Channel request, int timeout); void epoll_mod(SP_Channel request, int timeout); void epoll_del(SP_Channel request); std::vector> poll(); std::vector> getEventsRequest(int events_num); void add_timer(std::shared_ptr request_data, int timeout); int getEpollFd() { return epollFd_; } void handleExpired(); private: static const int MAXFDS = 100000; int epollFd_; std::vector events_; std::shared_ptr fd2chan_[MAXFDS]; std::shared_ptr fd2http_[MAXFDS]; TimerManager timerManager_; }; ================================================ FILE: WebServer/EventLoop.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "EventLoop.h" #include #include #include #include "Util.h" #include "base/Logging.h" using namespace std; __thread EventLoop* t_loopInThisThread = 0; int createEventfd() { int evtfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); if (evtfd < 0) { LOG << "Failed in eventfd"; abort(); } return evtfd; } EventLoop::EventLoop() : looping_(false), poller_(new Epoll()), wakeupFd_(createEventfd()), quit_(false), eventHandling_(false), callingPendingFunctors_(false), threadId_(CurrentThread::tid()), pwakeupChannel_(new Channel(this, wakeupFd_)) { if (t_loopInThisThread) { // LOG << "Another EventLoop " << t_loopInThisThread << " exists in this // thread " << threadId_; } else { t_loopInThisThread = this; } // pwakeupChannel_->setEvents(EPOLLIN | EPOLLET | EPOLLONESHOT); pwakeupChannel_->setEvents(EPOLLIN | EPOLLET); pwakeupChannel_->setReadHandler(bind(&EventLoop::handleRead, this)); pwakeupChannel_->setConnHandler(bind(&EventLoop::handleConn, this)); poller_->epoll_add(pwakeupChannel_, 0); } void EventLoop::handleConn() { // poller_->epoll_mod(wakeupFd_, pwakeupChannel_, (EPOLLIN | EPOLLET | // EPOLLONESHOT), 0); updatePoller(pwakeupChannel_, 0); } EventLoop::~EventLoop() { // wakeupChannel_->disableAll(); // wakeupChannel_->remove(); close(wakeupFd_); t_loopInThisThread = NULL; } void EventLoop::wakeup() { uint64_t one = 1; ssize_t n = writen(wakeupFd_, (char*)(&one), sizeof one); if (n != sizeof one) { LOG << "EventLoop::wakeup() writes " << n << " bytes instead of 8"; } } void EventLoop::handleRead() { uint64_t one = 1; ssize_t n = readn(wakeupFd_, &one, sizeof one); if (n != sizeof one) { LOG << "EventLoop::handleRead() reads " << n << " bytes instead of 8"; } // pwakeupChannel_->setEvents(EPOLLIN | EPOLLET | EPOLLONESHOT); pwakeupChannel_->setEvents(EPOLLIN | EPOLLET); } void EventLoop::runInLoop(Functor&& cb) { if (isInLoopThread()) cb(); else queueInLoop(std::move(cb)); } void EventLoop::queueInLoop(Functor&& cb) { { MutexLockGuard lock(mutex_); pendingFunctors_.emplace_back(std::move(cb)); } if (!isInLoopThread() || callingPendingFunctors_) wakeup(); } void EventLoop::loop() { assert(!looping_); assert(isInLoopThread()); looping_ = true; quit_ = false; // LOG_TRACE << "EventLoop " << this << " start looping"; std::vector ret; while (!quit_) { // cout << "doing" << endl; ret.clear(); ret = poller_->poll(); eventHandling_ = true; for (auto& it : ret) it->handleEvents(); eventHandling_ = false; doPendingFunctors(); poller_->handleExpired(); } looping_ = false; } void EventLoop::doPendingFunctors() { std::vector functors; callingPendingFunctors_ = true; { MutexLockGuard lock(mutex_); functors.swap(pendingFunctors_); } for (size_t i = 0; i < functors.size(); ++i) functors[i](); callingPendingFunctors_ = false; } void EventLoop::quit() { quit_ = true; if (!isInLoopThread()) { wakeup(); } } ================================================ FILE: WebServer/EventLoop.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include "Channel.h" #include "Epoll.h" #include "Util.h" #include "base/CurrentThread.h" #include "base/Logging.h" #include "base/Thread.h" #include using namespace std; class EventLoop { public: typedef std::function Functor; EventLoop(); ~EventLoop(); void loop(); void quit(); void runInLoop(Functor&& cb); void queueInLoop(Functor&& cb); bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); } void assertInLoopThread() { assert(isInLoopThread()); } void shutdown(shared_ptr channel) { shutDownWR(channel->getFd()); } void removeFromPoller(shared_ptr channel) { // shutDownWR(channel->getFd()); poller_->epoll_del(channel); } void updatePoller(shared_ptr channel, int timeout = 0) { poller_->epoll_mod(channel, timeout); } void addToPoller(shared_ptr channel, int timeout = 0) { poller_->epoll_add(channel, timeout); } private: // 声明顺序 wakeupFd_ > pwakeupChannel_ bool looping_; shared_ptr poller_; int wakeupFd_; bool quit_; bool eventHandling_; mutable MutexLock mutex_; std::vector pendingFunctors_; bool callingPendingFunctors_; const pid_t threadId_; shared_ptr pwakeupChannel_; void wakeup(); void handleRead(); void doPendingFunctors(); void handleConn(); }; ================================================ FILE: WebServer/EventLoopThread.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "EventLoopThread.h" #include EventLoopThread::EventLoopThread() : loop_(NULL), exiting_(false), thread_(bind(&EventLoopThread::threadFunc, this), "EventLoopThread"), mutex_(), cond_(mutex_) {} EventLoopThread::~EventLoopThread() { exiting_ = true; if (loop_ != NULL) { loop_->quit(); thread_.join(); } } EventLoop* EventLoopThread::startLoop() { assert(!thread_.started()); thread_.start(); { MutexLockGuard lock(mutex_); // 一直等到threadFun在Thread里真正跑起来 while (loop_ == NULL) cond_.wait(); } return loop_; } void EventLoopThread::threadFunc() { EventLoop loop; { MutexLockGuard lock(mutex_); loop_ = &loop; cond_.notify(); } loop.loop(); // assert(exiting_); loop_ = NULL; } ================================================ FILE: WebServer/EventLoopThread.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include "EventLoop.h" #include "base/Condition.h" #include "base/MutexLock.h" #include "base/Thread.h" #include "base/noncopyable.h" class EventLoopThread : noncopyable { public: EventLoopThread(); ~EventLoopThread(); EventLoop* startLoop(); private: void threadFunc(); EventLoop* loop_; bool exiting_; Thread thread_; MutexLock mutex_; Condition cond_; }; ================================================ FILE: WebServer/EventLoopThreadPool.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "EventLoopThreadPool.h" EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, int numThreads) : baseLoop_(baseLoop), started_(false), numThreads_(numThreads), next_(0) { if (numThreads_ <= 0) { LOG << "numThreads_ <= 0"; abort(); } } void EventLoopThreadPool::start() { baseLoop_->assertInLoopThread(); started_ = true; for (int i = 0; i < numThreads_; ++i) { std::shared_ptr t(new EventLoopThread()); threads_.push_back(t); loops_.push_back(t->startLoop()); } } EventLoop *EventLoopThreadPool::getNextLoop() { baseLoop_->assertInLoopThread(); assert(started_); EventLoop *loop = baseLoop_; if (!loops_.empty()) { loop = loops_[next_]; next_ = (next_ + 1) % numThreads_; } return loop; } ================================================ FILE: WebServer/EventLoopThreadPool.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include "EventLoopThread.h" #include "base/Logging.h" #include "base/noncopyable.h" class EventLoopThreadPool : noncopyable { public: EventLoopThreadPool(EventLoop* baseLoop, int numThreads); ~EventLoopThreadPool() { LOG << "~EventLoopThreadPool()"; } void start(); EventLoop* getNextLoop(); private: EventLoop* baseLoop_; bool started_; int numThreads_; int next_; std::vector> threads_; std::vector loops_; }; ================================================ FILE: WebServer/HttpData.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "HttpData.h" #include #include #include #include #include "Channel.h" #include "EventLoop.h" #include "Util.h" #include "time.h" using namespace std; pthread_once_t MimeType::once_control = PTHREAD_ONCE_INIT; std::unordered_map MimeType::mime; const __uint32_t DEFAULT_EVENT = EPOLLIN | EPOLLET | EPOLLONESHOT; const int DEFAULT_EXPIRED_TIME = 2000; // ms const int DEFAULT_KEEP_ALIVE_TIME = 5 * 60 * 1000; // ms char favicon[555] = { '\x89', 'P', 'N', 'G', '\xD', '\xA', '\x1A', '\xA', '\x0', '\x0', '\x0', '\xD', 'I', 'H', 'D', 'R', '\x0', '\x0', '\x0', '\x10', '\x0', '\x0', '\x0', '\x10', '\x8', '\x6', '\x0', '\x0', '\x0', '\x1F', '\xF3', '\xFF', 'a', '\x0', '\x0', '\x0', '\x19', 't', 'E', 'X', 't', 'S', 'o', 'f', 't', 'w', 'a', 'r', 'e', '\x0', 'A', 'd', 'o', 'b', 'e', '\x20', 'I', 'm', 'a', 'g', 'e', 'R', 'e', 'a', 'd', 'y', 'q', '\xC9', 'e', '\x3C', '\x0', '\x0', '\x1', '\xCD', 'I', 'D', 'A', 'T', 'x', '\xDA', '\x94', '\x93', '9', 'H', '\x3', 'A', '\x14', '\x86', '\xFF', '\x5D', 'b', '\xA7', '\x4', 'R', '\xC4', 'm', '\x22', '\x1E', '\xA0', 'F', '\x24', '\x8', '\x16', '\x16', 'v', '\xA', '6', '\xBA', 'J', '\x9A', '\x80', '\x8', 'A', '\xB4', 'q', '\x85', 'X', '\x89', 'G', '\xB0', 'I', '\xA9', 'Q', '\x24', '\xCD', '\xA6', '\x8', '\xA4', 'H', 'c', '\x91', 'B', '\xB', '\xAF', 'V', '\xC1', 'F', '\xB4', '\x15', '\xCF', '\x22', 'X', '\x98', '\xB', 'T', 'H', '\x8A', 'd', '\x93', '\x8D', '\xFB', 'F', 'g', '\xC9', '\x1A', '\x14', '\x7D', '\xF0', 'f', 'v', 'f', '\xDF', '\x7C', '\xEF', '\xE7', 'g', 'F', '\xA8', '\xD5', 'j', 'H', '\x24', '\x12', '\x2A', '\x0', '\x5', '\xBF', 'G', '\xD4', '\xEF', '\xF7', '\x2F', '6', '\xEC', '\x12', '\x20', '\x1E', '\x8F', '\xD7', '\xAA', '\xD5', '\xEA', '\xAF', 'I', '5', 'F', '\xAA', 'T', '\x5F', '\x9F', '\x22', 'A', '\x2A', '\x95', '\xA', '\x83', '\xE5', 'r', '9', 'd', '\xB3', 'Y', '\x96', '\x99', 'L', '\x6', '\xE9', 't', '\x9A', '\x25', '\x85', '\x2C', '\xCB', 'T', '\xA7', '\xC4', 'b', '1', '\xB5', '\x5E', '\x0', '\x3', 'h', '\x9A', '\xC6', '\x16', '\x82', '\x20', 'X', 'R', '\x14', 'E', '6', 'S', '\x94', '\xCB', 'e', 'x', '\xBD', '\x5E', '\xAA', 'U', 'T', '\x23', 'L', '\xC0', '\xE0', '\xE2', '\xC1', '\x8F', '\x0', '\x9E', '\xBC', '\x9', 'A', '\x7C', '\x3E', '\x1F', '\x83', 'D', '\x22', '\x11', '\xD5', 'T', '\x40', '\x3F', '8', '\x80', 'w', '\xE5', '3', '\x7', '\xB8', '\x5C', '\x2E', 'H', '\x92', '\x4', '\x87', '\xC3', '\x81', '\x40', '\x20', '\x40', 'g', '\x98', '\xE9', '6', '\x1A', '\xA6', 'g', '\x15', '\x4', '\xE3', '\xD7', '\xC8', '\xBD', '\x15', '\xE1', 'i', '\xB7', 'C', '\xAB', '\xEA', 'x', '\x2F', 'j', 'X', '\x92', '\xBB', '\x18', '\x20', '\x9F', '\xCF', '3', '\xC3', '\xB8', '\xE9', 'N', '\xA7', '\xD3', 'l', 'J', '\x0', 'i', '6', '\x7C', '\x8E', '\xE1', '\xFE', 'V', '\x84', '\xE7', '\x3C', '\x9F', 'r', '\x2B', '\x3A', 'B', '\x7B', '7', 'f', 'w', '\xAE', '\x8E', '\xE', '\xF3', '\xBD', 'R', '\xA9', 'd', '\x2', 'B', '\xAF', '\x85', '2', 'f', 'F', '\xBA', '\xC', '\xD9', '\x9F', '\x1D', '\x9A', 'l', '\x22', '\xE6', '\xC7', '\x3A', '\x2C', '\x80', '\xEF', '\xC1', '\x15', '\x90', '\x7', '\x93', '\xA2', '\x28', '\xA0', 'S', 'j', '\xB1', '\xB8', '\xDF', '\x29', '5', 'C', '\xE', '\x3F', 'X', '\xFC', '\x98', '\xDA', 'y', 'j', 'P', '\x40', '\x0', '\x87', '\xAE', '\x1B', '\x17', 'B', '\xB4', '\x3A', '\x3F', '\xBE', 'y', '\xC7', '\xA', '\x26', '\xB6', '\xEE', '\xD9', '\x9A', '\x60', '\x14', '\x93', '\xDB', '\x8F', '\xD', '\xA', '\x2E', '\xE9', '\x23', '\x95', '\x29', 'X', '\x0', '\x27', '\xEB', 'n', 'V', 'p', '\xBC', '\xD6', '\xCB', '\xD6', 'G', '\xAB', '\x3D', 'l', '\x7D', '\xB8', '\xD2', '\xDD', '\xA0', '\x60', '\x83', '\xBA', '\xEF', '\x5F', '\xA4', '\xEA', '\xCC', '\x2', 'N', '\xAE', '\x5E', 'p', '\x1A', '\xEC', '\xB3', '\x40', '9', '\xAC', '\xFE', '\xF2', '\x91', '\x89', 'g', '\x91', '\x85', '\x21', '\xA8', '\x87', '\xB7', 'X', '\x7E', '\x7E', '\x85', '\xBB', '\xCD', 'N', 'N', 'b', 't', '\x40', '\xFA', '\x93', '\x89', '\xEC', '\x1E', '\xEC', '\x86', '\x2', 'H', '\x26', '\x93', '\xD0', 'u', '\x1D', '\x7F', '\x9', '2', '\x95', '\xBF', '\x1F', '\xDB', '\xD7', 'c', '\x8A', '\x1A', '\xF7', '\x5C', '\xC1', '\xFF', '\x22', 'J', '\xC3', '\x87', '\x0', '\x3', '\x0', 'K', '\xBB', '\xF8', '\xD6', '\x2A', 'v', '\x98', 'I', '\x0', '\x0', '\x0', '\x0', 'I', 'E', 'N', 'D', '\xAE', 'B', '\x60', '\x82', }; void MimeType::init() { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "image/x-icon"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } std::string MimeType::getMime(const std::string &suffix) { pthread_once(&once_control, MimeType::init); if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } HttpData::HttpData(EventLoop *loop, int connfd) : loop_(loop), channel_(new Channel(loop, connfd)), fd_(connfd), error_(false), connectionState_(H_CONNECTED), method_(METHOD_GET), HTTPVersion_(HTTP_11), nowReadPos_(0), state_(STATE_PARSE_URI), hState_(H_START), keepAlive_(false) { // loop_->queueInLoop(bind(&HttpData::setHandlers, this)); channel_->setReadHandler(bind(&HttpData::handleRead, this)); channel_->setWriteHandler(bind(&HttpData::handleWrite, this)); channel_->setConnHandler(bind(&HttpData::handleConn, this)); } void HttpData::reset() { // inBuffer_.clear(); fileName_.clear(); path_.clear(); nowReadPos_ = 0; state_ = STATE_PARSE_URI; hState_ = H_START; headers_.clear(); // keepAlive_ = false; if (timer_.lock()) { shared_ptr my_timer(timer_.lock()); my_timer->clearReq(); timer_.reset(); } } void HttpData::seperateTimer() { // cout << "seperateTimer" << endl; if (timer_.lock()) { shared_ptr my_timer(timer_.lock()); my_timer->clearReq(); timer_.reset(); } } void HttpData::handleRead() { __uint32_t &events_ = channel_->getEvents(); do { bool zero = false; int read_num = readn(fd_, inBuffer_, zero); LOG << "Request: " << inBuffer_; if (connectionState_ == H_DISCONNECTING) { inBuffer_.clear(); break; } // cout << inBuffer_ << endl; if (read_num < 0) { perror("1"); error_ = true; handleError(fd_, 400, "Bad Request"); break; } // else if (read_num == 0) // { // error_ = true; // break; // } else if (zero) { // 有请求出现但是读不到数据,可能是Request // Aborted,或者来自网络的数据没有达到等原因 // 最可能是对端已经关闭了,统一按照对端已经关闭处理 // error_ = true; connectionState_ = H_DISCONNECTING; if (read_num == 0) { // error_ = true; break; } // cout << "readnum == 0" << endl; } if (state_ == STATE_PARSE_URI) { URIState flag = this->parseURI(); if (flag == PARSE_URI_AGAIN) break; else if (flag == PARSE_URI_ERROR) { perror("2"); LOG << "FD = " << fd_ << "," << inBuffer_ << "******"; inBuffer_.clear(); error_ = true; handleError(fd_, 400, "Bad Request"); break; } else state_ = STATE_PARSE_HEADERS; } if (state_ == STATE_PARSE_HEADERS) { HeaderState flag = this->parseHeaders(); if (flag == PARSE_HEADER_AGAIN) break; else if (flag == PARSE_HEADER_ERROR) { perror("3"); error_ = true; handleError(fd_, 400, "Bad Request"); break; } if (method_ == METHOD_POST) { // POST方法准备 state_ = STATE_RECV_BODY; } else { state_ = STATE_ANALYSIS; } } if (state_ == STATE_RECV_BODY) { int content_length = -1; if (headers_.find("Content-length") != headers_.end()) { content_length = stoi(headers_["Content-length"]); } else { // cout << "(state_ == STATE_RECV_BODY)" << endl; error_ = true; handleError(fd_, 400, "Bad Request: Lack of argument (Content-length)"); break; } if (static_cast(inBuffer_.size()) < content_length) break; state_ = STATE_ANALYSIS; } if (state_ == STATE_ANALYSIS) { AnalysisState flag = this->analysisRequest(); if (flag == ANALYSIS_SUCCESS) { state_ = STATE_FINISH; break; } else { // cout << "state_ == STATE_ANALYSIS" << endl; error_ = true; break; } } } while (false); // cout << "state_=" << state_ << endl; if (!error_) { if (outBuffer_.size() > 0) { handleWrite(); // events_ |= EPOLLOUT; } // error_ may change if (!error_ && state_ == STATE_FINISH) { this->reset(); if (inBuffer_.size() > 0) { if (connectionState_ != H_DISCONNECTING) handleRead(); } // if ((keepAlive_ || inBuffer_.size() > 0) && connectionState_ == // H_CONNECTED) // { // this->reset(); // events_ |= EPOLLIN; // } } else if (!error_ && connectionState_ != H_DISCONNECTED) events_ |= EPOLLIN; } } void HttpData::handleWrite() { if (!error_ && connectionState_ != H_DISCONNECTED) { __uint32_t &events_ = channel_->getEvents(); if (writen(fd_, outBuffer_) < 0) { perror("writen"); events_ = 0; error_ = true; } if (outBuffer_.size() > 0) events_ |= EPOLLOUT; } } void HttpData::handleConn() { seperateTimer(); __uint32_t &events_ = channel_->getEvents(); if (!error_ && connectionState_ == H_CONNECTED) { if (events_ != 0) { int timeout = DEFAULT_EXPIRED_TIME; if (keepAlive_) timeout = DEFAULT_KEEP_ALIVE_TIME; if ((events_ & EPOLLIN) && (events_ & EPOLLOUT)) { events_ = __uint32_t(0); events_ |= EPOLLOUT; } // events_ |= (EPOLLET | EPOLLONESHOT); events_ |= EPOLLET; loop_->updatePoller(channel_, timeout); } else if (keepAlive_) { events_ |= (EPOLLIN | EPOLLET); // events_ |= (EPOLLIN | EPOLLET | EPOLLONESHOT); int timeout = DEFAULT_KEEP_ALIVE_TIME; loop_->updatePoller(channel_, timeout); } else { // cout << "close normally" << endl; // loop_->shutdown(channel_); // loop_->runInLoop(bind(&HttpData::handleClose, shared_from_this())); events_ |= (EPOLLIN | EPOLLET); // events_ |= (EPOLLIN | EPOLLET | EPOLLONESHOT); int timeout = (DEFAULT_KEEP_ALIVE_TIME >> 1); loop_->updatePoller(channel_, timeout); } } else if (!error_ && connectionState_ == H_DISCONNECTING && (events_ & EPOLLOUT)) { events_ = (EPOLLOUT | EPOLLET); } else { // cout << "close with errors" << endl; loop_->runInLoop(bind(&HttpData::handleClose, shared_from_this())); } } URIState HttpData::parseURI() { string &str = inBuffer_; string cop = str; // 读到完整的请求行再开始解析请求 size_t pos = str.find('\r', nowReadPos_); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method int posGet = request_line.find("GET"); int posPost = request_line.find("POST"); int posHead = request_line.find("HEAD"); if (posGet >= 0) { pos = posGet; method_ = METHOD_GET; } else if (posPost >= 0) { pos = posPost; method_ = METHOD_POST; } else if (posHead >= 0) { pos = posHead; method_ = METHOD_HEAD; } else { return PARSE_URI_ERROR; } // filename pos = request_line.find("/", pos); if (pos < 0) { fileName_ = "index.html"; HTTPVersion_ = HTTP_11; return PARSE_URI_SUCCESS; } else { size_t _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { fileName_ = request_line.substr(pos + 1, _pos - pos - 1); size_t __pos = fileName_.find('?'); if (__pos >= 0) { fileName_ = fileName_.substr(0, __pos); } } else fileName_ = "index.html"; } pos = _pos; } // cout << "fileName_: " << fileName_ << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) return PARSE_URI_ERROR; else { if (request_line.size() - pos <= 3) return PARSE_URI_ERROR; else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPVersion_ = HTTP_10; else if (ver == "1.1") HTTPVersion_ = HTTP_11; else return PARSE_URI_ERROR; } } return PARSE_URI_SUCCESS; } HeaderState HttpData::parseHeaders() { string &str = inBuffer_; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; size_t i = 0; for (; i < str.size() && notFinish; ++i) { switch (hState_) { case H_START: { if (str[i] == '\n' || str[i] == '\r') break; hState_ = H_KEY; key_start = i; now_read_line_begin = i; break; } case H_KEY: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; hState_ = H_COLON; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case H_COLON: { if (str[i] == ' ') { hState_ = H_SPACES_AFTER_COLON; } else return PARSE_HEADER_ERROR; break; } case H_SPACES_AFTER_COLON: { hState_ = H_VALUE; value_start = i; break; } case H_VALUE: { if (str[i] == '\r') { hState_ = H_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case H_CR: { if (str[i] == '\n') { hState_ = H_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers_[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case H_LF: { if (str[i] == '\r') { hState_ = H_END_CR; } else { key_start = i; hState_ = H_KEY; } break; } case H_END_CR: { if (str[i] == '\n') { hState_ = H_END_LF; } else return PARSE_HEADER_ERROR; break; } case H_END_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (hState_ == H_END_LF) { str = str.substr(i); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } AnalysisState HttpData::analysisRequest() { if (method_ == METHOD_POST) { // ------------------------------------------------------ // My CV stitching handler which requires OpenCV library // ------------------------------------------------------ // string header; // header += string("HTTP/1.1 200 OK\r\n"); // if(headers_.find("Connection") != headers_.end() && // headers_["Connection"] == "Keep-Alive") // { // keepAlive_ = true; // header += string("Connection: Keep-Alive\r\n") + "Keep-Alive: // timeout=" + to_string(DEFAULT_KEEP_ALIVE_TIME) + "\r\n"; // } // int length = stoi(headers_["Content-length"]); // vector data(inBuffer_.begin(), inBuffer_.begin() + length); // Mat src = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); // //imwrite("receive.bmp", src); // Mat res = stitch(src); // vector data_encode; // imencode(".png", res, data_encode); // header += string("Content-length: ") + to_string(data_encode.size()) + // "\r\n\r\n"; // outBuffer_ += header + string(data_encode.begin(), data_encode.end()); // inBuffer_ = inBuffer_.substr(length); // return ANALYSIS_SUCCESS; } else if (method_ == METHOD_GET || method_ == METHOD_HEAD) { string header; header += "HTTP/1.1 200 OK\r\n"; if (headers_.find("Connection") != headers_.end() && (headers_["Connection"] == "Keep-Alive" || headers_["Connection"] == "keep-alive")) { keepAlive_ = true; header += string("Connection: Keep-Alive\r\n") + "Keep-Alive: timeout=" + to_string(DEFAULT_KEEP_ALIVE_TIME) + "\r\n"; } int dot_pos = fileName_.find('.'); string filetype; if (dot_pos < 0) filetype = MimeType::getMime("default"); else filetype = MimeType::getMime(fileName_.substr(dot_pos)); // echo test if (fileName_ == "hello") { outBuffer_ = "HTTP/1.1 200 OK\r\nContent-type: text/plain\r\n\r\nHello World"; return ANALYSIS_SUCCESS; } if (fileName_ == "favicon.ico") { header += "Content-Type: image/png\r\n"; header += "Content-Length: " + to_string(sizeof favicon) + "\r\n"; header += "Server: LinYa's Web Server\r\n"; header += "\r\n"; outBuffer_ += header; outBuffer_ += string(favicon, favicon + sizeof favicon); ; return ANALYSIS_SUCCESS; } struct stat sbuf; if (stat(fileName_.c_str(), &sbuf) < 0) { header.clear(); handleError(fd_, 404, "Not Found!"); return ANALYSIS_ERROR; } header += "Content-Type: " + filetype + "\r\n"; header += "Content-Length: " + to_string(sbuf.st_size) + "\r\n"; header += "Server: LinYa's Web Server\r\n"; // 头部结束 header += "\r\n"; outBuffer_ += header; if (method_ == METHOD_HEAD) return ANALYSIS_SUCCESS; int src_fd = open(fileName_.c_str(), O_RDONLY, 0); if (src_fd < 0) { outBuffer_.clear(); handleError(fd_, 404, "Not Found!"); return ANALYSIS_ERROR; } void *mmapRet = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0); close(src_fd); if (mmapRet == (void *)-1) { munmap(mmapRet, sbuf.st_size); outBuffer_.clear(); handleError(fd_, 404, "Not Found!"); return ANALYSIS_ERROR; } char *src_addr = static_cast(mmapRet); outBuffer_ += string(src_addr, src_addr + sbuf.st_size); ; munmap(mmapRet, sbuf.st_size); return ANALYSIS_SUCCESS; } return ANALYSIS_ERROR; } void HttpData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[4096]; string body_buff, header_buff; body_buff += "哎~出错了"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-Type: text/html\r\n"; header_buff += "Connection: Close\r\n"; header_buff += "Content-Length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "Server: LinYa's Web Server\r\n"; ; header_buff += "\r\n"; // 错误处理不考虑writen不完的情况 sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } void HttpData::handleClose() { connectionState_ = H_DISCONNECTED; shared_ptr guard(shared_from_this()); loop_->removeFromPoller(channel_); } void HttpData::newEvent() { channel_->setEvents(DEFAULT_EVENT); loop_->addToPoller(channel_, DEFAULT_EXPIRED_TIME); } ================================================ FILE: WebServer/HttpData.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include #include #include #include "Timer.h" class EventLoop; class TimerNode; class Channel; enum ProcessState { STATE_PARSE_URI = 1, STATE_PARSE_HEADERS, STATE_RECV_BODY, STATE_ANALYSIS, STATE_FINISH }; enum URIState { PARSE_URI_AGAIN = 1, PARSE_URI_ERROR, PARSE_URI_SUCCESS, }; enum HeaderState { PARSE_HEADER_SUCCESS = 1, PARSE_HEADER_AGAIN, PARSE_HEADER_ERROR }; enum AnalysisState { ANALYSIS_SUCCESS = 1, ANALYSIS_ERROR }; enum ParseState { H_START = 0, H_KEY, H_COLON, H_SPACES_AFTER_COLON, H_VALUE, H_CR, H_LF, H_END_CR, H_END_LF }; enum ConnectionState { H_CONNECTED = 0, H_DISCONNECTING, H_DISCONNECTED }; enum HttpMethod { METHOD_POST = 1, METHOD_GET, METHOD_HEAD }; enum HttpVersion { HTTP_10 = 1, HTTP_11 }; class MimeType { private: static void init(); static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); private: static pthread_once_t once_control; }; class HttpData : public std::enable_shared_from_this { public: HttpData(EventLoop *loop, int connfd); ~HttpData() { close(fd_); } void reset(); void seperateTimer(); void linkTimer(std::shared_ptr mtimer) { // shared_ptr重载了bool, 但weak_ptr没有 timer_ = mtimer; } std::shared_ptr getChannel() { return channel_; } EventLoop *getLoop() { return loop_; } void handleClose(); void newEvent(); private: EventLoop *loop_; std::shared_ptr channel_; int fd_; std::string inBuffer_; std::string outBuffer_; bool error_; ConnectionState connectionState_; HttpMethod method_; HttpVersion HTTPVersion_; std::string fileName_; std::string path_; int nowReadPos_; ProcessState state_; ParseState hState_; bool keepAlive_; std::map headers_; std::weak_ptr timer_; void handleRead(); void handleWrite(); void handleConn(); void handleError(int fd, int err_num, std::string short_msg); URIState parseURI(); HeaderState parseHeaders(); AnalysisState analysisRequest(); }; ================================================ FILE: WebServer/Main.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include #include #include "EventLoop.h" #include "Server.h" #include "base/Logging.h" int main(int argc, char *argv[]) { int threadNum = 4; int port = 80; std::string logPath = "./WebServer.log"; // parse args int opt; const char *str = "t:l:p:"; while ((opt = getopt(argc, argv, str)) != -1) { switch (opt) { case 't': { threadNum = atoi(optarg); break; } case 'l': { logPath = optarg; if (logPath.size() < 2 || optarg[0] != '/') { printf("logPath should start with \"/\"\n"); abort(); } break; } case 'p': { port = atoi(optarg); break; } default: break; } } Logger::setLogFileName(logPath); // STL库在多线程上应用 #ifndef _PTHREADS LOG << "_PTHREADS is not defined !"; #endif EventLoop mainLoop; Server myHTTPServer(&mainLoop, threadNum, port); myHTTPServer.start(); mainLoop.loop(); return 0; } ================================================ FILE: WebServer/Makefile ================================================ # MAINSOURCE代表含有main入口函数的cpp文件,因为含有测试代码, # 所以要为多个目标编译,这里把Makefile写的通用了一点, # 以后加东西Makefile不用做多少改动 MAINSOURCE := Main.cpp base/tests/LoggingTest.cpp tests/HTTPClient.cpp # MAINOBJS := $(patsubst %.cpp,%.o,$(MAINSOURCE)) SOURCE := $(wildcard *.cpp base/*.cpp tests/*.cpp) override SOURCE := $(filter-out $(MAINSOURCE),$(SOURCE)) OBJS := $(patsubst %.cpp,%.o,$(SOURCE)) TARGET := WebServer CC := g++ LIBS := -lpthread INCLUDE:= -I./usr/local/lib CFLAGS := -std=c++11 -g -Wall -O3 -D_PTHREADS CXXFLAGS:= $(CFLAGS) # Test object SUBTARGET1 := LoggingTest SUBTARGET2 := HTTPClient .PHONY : objs clean veryclean rebuild all tests debug all : $(TARGET) $(SUBTARGET1) $(SUBTARGET2) objs : $(OBJS) rebuild: veryclean all tests : $(SUBTARGET1) $(SUBTARGET2) clean : find . -name '*.o' | xargs rm -f veryclean : find . -name '*.o' | xargs rm -f find . -name $(TARGET) | xargs rm -f find . -name $(SUBTARGET1) | xargs rm -f find . -name $(SUBTARGET2) | xargs rm -f debug: @echo $(SOURCE) $(TARGET) : $(OBJS) Main.o $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) # $@代表目标,这里是$(TARGET) $(SUBTARGET1) : $(OBJS) base/tests/LoggingTest.o $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) $(SUBTARGET2) : $(OBJS) tests/HTTPClient.o $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) ================================================ FILE: WebServer/Makefile.bak ================================================ # @Author Lin Ya # @Email xxbbb@vip.qq.com # MAINSOURCE代表含有main入口函数的cpp文件,因为含有测试代码, # 所以要为多个目标编译,这里把Makefile写的通用了一点, # 以后加东西Makefile不用做多少改动 MAINSOURCE := Main.cpp base/tests/LoggingTest.cpp tests/HTTPClient.cpp # MAINOBJS := $(patsubst %.cpp,%.o,$(MAINSOURCE)) SOURCE := $(wildcard *.cpp base/*.cpp tests/*.cpp) override SOURCE := $(filter-out $(MAINSOURCE),$(SOURCE)) OBJS := $(patsubst %.cpp,%.o,$(SOURCE)) TARGET := WebServer CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs INCLUDE:= -I./usr/local/lib CFLAGS := -std=c++11 -g -Wall -O3 -D_PTHREADS CXXFLAGS:= $(CFLAGS) # Test object SUBTARGET1 := LoggingTest SUBTARGET2 := HTTPClient .PHONY : objs clean veryclean rebuild all tests debug all : $(TARGET) $(SUBTARGET1) $(SUBTARGET2) objs : $(OBJS) rebuild: veryclean all tests : $(SUBTARGET1) $(SUBTARGET2) clean : find . -name '*.o' | xargs rm -f veryclean : find . -name '*.o' | xargs rm -f find . -name $(TARGET) | xargs rm -f find . -name $(SUBTARGET1) | xargs rm -f find . -name $(SUBTARGET2) | xargs rm -f debug: @echo $(SOURCE) $(TARGET) : $(OBJS) Main.o $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) # $@代表目标,这里是$(TARGET) $(SUBTARGET1) : $(OBJS) base/tests/LoggingTest.o $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) $(SUBTARGET2) : $(OBJS) tests/HTTPClient.o $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) ================================================ FILE: WebServer/Server.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Server.h" #include #include #include #include #include "Util.h" #include "base/Logging.h" Server::Server(EventLoop *loop, int threadNum, int port) : loop_(loop), threadNum_(threadNum), eventLoopThreadPool_(new EventLoopThreadPool(loop_, threadNum)), started_(false), acceptChannel_(new Channel(loop_)), port_(port), listenFd_(socket_bind_listen(port_)) { acceptChannel_->setFd(listenFd_); handle_for_sigpipe(); if (setSocketNonBlocking(listenFd_) < 0) { perror("set socket non block failed"); abort(); } } void Server::start() { eventLoopThreadPool_->start(); // acceptChannel_->setEvents(EPOLLIN | EPOLLET | EPOLLONESHOT); acceptChannel_->setEvents(EPOLLIN | EPOLLET); acceptChannel_->setReadHandler(bind(&Server::handNewConn, this)); acceptChannel_->setConnHandler(bind(&Server::handThisConn, this)); loop_->addToPoller(acceptChannel_, 0); started_ = true; } void Server::handNewConn() { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_addr_len = sizeof(client_addr); int accept_fd = 0; while ((accept_fd = accept(listenFd_, (struct sockaddr *)&client_addr, &client_addr_len)) > 0) { EventLoop *loop = eventLoopThreadPool_->getNextLoop(); LOG << "New connection from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port); // cout << "new connection" << endl; // cout << inet_ntoa(client_addr.sin_addr) << endl; // cout << ntohs(client_addr.sin_port) << endl; /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 限制服务器的最大并发连接数 if (accept_fd >= MAXFDS) { close(accept_fd); continue; } // 设为非阻塞模式 if (setSocketNonBlocking(accept_fd) < 0) { LOG << "Set non block failed!"; // perror("Set non block failed!"); return; } setSocketNodelay(accept_fd); // setSocketNoLinger(accept_fd); shared_ptr req_info(new HttpData(loop, accept_fd)); req_info->getChannel()->setHolder(req_info); loop->queueInLoop(std::bind(&HttpData::newEvent, req_info)); } acceptChannel_->setEvents(EPOLLIN | EPOLLET); } ================================================ FILE: WebServer/Server.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include "Channel.h" #include "EventLoop.h" #include "EventLoopThreadPool.h" class Server { public: Server(EventLoop *loop, int threadNum, int port); ~Server() {} EventLoop *getLoop() const { return loop_; } void start(); void handNewConn(); void handThisConn() { loop_->updatePoller(acceptChannel_); } private: EventLoop *loop_; int threadNum_; std::unique_ptr eventLoopThreadPool_; bool started_; std::shared_ptr acceptChannel_; int port_; int listenFd_; static const int MAXFDS = 100000; }; ================================================ FILE: WebServer/ThreadPool.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com // This file has not been used #include "ThreadPool.h" pthread_mutex_t ThreadPool::lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ThreadPool::notify = PTHREAD_COND_INITIALIZER; std::vector ThreadPool::threads; std::vector ThreadPool::queue; int ThreadPool::thread_count = 0; int ThreadPool::queue_size = 0; int ThreadPool::head = 0; int ThreadPool::tail = 0; int ThreadPool::count = 0; int ThreadPool::shutdown = 0; int ThreadPool::started = 0; int ThreadPool::threadpool_create(int _thread_count, int _queue_size) { bool err = false; do { if(_thread_count <= 0 || _thread_count > MAX_THREADS || _queue_size <= 0 || _queue_size > MAX_QUEUE) { _thread_count = 4; _queue_size = 1024; } thread_count = 0; queue_size = _queue_size; head = tail = count = 0; shutdown = started = 0; threads.resize(_thread_count); queue.resize(_queue_size); /* Start worker threads */ for(int i = 0; i < _thread_count; ++i) { if(pthread_create(&threads[i], NULL, threadpool_thread, (void*)(0)) != 0) { //threadpool_destroy(pool, 0); return -1; } ++thread_count; ++started; } } while(false); if (err) { //threadpool_free(pool); return -1; } return 0; } int ThreadPool::threadpool_add(std::shared_ptr args, std::function)> fun) { int next, err = 0; if(pthread_mutex_lock(&lock) != 0) return THREADPOOL_LOCK_FAILURE; do { next = (tail + 1) % queue_size; // 队列满 if(count == queue_size) { err = THREADPOOL_QUEUE_FULL; break; } // 已关闭 if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } queue[tail].fun = fun; queue[tail].args = args; tail = next; ++count; /* pthread_cond_broadcast */ if(pthread_cond_signal(¬ify) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&lock) != 0) err = THREADPOOL_LOCK_FAILURE; return err; } int ThreadPool::threadpool_destroy(ShutDownOption shutdown_option) { printf("Thread pool destroy !\n"); int i, err = 0; if(pthread_mutex_lock(&lock) != 0) { return THREADPOOL_LOCK_FAILURE; } do { if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } shutdown = shutdown_option; if((pthread_cond_broadcast(¬ify) != 0) || (pthread_mutex_unlock(&lock) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } for(i = 0; i < thread_count; ++i) { if(pthread_join(threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); if(!err) { threadpool_free(); } return err; } int ThreadPool::threadpool_free() { if(started > 0) return -1; pthread_mutex_lock(&lock); pthread_mutex_destroy(&lock); pthread_cond_destroy(¬ify); return 0; } void *ThreadPool::threadpool_thread(void *args) { while (true) { ThreadPoolTask task; pthread_mutex_lock(&lock); while((count == 0) && (!shutdown)) { pthread_cond_wait(¬ify, &lock); } if((shutdown == immediate_shutdown) || ((shutdown == graceful_shutdown) && (count == 0))) { break; } task.fun = queue[head].fun; task.args = queue[head].args; queue[head].fun = NULL; queue[head].args.reset(); head = (head + 1) % queue_size; --count; pthread_mutex_unlock(&lock); (task.fun)(task.args); } --started; pthread_mutex_unlock(&lock); printf("This threadpool thread finishs!\n"); pthread_exit(NULL); return(NULL); } ================================================ FILE: WebServer/ThreadPool.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com // This file has not been used #pragma once #include "Channel.h" #include #include #include #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } ShutDownOption; struct ThreadPoolTask { std::function)> fun; std::shared_ptr args; }; class ThreadPool { private: static pthread_mutex_t lock; static pthread_cond_t notify; static std::vector threads; static std::vector queue; static int thread_count; static int queue_size; static int head; // tail 指向尾节点的下一节点 static int tail; static int count; static int shutdown; static int started; public: static int threadpool_create(int _thread_count, int _queue_size); static int threadpool_add(std::shared_ptr args, std::function)> fun); static int threadpool_destroy(ShutDownOption shutdown_option = graceful_shutdown); static int threadpool_free(); static void *threadpool_thread(void *args); }; ================================================ FILE: WebServer/Timer.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Timer.h" #include #include #include TimerNode::TimerNode(std::shared_ptr requestData, int timeout) : deleted_(false), SPHttpData(requestData) { struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expiredTime_ = (((now.tv_sec % 10000) * 1000) + (now.tv_usec / 1000)) + timeout; } TimerNode::~TimerNode() { if (SPHttpData) SPHttpData->handleClose(); } TimerNode::TimerNode(TimerNode &tn) : SPHttpData(tn.SPHttpData), expiredTime_(0) {} void TimerNode::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expiredTime_ = (((now.tv_sec % 10000) * 1000) + (now.tv_usec / 1000)) + timeout; } bool TimerNode::isValid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = (((now.tv_sec % 10000) * 1000) + (now.tv_usec / 1000)); if (temp < expiredTime_) return true; else { this->setDeleted(); return false; } } void TimerNode::clearReq() { SPHttpData.reset(); this->setDeleted(); } TimerManager::TimerManager() {} TimerManager::~TimerManager() {} void TimerManager::addTimer(std::shared_ptr SPHttpData, int timeout) { SPTimerNode new_node(new TimerNode(SPHttpData, timeout)); timerNodeQueue.push(new_node); SPHttpData->linkTimer(new_node); } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请RequestData节点了,这样可以继续重复利用前面的RequestData,减少了一次delete和一次new的时间。 */ void TimerManager::handleExpiredEvent() { // MutexLockGuard locker(lock); while (!timerNodeQueue.empty()) { SPTimerNode ptimer_now = timerNodeQueue.top(); if (ptimer_now->isDeleted()) timerNodeQueue.pop(); else if (ptimer_now->isValid() == false) timerNodeQueue.pop(); else break; } } ================================================ FILE: WebServer/Timer.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include "HttpData.h" #include "base/MutexLock.h" #include "base/noncopyable.h" class HttpData; class TimerNode { public: TimerNode(std::shared_ptr requestData, int timeout); ~TimerNode(); TimerNode(TimerNode &tn); void update(int timeout); bool isValid(); void clearReq(); void setDeleted() { deleted_ = true; } bool isDeleted() const { return deleted_; } size_t getExpTime() const { return expiredTime_; } private: bool deleted_; size_t expiredTime_; std::shared_ptr SPHttpData; }; struct TimerCmp { bool operator()(std::shared_ptr &a, std::shared_ptr &b) const { return a->getExpTime() > b->getExpTime(); } }; class TimerManager { public: TimerManager(); ~TimerManager(); void addTimer(std::shared_ptr SPHttpData, int timeout); void handleExpiredEvent(); private: typedef std::shared_ptr SPTimerNode; std::priority_queue, TimerCmp> timerNodeQueue; // MutexLock lock; }; ================================================ FILE: WebServer/Util.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Util.h" #include #include #include #include #include #include #include #include const int MAX_BUFF = 4096; ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char *)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t readn(int fd, std::string &inBuffer, bool &zero) { ssize_t nread = 0; ssize_t readSum = 0; while (true) { char buff[MAX_BUFF]; if ((nread = read(fd, buff, MAX_BUFF)) < 0) { if (errno == EINTR) continue; else if (errno == EAGAIN) { return readSum; } else { perror("read error"); return -1; } } else if (nread == 0) { // printf("redsum = %d\n", readSum); zero = true; break; } // printf("before inBuffer.size() = %d\n", inBuffer.size()); // printf("nread = %d\n", nread); readSum += nread; // buff += nread; inBuffer += std::string(buff, buff + nread); // printf("after inBuffer.size() = %d\n", inBuffer.size()); } return readSum; } ssize_t readn(int fd, std::string &inBuffer) { ssize_t nread = 0; ssize_t readSum = 0; while (true) { char buff[MAX_BUFF]; if ((nread = read(fd, buff, MAX_BUFF)) < 0) { if (errno == EINTR) continue; else if (errno == EAGAIN) { return readSum; } else { perror("read error"); return -1; } } else if (nread == 0) { // printf("redsum = %d\n", readSum); break; } // printf("before inBuffer.size() = %d\n", inBuffer.size()); // printf("nread = %d\n", nread); readSum += nread; // buff += nread; inBuffer += std::string(buff, buff + nread); // printf("after inBuffer.size() = %d\n", inBuffer.size()); } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char *)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR) { nwritten = 0; continue; } else if (errno == EAGAIN) { return writeSum; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } ssize_t writen(int fd, std::string &sbuff) { size_t nleft = sbuff.size(); ssize_t nwritten = 0; ssize_t writeSum = 0; const char *ptr = sbuff.c_str(); while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR) { nwritten = 0; continue; } else if (errno == EAGAIN) break; else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } if (writeSum == static_cast(sbuff.size())) sbuff.clear(); else sbuff = sbuff.substr(writeSum); return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if (sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if (flag == -1) return -1; flag |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } void setSocketNodelay(int fd) { int enable = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&enable, sizeof(enable)); } void setSocketNoLinger(int fd) { struct linger linger_; linger_.l_onoff = 1; linger_.l_linger = 30; setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&linger_, sizeof(linger_)); } void shutDownWR(int fd) { shutdown(fd, SHUT_WR); // printf("shutdown\n"); } int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 0 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { close(listen_fd); return -1; } // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { close(listen_fd); return -1; } // 开始监听,最大等待队列长为LISTENQ if (listen(listen_fd, 2048) == -1) { close(listen_fd); return -1; } // 无效监听描述符 if (listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } ================================================ FILE: WebServer/Util.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include ssize_t readn(int fd, void *buff, size_t n); ssize_t readn(int fd, std::string &inBuffer, bool &zero); ssize_t readn(int fd, std::string &inBuffer); ssize_t writen(int fd, void *buff, size_t n); ssize_t writen(int fd, std::string &sbuff); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); void setSocketNodelay(int fd); void setSocketNoLinger(int fd); void shutDownWR(int fd); int socket_bind_listen(int port); ================================================ FILE: WebServer/base/AsyncLogging.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "AsyncLogging.h" #include #include #include #include #include "LogFile.h" AsyncLogging::AsyncLogging(std::string logFileName_, int flushInterval) : flushInterval_(flushInterval), running_(false), basename_(logFileName_), thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"), mutex_(), cond_(mutex_), currentBuffer_(new Buffer), nextBuffer_(new Buffer), buffers_(), latch_(1) { assert(logFileName_.size() > 1); currentBuffer_->bzero(); nextBuffer_->bzero(); buffers_.reserve(16); } void AsyncLogging::append(const char* logline, int len) { MutexLockGuard lock(mutex_); if (currentBuffer_->avail() > len) currentBuffer_->append(logline, len); else { buffers_.push_back(currentBuffer_); currentBuffer_.reset(); if (nextBuffer_) currentBuffer_ = std::move(nextBuffer_); else currentBuffer_.reset(new Buffer); currentBuffer_->append(logline, len); cond_.notify(); } } void AsyncLogging::threadFunc() { assert(running_ == true); latch_.countDown(); LogFile output(basename_); BufferPtr newBuffer1(new Buffer); BufferPtr newBuffer2(new Buffer); newBuffer1->bzero(); newBuffer2->bzero(); BufferVector buffersToWrite; buffersToWrite.reserve(16); while (running_) { assert(newBuffer1 && newBuffer1->length() == 0); assert(newBuffer2 && newBuffer2->length() == 0); assert(buffersToWrite.empty()); { MutexLockGuard lock(mutex_); if (buffers_.empty()) // unusual usage! { cond_.waitForSeconds(flushInterval_); } buffers_.push_back(currentBuffer_); currentBuffer_.reset(); currentBuffer_ = std::move(newBuffer1); buffersToWrite.swap(buffers_); if (!nextBuffer_) { nextBuffer_ = std::move(newBuffer2); } } assert(!buffersToWrite.empty()); if (buffersToWrite.size() > 25) { // char buf[256]; // snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger // buffers\n", // Timestamp::now().toFormattedString().c_str(), // buffersToWrite.size()-2); // fputs(buf, stderr); // output.append(buf, static_cast(strlen(buf))); buffersToWrite.erase(buffersToWrite.begin() + 2, buffersToWrite.end()); } for (size_t i = 0; i < buffersToWrite.size(); ++i) { // FIXME: use unbuffered stdio FILE ? or use ::writev ? output.append(buffersToWrite[i]->data(), buffersToWrite[i]->length()); } if (buffersToWrite.size() > 2) { // drop non-bzero-ed buffers, avoid trashing buffersToWrite.resize(2); } if (!newBuffer1) { assert(!buffersToWrite.empty()); newBuffer1 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer1->reset(); } if (!newBuffer2) { assert(!buffersToWrite.empty()); newBuffer2 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer2->reset(); } buffersToWrite.clear(); output.flush(); } output.flush(); } ================================================ FILE: WebServer/base/AsyncLogging.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include "CountDownLatch.h" #include "LogStream.h" #include "MutexLock.h" #include "Thread.h" #include "noncopyable.h" class AsyncLogging : noncopyable { public: AsyncLogging(const std::string basename, int flushInterval = 2); ~AsyncLogging() { if (running_) stop(); } void append(const char* logline, int len); void start() { running_ = true; thread_.start(); latch_.wait(); } void stop() { running_ = false; cond_.notify(); thread_.join(); } private: void threadFunc(); typedef FixedBuffer Buffer; typedef std::vector> BufferVector; typedef std::shared_ptr BufferPtr; const int flushInterval_; bool running_; std::string basename_; Thread thread_; MutexLock mutex_; Condition cond_; BufferPtr currentBuffer_; BufferPtr nextBuffer_; BufferVector buffers_; CountDownLatch latch_; }; ================================================ FILE: WebServer/base/CMakeLists.txt ================================================ set(LIB_SRC AsyncLogging.cpp CountDownLatch.cpp FileUtil.cpp LogFile.cpp Logging.cpp LogStream.cpp Thread.cpp ) add_library(libserver_base ${LIB_SRC}) target_link_libraries(libserver_base pthread rt) set_target_properties(libserver_base PROPERTIES OUTPUT_NAME "server_base") add_subdirectory(tests) ================================================ FILE: WebServer/base/Condition.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include #include "MutexLock.h" #include "noncopyable.h" class Condition : noncopyable { public: explicit Condition(MutexLock &_mutex) : mutex(_mutex) { pthread_cond_init(&cond, NULL); } ~Condition() { pthread_cond_destroy(&cond); } void wait() { pthread_cond_wait(&cond, mutex.get()); } void notify() { pthread_cond_signal(&cond); } void notifyAll() { pthread_cond_broadcast(&cond); } bool waitForSeconds(int seconds) { struct timespec abstime; clock_gettime(CLOCK_REALTIME, &abstime); abstime.tv_sec += static_cast(seconds); return ETIMEDOUT == pthread_cond_timedwait(&cond, mutex.get(), &abstime); } private: MutexLock &mutex; pthread_cond_t cond; }; ================================================ FILE: WebServer/base/CountDownLatch.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "CountDownLatch.h" CountDownLatch::CountDownLatch(int count) : mutex_(), condition_(mutex_), count_(count) {} void CountDownLatch::wait() { MutexLockGuard lock(mutex_); while (count_ > 0) condition_.wait(); } void CountDownLatch::countDown() { MutexLockGuard lock(mutex_); --count_; if (count_ == 0) condition_.notifyAll(); } ================================================ FILE: WebServer/base/CountDownLatch.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include "Condition.h" #include "MutexLock.h" #include "noncopyable.h" // CountDownLatch的主要作用是确保Thread中传进去的func真的启动了以后 // 外层的start才返回 class CountDownLatch : noncopyable { public: explicit CountDownLatch(int count); void wait(); void countDown(); private: mutable MutexLock mutex_; Condition condition_; int count_; }; ================================================ FILE: WebServer/base/CurrentThread.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include namespace CurrentThread { // internal extern __thread int t_cachedTid; extern __thread char t_tidString[32]; extern __thread int t_tidStringLength; extern __thread const char* t_threadName; void cacheTid(); inline int tid() { if (__builtin_expect(t_cachedTid == 0, 0)) { cacheTid(); } return t_cachedTid; } inline const char* tidString() // for logging { return t_tidString; } inline int tidStringLength() // for logging { return t_tidStringLength; } inline const char* name() { return t_threadName; } } ================================================ FILE: WebServer/base/FileUtil.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "FileUtil.h" #include #include #include #include #include #include using namespace std; AppendFile::AppendFile(string filename) : fp_(fopen(filename.c_str(), "ae")) { // 用户提供缓冲区 setbuffer(fp_, buffer_, sizeof buffer_); } AppendFile::~AppendFile() { fclose(fp_); } void AppendFile::append(const char* logline, const size_t len) { size_t n = this->write(logline, len); size_t remain = len - n; while (remain > 0) { size_t x = this->write(logline + n, remain); if (x == 0) { int err = ferror(fp_); if (err) fprintf(stderr, "AppendFile::append() failed !\n"); break; } n += x; remain = len - n; } } void AppendFile::flush() { fflush(fp_); } size_t AppendFile::write(const char* logline, size_t len) { return fwrite_unlocked(logline, 1, len, fp_); } ================================================ FILE: WebServer/base/FileUtil.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include "noncopyable.h" class AppendFile : noncopyable { public: explicit AppendFile(std::string filename); ~AppendFile(); // append 会向文件写 void append(const char *logline, const size_t len); void flush(); private: size_t write(const char *logline, size_t len); FILE *fp_; char buffer_[64 * 1024]; }; ================================================ FILE: WebServer/base/LogFile.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "LogFile.h" #include #include #include #include "FileUtil.h" using namespace std; LogFile::LogFile(const string& basename, int flushEveryN) : basename_(basename), flushEveryN_(flushEveryN), count_(0), mutex_(new MutexLock) { // assert(basename.find('/') >= 0); file_.reset(new AppendFile(basename)); } LogFile::~LogFile() {} void LogFile::append(const char* logline, int len) { MutexLockGuard lock(*mutex_); append_unlocked(logline, len); } void LogFile::flush() { MutexLockGuard lock(*mutex_); file_->flush(); } void LogFile::append_unlocked(const char* logline, int len) { file_->append(logline, len); ++count_; if (count_ >= flushEveryN_) { count_ = 0; file_->flush(); } } ================================================ FILE: WebServer/base/LogFile.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include "FileUtil.h" #include "MutexLock.h" #include "noncopyable.h" // TODO 提供自动归档功能 class LogFile : noncopyable { public: // 每被append flushEveryN次,flush一下,会往文件写,只不过,文件也是带缓冲区的 LogFile(const std::string& basename, int flushEveryN = 1024); ~LogFile(); void append(const char* logline, int len); void flush(); bool rollFile(); private: void append_unlocked(const char* logline, int len); const std::string basename_; const int flushEveryN_; int count_; std::unique_ptr mutex_; std::unique_ptr file_; }; ================================================ FILE: WebServer/base/LogStream.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "LogStream.h" #include #include #include #include #include #include const char digits[] = "9876543210123456789"; const char* zero = digits + 9; // From muduo template size_t convert(char buf[], T value) { T i = value; char* p = buf; do { int lsd = static_cast(i % 10); i /= 10; *p++ = zero[lsd]; } while (i != 0); if (value < 0) { *p++ = '-'; } *p = '\0'; std::reverse(buf, p); return p - buf; } template class FixedBuffer; template class FixedBuffer; template void LogStream::formatInteger(T v) { // buffer容不下kMaxNumericSize个字符的话会被直接丢弃 if (buffer_.avail() >= kMaxNumericSize) { size_t len = convert(buffer_.current(), v); buffer_.add(len); } } LogStream& LogStream::operator<<(short v) { *this << static_cast(v); return *this; } LogStream& LogStream::operator<<(unsigned short v) { *this << static_cast(v); return *this; } LogStream& LogStream::operator<<(int v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(unsigned int v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(unsigned long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(long long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(unsigned long long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(double v) { if (buffer_.avail() >= kMaxNumericSize) { int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12g", v); buffer_.add(len); } return *this; } LogStream& LogStream::operator<<(long double v) { if (buffer_.avail() >= kMaxNumericSize) { int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12Lg", v); buffer_.add(len); } return *this; } ================================================ FILE: WebServer/base/LogStream.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include "noncopyable.h" class AsyncLogging; const int kSmallBuffer = 4000; const int kLargeBuffer = 4000 * 1000; template class FixedBuffer : noncopyable { public: FixedBuffer() : cur_(data_) {} ~FixedBuffer() {} void append(const char* buf, size_t len) { if (avail() > static_cast(len)) { memcpy(cur_, buf, len); cur_ += len; } } const char* data() const { return data_; } int length() const { return static_cast(cur_ - data_); } char* current() { return cur_; } int avail() const { return static_cast(end() - cur_); } void add(size_t len) { cur_ += len; } void reset() { cur_ = data_; } void bzero() { memset(data_, 0, sizeof data_); } private: const char* end() const { return data_ + sizeof data_; } char data_[SIZE]; char* cur_; }; class LogStream : noncopyable { public: typedef FixedBuffer Buffer; LogStream& operator<<(bool v) { buffer_.append(v ? "1" : "0", 1); return *this; } LogStream& operator<<(short); LogStream& operator<<(unsigned short); LogStream& operator<<(int); LogStream& operator<<(unsigned int); LogStream& operator<<(long); LogStream& operator<<(unsigned long); LogStream& operator<<(long long); LogStream& operator<<(unsigned long long); LogStream& operator<<(const void*); LogStream& operator<<(float v) { *this << static_cast(v); return *this; } LogStream& operator<<(double); LogStream& operator<<(long double); LogStream& operator<<(char v) { buffer_.append(&v, 1); return *this; } LogStream& operator<<(const char* str) { if (str) buffer_.append(str, strlen(str)); else buffer_.append("(null)", 6); return *this; } LogStream& operator<<(const unsigned char* str) { return operator<<(reinterpret_cast(str)); } LogStream& operator<<(const std::string& v) { buffer_.append(v.c_str(), v.size()); return *this; } void append(const char* data, int len) { buffer_.append(data, len); } const Buffer& buffer() const { return buffer_; } void resetBuffer() { buffer_.reset(); } private: void staticCheck(); template void formatInteger(T); Buffer buffer_; static const int kMaxNumericSize = 32; }; ================================================ FILE: WebServer/base/Logging.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Logging.h" #include "CurrentThread.h" #include "Thread.h" #include "AsyncLogging.h" #include #include #include #include static pthread_once_t once_control_ = PTHREAD_ONCE_INIT; static AsyncLogging *AsyncLogger_; std::string Logger::logFileName_ = "./WebServer.log"; void once_init() { AsyncLogger_ = new AsyncLogging(Logger::getLogFileName()); AsyncLogger_->start(); } void output(const char* msg, int len) { pthread_once(&once_control_, once_init); AsyncLogger_->append(msg, len); } Logger::Impl::Impl(const char *fileName, int line) : stream_(), line_(line), basename_(fileName) { formatTime(); } void Logger::Impl::formatTime() { struct timeval tv; time_t time; char str_t[26] = {0}; gettimeofday (&tv, NULL); time = tv.tv_sec; struct tm* p_time = localtime(&time); strftime(str_t, 26, "%Y-%m-%d %H:%M:%S\n", p_time); stream_ << str_t; } Logger::Logger(const char *fileName, int line) : impl_(fileName, line) { } Logger::~Logger() { impl_.stream_ << " -- " << impl_.basename_ << ':' << impl_.line_ << '\n'; const LogStream::Buffer& buf(stream().buffer()); output(buf.data(), buf.length()); } ================================================ FILE: WebServer/base/Logging.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include "LogStream.h" class AsyncLogging; class Logger { public: Logger(const char *fileName, int line); ~Logger(); LogStream &stream() { return impl_.stream_; } static void setLogFileName(std::string fileName) { logFileName_ = fileName; } static std::string getLogFileName() { return logFileName_; } private: class Impl { public: Impl(const char *fileName, int line); void formatTime(); LogStream stream_; int line_; std::string basename_; }; Impl impl_; static std::string logFileName_; }; #define LOG Logger(__FILE__, __LINE__).stream() ================================================ FILE: WebServer/base/Log的设计.txt ================================================ Log的设计仿照了muduo库的设计,但我写的没那么复杂 https://github.com/chenshuo/muduo 与Log相关的类包括FileUtil、LogFile、AsyncLogging、LogStream、Logging。 其中前4个类每一个类都含有一个append函数,Log的设计也是主要围绕这个append函数展开的。 FileUtil是最底层的文件类,封装了Log文件的打开、写入并在类析构的时候关闭文件,底层使用了标准IO,该append函数直接向文件写。 LogFile进一步封装了FileUtil,并设置了一个循环次数,没过这么多次就flush一次。 AsyncLogging是核心,它负责启动一个log线程,专门用来将log写入LogFile,应用了“双缓冲技术”,其实有4个以上的缓冲区,但思想是一样的。 AsyncLogging负责(定时到或被填满时)将缓冲区中的数据写入LogFile中。 LogStream主要用来格式化输出,重载了<<运算符,同时也有自己的一块缓冲区,这里缓冲区的存在是为了缓存一行,把多个<<的结果连成一块。 Logging是对外接口,Logging类内涵一个LogStream对象,主要是为了每次打log的时候在log之前和之后加上固定的格式化的信息,比如打log的行、 文件名等信息。 ================================================ FILE: WebServer/base/MutexLock.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include "noncopyable.h" class MutexLock : noncopyable { public: MutexLock() { pthread_mutex_init(&mutex, NULL); } ~MutexLock() { pthread_mutex_lock(&mutex); pthread_mutex_destroy(&mutex); } void lock() { pthread_mutex_lock(&mutex); } void unlock() { pthread_mutex_unlock(&mutex); } pthread_mutex_t *get() { return &mutex; } private: pthread_mutex_t mutex; // 友元类不受访问权限影响 private: friend class Condition; }; class MutexLockGuard : noncopyable { public: explicit MutexLockGuard(MutexLock &_mutex) : mutex(_mutex) { mutex.lock(); } ~MutexLockGuard() { mutex.unlock(); } private: MutexLock &mutex; }; ================================================ FILE: WebServer/base/Thread.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "Thread.h" #include #include #include #include #include #include #include #include #include #include "CurrentThread.h" #include using namespace std; namespace CurrentThread { __thread int t_cachedTid = 0; __thread char t_tidString[32]; __thread int t_tidStringLength = 6; __thread const char* t_threadName = "default"; } pid_t gettid() { return static_cast(::syscall(SYS_gettid)); } void CurrentThread::cacheTid() { if (t_cachedTid == 0) { t_cachedTid = gettid(); t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid); } } // 为了在线程中保留name,tid这些数据 struct ThreadData { typedef Thread::ThreadFunc ThreadFunc; ThreadFunc func_; string name_; pid_t* tid_; CountDownLatch* latch_; ThreadData(const ThreadFunc& func, const string& name, pid_t* tid, CountDownLatch* latch) : func_(func), name_(name), tid_(tid), latch_(latch) {} void runInThread() { *tid_ = CurrentThread::tid(); tid_ = NULL; latch_->countDown(); latch_ = NULL; CurrentThread::t_threadName = name_.empty() ? "Thread" : name_.c_str(); prctl(PR_SET_NAME, CurrentThread::t_threadName); func_(); CurrentThread::t_threadName = "finished"; } }; void* startThread(void* obj) { ThreadData* data = static_cast(obj); data->runInThread(); delete data; return NULL; } Thread::Thread(const ThreadFunc& func, const string& n) : started_(false), joined_(false), pthreadId_(0), tid_(0), func_(func), name_(n), latch_(1) { setDefaultName(); } Thread::~Thread() { if (started_ && !joined_) pthread_detach(pthreadId_); } void Thread::setDefaultName() { if (name_.empty()) { char buf[32]; snprintf(buf, sizeof buf, "Thread"); name_ = buf; } } void Thread::start() { assert(!started_); started_ = true; ThreadData* data = new ThreadData(func_, name_, &tid_, &latch_); if (pthread_create(&pthreadId_, NULL, &startThread, data)) { started_ = false; delete data; } else { latch_.wait(); assert(tid_ > 0); } } int Thread::join() { assert(started_); assert(!joined_); joined_ = true; return pthread_join(pthreadId_, NULL); } ================================================ FILE: WebServer/base/Thread.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once #include #include #include #include #include #include #include "CountDownLatch.h" #include "noncopyable.h" class Thread : noncopyable { public: typedef std::function ThreadFunc; explicit Thread(const ThreadFunc&, const std::string& name = std::string()); ~Thread(); void start(); int join(); bool started() const { return started_; } pid_t tid() const { return tid_; } const std::string& name() const { return name_; } private: void setDefaultName(); bool started_; bool joined_; pthread_t pthreadId_; pid_t tid_; ThreadFunc func_; std::string name_; CountDownLatch latch_; }; ================================================ FILE: WebServer/base/noncopyable.h ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #pragma once class noncopyable { protected: noncopyable() {} ~noncopyable() {} private: noncopyable(const noncopyable&); const noncopyable& operator=(const noncopyable&); }; ================================================ FILE: WebServer/base/tests/CMakeLists.txt ================================================ add_executable(LoggingTest LoggingTest.cpp) target_link_libraries(LoggingTest libserver_base) ================================================ FILE: WebServer/base/tests/LoggingTest.cpp ================================================ // @Author Lin Ya // @Email xxbbb@vip.qq.com #include "../Logging.h" #include "../Thread.h" #include #include #include #include #include using namespace std; void threadFunc() { for (int i = 0; i < 100000; ++i) { LOG << i; } } void type_test() { // 13 lines cout << "----------type test-----------" << endl; LOG << 0; LOG << 1234567890123; LOG << 1.0f; LOG << 3.1415926; LOG << (short) 1; LOG << (long long) 1; LOG << (unsigned int) 1; LOG << (unsigned long) 1; LOG << (long double) 1.6555556; LOG << (unsigned long long) 1; LOG << 'c'; LOG << "abcdefg"; LOG << string("This is a string"); } void stressing_single_thread() { // 100000 lines cout << "----------stressing test single thread-----------" << endl; for (int i = 0; i < 100000; ++i) { LOG << i; } } void stressing_multi_threads(int threadNum = 4) { // threadNum * 100000 lines cout << "----------stressing test multi thread-----------" << endl; vector> vsp; for (int i = 0; i < threadNum; ++i) { shared_ptr tmp(new Thread(threadFunc, "testFunc")); vsp.push_back(tmp); } for (int i = 0; i < threadNum; ++i) { vsp[i]->start(); } sleep(3); } void other() { // 1 line cout << "----------other test-----------" << endl; LOG << "fddsa" << 'c' << 0 << 3.666 << string("This is a string"); } int main() { // 共500014行 type_test(); sleep(3); stressing_single_thread(); sleep(3); other(); sleep(3); stressing_multi_threads(); sleep(3); return 0; } ================================================ FILE: WebServer/config.h ================================================ #pragma once ================================================ FILE: WebServer/tests/CMakeLists.txt ================================================ add_executable(HTTPClient HTTPClient.cpp) ================================================ FILE: WebServer/tests/HTTPClient.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #define MAXSIZE 1024 #define IPADDRESS "127.0.0.1" #define SERV_PORT 8888 #define FDSIZE 1024 #define EPOLLEVENTS 20 // static void handle_connection(int sockfd); // static void handle_events(int epollfd,struct epoll_event *events,int num,int // sockfd,char *buf); // static void do_read(int epollfd,int fd,int sockfd,char *buf); // static void do_read(int epollfd,int fd,int sockfd,char *buf); // static void do_write(int epollfd,int fd,int sockfd,char *buf); // static void add_event(int epollfd,int fd,int state); // static void delete_event(int epollfd,int fd,int state); // static void modify_event(int epollfd,int fd,int state); int setSocketNonBlocking1(int fd) { int flag = fcntl(fd, F_GETFL, 0); if (flag == -1) return -1; flag |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in servaddr; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, IPADDRESS, &servaddr.sin_addr); char buff[4096]; buff[0] = '\0'; // 发空串 const char *p = " "; if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) { setSocketNonBlocking1(sockfd); cout << "1:" << endl; ssize_t n = write(sockfd, p, strlen(p)); cout << "strlen(p) = " << strlen(p) << endl; sleep(1); n = read(sockfd, buff, 4096); cout << "n=" << n << endl; printf("%s", buff); close(sockfd); } else { perror("err1"); } sleep(1); // 发"GET HTTP/1.1" p = "GET HTTP/1.1"; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) { setSocketNonBlocking1(sockfd); cout << "2:" << endl; ssize_t n = write(sockfd, p, strlen(p)); cout << "strlen(p) = " << strlen(p) << endl; sleep(1); n = read(sockfd, buff, 4096); cout << "n=" << n << endl; printf("%s", buff); close(sockfd); } else { perror("err2"); } sleep(1); // 发 // GET HTTP/1.1 // Host: 192.168.52.135:8888 // Content-Type: application/x-www-form-urlencoded // Connection: Keep-Alive p = "GET / HTTP/1.1\r\nHost: 192.168.52.135:8888\r\nContent-Type: " "application/x-www-form-urlencoded\r\nConnection: Keep-Alive\r\n\r\n"; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) { setSocketNonBlocking1(sockfd); cout << "3:" << endl; ssize_t n = write(sockfd, p, strlen(p)); cout << "strlen(p) = " << strlen(p) << endl; sleep(1); n = read(sockfd, buff, 4096); cout << "n=" << n << endl; printf("%s", buff); close(sockfd); } else { perror("err3"); } return 0; } // static void handle_connection(int sockfd) // { // int epollfd; // struct epoll_event events[EPOLLEVENTS]; // char buf[MAXSIZE]; // int ret; // epollfd = epoll_create(FDSIZE); // add_event(epollfd,STDIN_FILENO,EPOLLIN); // for ( ; ; ) // { // ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1); // handle_events(epollfd,events,ret,sockfd,buf); // } // close(epollfd); // } // static void // handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char // *buf) // { // int fd; // int i; // for (i = 0;i < num;i++) // { // fd = events[i].data.fd; // if (events[i].events & EPOLLIN) // do_read(epollfd,fd,sockfd,buf); // else if (events[i].events & EPOLLOUT) // do_write(epollfd,fd,sockfd,buf); // } // } // static void do_read(int epollfd,int fd,int sockfd,char *buf) // { // int nread; // nread = read(fd,buf,MAXSIZE); // if (nread == -1) // { // perror("read error:"); // close(fd); // } // else if (nread == 0) // { // fprintf(stderr,"server close.\n"); // close(fd); // } // else // { // if (fd == STDIN_FILENO) // add_event(epollfd,sockfd,EPOLLOUT); // else // { // delete_event(epollfd,sockfd,EPOLLIN); // add_event(epollfd,STDOUT_FILENO,EPOLLOUT); // } // } // } // static void do_write(int epollfd,int fd,int sockfd,char *buf) // { // int nwrite; // nwrite = write(fd,buf,strlen(buf)); // if (nwrite == -1) // { // perror("write error:"); // close(fd); // } // else // { // if (fd == STDOUT_FILENO) // delete_event(epollfd,fd,EPOLLOUT); // else // modify_event(epollfd,fd,EPOLLIN); // } // memset(buf,0,MAXSIZE); // } // static void add_event(int epollfd,int fd,int state) // { // struct epoll_event ev; // ev.events = state; // ev.data.fd = fd; // epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev); // } // static void delete_event(int epollfd,int fd,int state) // { // struct epoll_event ev; // ev.events = state; // ev.data.fd = fd; // epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev); // } // static void modify_event(int epollfd,int fd,int state) // { // struct epoll_event ev; // ev.events = state; // ev.data.fd = fd; // epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev); // } ================================================ FILE: build.sh ================================================ #!/bin/sh set -x SOURCE_DIR=`pwd` BUILD_DIR=${BUILD_DIR:-../build} BUILD_TYPE=${BUILD_TYPE:-Debug} mkdir -p $BUILD_DIR/$BUILD_TYPE \ && cd $BUILD_DIR/$BUILD_TYPE \ && cmake \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ $SOURCE_DIR \ && make $* ================================================ FILE: old_version/old_version_0.1/Makefile ================================================ SOURCE := $(wildcard *.cpp) OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE))) TARGET := myserver CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui INCLUDE:= -I./usr/local/include/opencv CFLAGS := -std=c++11 -g -Wall -O3 $(INCLUDE) CXXFLAGS:= $(CFLAGS) .PHONY : objs clean veryclean rebuild all all : $(TARGET) objs : $(OBJS) rebuild: veryclean all clean : rm -fr *.o veryclean : clean rm -rf $(TARGET) $(TARGET) : $(OBJS) $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) ================================================ FILE: old_version/old_version_0.1/Makefile1 ================================================ cc=g++ -std=c++11 main: main.o requestData.o epoll.o threadpool.o $(cc) -o main main.o requestData.o epoll.o threadpool.o -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml main.o: main.cpp $(cc) -I/usr/local/include/opencv -c main.cpp requestData.o: requestData.h requestData.cpp $(cc) -I/usr/local/include/opencv -c requestData.cpp epoll.o: epoll.h epoll.cpp $(cc) -I/usr/local/include/opencv -c epoll.cpp threadpool.o: threadpool.h threadpool.cpp $(cc) -I/usr/local/include/opencv -c threadpool.cpp .PHONY: clean clean: -rm main *.o .PHONY : everything objs clean veryclean rebuild everything : $(TARGET) all : $(TARGET) objs : $(OBJS) rebuild: veryclean everything clean : rm -fr *.o veryclean : clean rm -fr $(TARGET) ================================================ FILE: old_version/old_version_0.1/epoll.cpp ================================================ #include "epoll.h" #include #include #include "threadpool.h" struct epoll_event* events; int epoll_init() { int epoll_fd = epoll_create(LISTENQ + 1); if(epoll_fd == -1) return -1; //events = (struct epoll_event*)malloc(sizeof(struct epoll_event) * MAXEVENTS); events = new epoll_event[MAXEVENTS]; return epoll_fd; } // 注册新描述符 int epoll_add(int epoll_fd, int fd, void *request, __uint32_t events) { struct epoll_event event; event.data.ptr = request; event.events = events; //printf("add to epoll %d\n", fd); if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); return -1; } return 0; } // 修改描述符状态 int epoll_mod(int epoll_fd, int fd, void *request, __uint32_t events) { struct epoll_event event; event.data.ptr = request; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); return -1; } return 0; } // 从epoll中删除描述符 int epoll_del(int epoll_fd, int fd, void *request, __uint32_t events) { struct epoll_event event; event.data.ptr = request; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); return -1; } return 0; } // 返回活跃事件数 int my_epoll_wait(int epoll_fd, struct epoll_event* events, int max_events, int timeout) { int ret_count = epoll_wait(epoll_fd, events, max_events, timeout); if (ret_count < 0) { perror("epoll wait error"); } return ret_count; } ================================================ FILE: old_version/old_version_0.1/epoll.h ================================================ #ifndef EVENTPOLL #define EVENTPOLL #include "requestData.h" const int MAXEVENTS = 5000; const int LISTENQ = 1024; int epoll_init(); int epoll_add(int epoll_fd, int fd, void *request, __uint32_t events); int epoll_mod(int epoll_fd, int fd, void *request, __uint32_t events); int epoll_del(int epoll_fd, int fd, void *request, __uint32_t events); int my_epoll_wait(int epoll_fd, struct epoll_event *events, int max_events, int timeout); #endif ================================================ FILE: old_version/old_version_0.1/improvement.txt ================================================ 1. 指针可以改成shared_ptr,不需要delete。 2. 想法在某些地方写成单例模式。 3. readn 和 writen 改成写到不能写后或读到不能读后加入epoll等待,要记录写到哪或读到哪,维护这样一个状态。比较两者的性能差异。 4. 信号处理部分可以将 epoll_wait 替换为更安全的 epoll_pwait。 踩坑: 1. 对EPOLLONESHOT的误解,原以为当epoll_wait监听到相应的事件触发后,epoll会把与事件关联的fd从epoll描述符中禁止掉并且彻底删除,实际上并不会删除,man手册上的解释如下: EPOLLONESHOT (since Linux 2.6.2) Sets the one-shot behavior for the associated file descriptor. This means that after an event is pulled out with epoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epoll interface. The user must call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask. 另外: Linux Programmer's Manual 中第六个问题: Q6 Will closing a file descriptor cause it to be removed from all epoll sets automatically? A6 Yes, but be aware of the following point. A file descriptor is a reference to an open file description (see open(2)). Whenever a file descriptor is duplicated via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created. An open file description con‐ tinues to exist until all file descriptors referring to it have been closed. A file descriptor is removed from an epoll set only after all the file descriptors referring to the underlying open file description have been closed (or before if the file descrip‐ tor is explicitly removed using epoll_ctl(2) EPOLL_CTL_DEL). This means that even after a file descriptor that is part of an epoll set has been closed, events may be reported for that file descriptor if other file descriptors referring to the same under‐ lying file description remain open. 当调用close()关闭对应的fd时,会使相应的引用计数减一,只有减到0时,epoll才会真的删掉它,所以,比较安全的做法是: 先del掉它,再close它(如果不确定close是否真的关闭了这个文件。)。 ubuntu 配置opencv: vim /etc/ld.so.conf 加一行include /usr/local/opencv/* /sbin/ldconfig –v ================================================ FILE: old_version/old_version_0.1/index.html ================================================ Hello World ! ================================================ FILE: old_version/old_version_0.1/main.cpp ================================================ #include "requestData.h" #include "epoll.h" #include "threadpool.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; const int THREADPOOL_THREAD_NUM = 4; const int QUEUE_SIZE = 65535; const int PORT = 8888; const int ASK_STATIC_FILE = 1; const int ASK_IMAGE_STITCH = 2; const string PATH = "/"; const int TIMER_TIME_OUT = 500; extern pthread_mutex_t qlock; extern struct epoll_event* events; void acceptConnection(int listen_fd, int epoll_fd, const string &path); extern priority_queue, timerCmp> myTimerQueue; int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 1024 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) return -1; // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char*)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) return -1; // 开始监听,最大等待队列长为LISTENQ if(listen(listen_fd, LISTENQ) == -1) return -1; // 无效监听描述符 if(listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } void myHandler(void *args) { requestData *req_data = (requestData*)args; req_data->handleRequest(); } void acceptConnection(int listen_fd, int epoll_fd, const string &path) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_addr_len = 0; int accept_fd = 0; while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0) { /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 设为非阻塞模式 int ret = setSocketNonBlocking(accept_fd); if (ret < 0) { perror("Set non block failed!"); return; } requestData *req_info = new requestData(epoll_fd, accept_fd, path); // 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理 __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; epoll_add(epoll_fd, accept_fd, static_cast(req_info), _epo_event); // 新增时间信息 mytimer *mtimer = new mytimer(req_info, TIMER_TIME_OUT); req_info->addTimer(mtimer); pthread_mutex_lock(&qlock); myTimerQueue.push(mtimer); pthread_mutex_unlock(&qlock); } //if(accept_fd == -1) // perror("accept"); } // 分发处理函数 void handle_events(int epoll_fd, int listen_fd, struct epoll_event* events, int events_num, const string &path, threadpool_t* tp) { for(int i = 0; i < events_num; i++) { // 获取有事件产生的描述符 requestData* request = (requestData*)(events[i].data.ptr); int fd = request->getFd(); // 有事件发生的描述符为监听描述符 if(fd == listen_fd) { //cout << "This is listen_fd" << endl; acceptConnection(listen_fd, epoll_fd, path); } else { // 排除错误事件 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { printf("error event\n"); delete request; continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 request->seperateTimer(); int rc = threadpool_add(tp, myHandler, events[i].data.ptr, 0); } } } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请requestData节点了,这样可以继续重复利用前面的requestData,减少了一次delete和一次new的时间。 */ void handle_expired_event() { pthread_mutex_lock(&qlock); while (!myTimerQueue.empty()) { mytimer *ptimer_now = myTimerQueue.top(); if (ptimer_now->isDeleted()) { myTimerQueue.pop(); delete ptimer_now; } else if (ptimer_now->isvalid() == false) { myTimerQueue.pop(); delete ptimer_now; } else { break; } } pthread_mutex_unlock(&qlock); } int main() { handle_for_sigpipe(); int epoll_fd = epoll_init(); if (epoll_fd < 0) { perror("epoll init failed"); return 1; } threadpool_t *threadpool = threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE, 0); int listen_fd = socket_bind_listen(PORT); if (listen_fd < 0) { perror("socket bind failed"); return 1; } if (setSocketNonBlocking(listen_fd) < 0) { perror("set socket non block failed"); return 1; } __uint32_t event = EPOLLIN | EPOLLET; requestData *req = new requestData(); req->setFd(listen_fd); epoll_add(epoll_fd, listen_fd, static_cast(req), event); while (true) { int events_num = my_epoll_wait(epoll_fd, events, MAXEVENTS, -1); //printf("%zu\n", myTimerQueue.size()); if (events_num == 0) continue; printf("%d\n", events_num); //printf("%zu\n", myTimerQueue.size()); // else // cout << "one connection has come!" << endl; // 遍历events数组,根据监听种类及描述符类型分发操作 handle_events(epoll_fd, listen_fd, events, events_num, PATH, threadpool); handle_expired_event(); } return 0; } ================================================ FILE: old_version/old_version_0.1/requestData.cpp ================================================ #include "requestData.h" #include "util.h" #include "epoll.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace cv; //test #include using namespace std; pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t MimeType::lock = PTHREAD_MUTEX_INITIALIZER; std::unordered_map MimeType::mime; std::string MimeType::getMime(const std::string &suffix) { if (mime.size() == 0) { pthread_mutex_lock(&lock); if (mime.size() == 0) { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "application/x-ico"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } pthread_mutex_unlock(&lock); } if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } priority_queue, timerCmp> myTimerQueue; requestData::requestData(): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0), timer(NULL) { cout << "requestData constructed !" << endl; } requestData::requestData(int _epollfd, int _fd, std::string _path): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0), timer(NULL), path(_path), fd(_fd), epollfd(_epollfd) {} requestData::~requestData() { cout << "~requestData()" << endl; struct epoll_event ev; // 超时的一定都是读请求,没有"被动"写。 ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; ev.data.ptr = (void*)this; epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); if (timer != NULL) { timer->clearReq(); timer = NULL; } close(fd); } void requestData::addTimer(mytimer *mtimer) { if (timer == NULL) timer = mtimer; } int requestData::getFd() { return fd; } void requestData::setFd(int _fd) { fd = _fd; } void requestData::reset() { againTimes = 0; content.clear(); file_name.clear(); path.clear(); now_read_pos = 0; state = STATE_PARSE_URI; h_state = h_start; headers.clear(); keep_alive = false; } void requestData::seperateTimer() { if (timer) { timer->clearReq(); timer = NULL; } } void requestData::handleRequest() { char buff[MAX_BUFF]; bool isError = false; while (true) { int read_num = readn(fd, buff, MAX_BUFF); if (read_num < 0) { perror("1"); isError = true; break; } else if (read_num == 0) { // 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因 perror("read_num == 0"); if (errno == EAGAIN) { if (againTimes > AGAIN_MAX_TIMES) isError = true; else ++againTimes; } else if (errno != 0) isError = true; break; } string now_read(buff, buff + read_num); content += now_read; if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) { break; } else if (flag == PARSE_URI_ERROR) { perror("2"); isError = true; break; } } if (state == STATE_PARSE_HEADERS) { int flag = this->parse_Headers(); if (flag == PARSE_HEADER_AGAIN) { break; } else if (flag == PARSE_HEADER_ERROR) { perror("3"); isError = true; break; } if(method == METHOD_POST) { state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) { int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { isError = true; break; } if (content.size() < content_length) continue; state = STATE_ANALYSIS; } if (state == STATE_ANALYSIS) { int flag = this->analysisRequest(); if (flag < 0) { isError = true; break; } else if (flag == ANALYSIS_SUCCESS) { state = STATE_FINISH; break; } else { isError = true; break; } } } if (isError) { delete this; return; } // 加入epoll继续 if (state == STATE_FINISH) { if (keep_alive) { printf("ok\n"); this->reset(); } else { delete this; return; } } // 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。 // 新增时间信息 pthread_mutex_lock(&qlock); mytimer *mtimer = new mytimer(this, 500); timer = mtimer; myTimerQueue.push(mtimer); pthread_mutex_unlock(&qlock); __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; int ret = epoll_mod(epollfd, fd, static_cast(this), _epo_event); if (ret < 0) { // 返回错误处理 delete this; return; } } int requestData::parse_URI() { string &str = content; // 读到完整的请求行再开始解析请求 int pos = str.find('\r', now_read_pos); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method pos = request_line.find("GET"); if (pos < 0) { pos = request_line.find("POST"); if (pos < 0) { return PARSE_URI_ERROR; } else { method = METHOD_POST; } } else { method = METHOD_GET; } //printf("method = %d\n", method); // filename pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { int _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { file_name = request_line.substr(pos + 1, _pos - pos - 1); int __pos = file_name.find('?'); if (__pos >= 0) { file_name = file_name.substr(0, __pos); } } else file_name = "index.html"; } pos = _pos; } //cout << "file_name: " << file_name << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { if (request_line.size() - pos <= 3) { return PARSE_URI_ERROR; } else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPversion = HTTP_10; else if (ver == "1.1") HTTPversion = HTTP_11; else return PARSE_URI_ERROR; } } state = STATE_PARSE_HEADERS; return PARSE_URI_SUCCESS; } int requestData::parse_Headers() { string &str = content; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case h_colon: { if (str[i] == ' ') { h_state = h_spaces_after_colon; } else return PARSE_HEADER_ERROR; break; } case h_spaces_after_colon: { h_state = h_value; value_start = i; break; } case h_value: { if (str[i] == '\r') { h_state = h_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case h_CR: { if (str[i] == '\n') { h_state = h_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case h_LF: { if (str[i] == '\r') { h_state = h_end_CR; } else { key_start = i; h_state = h_key; } break; } case h_end_CR: { if (str[i] == '\n') { h_state = h_end_LF; } else return PARSE_HEADER_ERROR; break; } case h_end_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (h_state == h_end_LF) { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } int requestData::analysisRequest() { if (method == METHOD_POST) { //get content char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } //cout << "content=" << content << endl; // test char* char *send_content = "I have receiced this."; sprintf(header, "%sContent-length: %zu\r\n", header, strlen(send_content)); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } send_len = (size_t)writen(fd, send_content, strlen(send_content)); if(send_len != strlen(send_content)) { perror("Send content failed"); return ANALYSIS_ERROR; } cout << "content size ==" << content.size() << endl; vector data(content.begin(), content.end()); Mat test = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); imwrite("receive.bmp", test); return ANALYSIS_SUCCESS; } else if (method == METHOD_GET) { char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } int dot_pos = file_name.find('.'); const char* filetype; if (dot_pos < 0) filetype = MimeType::getMime("default").c_str(); else filetype = MimeType::getMime(file_name.substr(dot_pos)).c_str(); struct stat sbuf; if (stat(file_name.c_str(), &sbuf) < 0) { handleError(fd, 404, "Not Found!"); return ANALYSIS_ERROR; } sprintf(header, "%sContent-type: %s\r\n", header, filetype); // 通过Content-length返回文件大小 sprintf(header, "%sContent-length: %ld\r\n", header, sbuf.st_size); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } int src_fd = open(file_name.c_str(), O_RDONLY, 0); char *src_addr = static_cast(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0)); close(src_fd); // 发送文件并校验完整性 send_len = writen(fd, src_addr, sbuf.st_size); if(send_len != sbuf.st_size) { perror("Send file failed"); return ANALYSIS_ERROR; } munmap(src_addr, sbuf.st_size); return ANALYSIS_SUCCESS; } else return ANALYSIS_ERROR; } void requestData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[MAX_BUFF]; string body_buff, header_buff; body_buff += "TKeed Error"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-type: text/html\r\n"; header_buff += "Connection: close\r\n"; header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "\r\n"; sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } mytimer::mytimer(requestData *_request_data, int timeout): deleted(false), request_data(_request_data) { //cout << "mytimer()" << endl; struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } mytimer::~mytimer() { cout << "~mytimer()" << endl; if (request_data != NULL) { cout << "request_data=" << request_data << endl; delete request_data; request_data = NULL; } } void mytimer::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } bool mytimer::isvalid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = ((now.tv_sec * 1000) + (now.tv_usec / 1000)); if (temp < expired_time) { return true; } else { this->setDeleted(); return false; } } void mytimer::clearReq() { request_data = NULL; this->setDeleted(); } void mytimer::setDeleted() { deleted = true; } bool mytimer::isDeleted() const { return deleted; } size_t mytimer::getExpTime() const { return expired_time; } bool timerCmp::operator()(const mytimer *a, const mytimer *b) const { return a->getExpTime() > b->getExpTime(); } ================================================ FILE: old_version/old_version_0.1/requestData.h ================================================ #ifndef REQUESTDATA #define REQUESTDATA #include #include const int STATE_PARSE_URI = 1; const int STATE_PARSE_HEADERS = 2; const int STATE_RECV_BODY = 3; const int STATE_ANALYSIS = 4; const int STATE_FINISH = 5; const int MAX_BUFF = 4096; // 有请求出现但是读不到数据,可能是Request Aborted, // 或者来自网络的数据没有达到等原因, // 对这样的请求尝试超过一定的次数就抛弃 const int AGAIN_MAX_TIMES = 200; const int PARSE_URI_AGAIN = -1; const int PARSE_URI_ERROR = -2; const int PARSE_URI_SUCCESS = 0; const int PARSE_HEADER_AGAIN = -1; const int PARSE_HEADER_ERROR = -2; const int PARSE_HEADER_SUCCESS = 0; const int ANALYSIS_ERROR = -2; const int ANALYSIS_SUCCESS = 0; const int METHOD_POST = 1; const int METHOD_GET = 2; const int HTTP_10 = 1; const int HTTP_11 = 2; const int EPOLL_WAIT_TIME = 500; class MimeType { private: static pthread_mutex_t lock; static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); }; enum HeadersState { h_start = 0, h_key, h_colon, h_spaces_after_colon, h_value, h_CR, h_LF, h_end_CR, h_end_LF }; struct mytimer; struct requestData; struct requestData { private: int againTimes; std::string path; int fd; int epollfd; // content的内容用完就清 std::string content; int method; int HTTPversion; std::string file_name; int now_read_pos; int state; int h_state; bool isfinish; bool keep_alive; std::unordered_map headers; mytimer *timer; private: int parse_URI(); int parse_Headers(); int analysisRequest(); public: requestData(); requestData(int _epollfd, int _fd, std::string _path); ~requestData(); void addTimer(mytimer *mtimer); void reset(); void seperateTimer(); int getFd(); void setFd(int _fd); void handleRequest(); void handleError(int fd, int err_num, std::string short_msg); }; struct mytimer { bool deleted; size_t expired_time; requestData *request_data; mytimer(requestData *_request_data, int timeout); ~mytimer(); void update(int timeout); bool isvalid(); void clearReq(); void setDeleted(); bool isDeleted() const; size_t getExpTime() const; }; struct timerCmp { bool operator()(const mytimer *a, const mytimer *b) const; }; #endif ================================================ FILE: old_version/old_version_0.1/threadpool.cpp ================================================ #include "threadpool.h" threadpool_t *threadpool_create(int thread_count, int queue_size, int flags) { threadpool_t *pool; int i; //(void) flags; do { if(thread_count <= 0 || thread_count > MAX_THREADS || queue_size <= 0 || queue_size > MAX_QUEUE) { return NULL; } if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) { break; } /* Initialize */ pool->thread_count = 0; pool->queue_size = queue_size; pool->head = pool->tail = pool->count = 0; pool->shutdown = pool->started = 0; /* Allocate thread and task queue */ pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count); pool->queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_size); /* Initialize mutex and conditional variable first */ if((pthread_mutex_init(&(pool->lock), NULL) != 0) || (pthread_cond_init(&(pool->notify), NULL) != 0) || (pool->threads == NULL) || (pool->queue == NULL)) { break; } /* Start worker threads */ for(i = 0; i < thread_count; i++) { if(pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool) != 0) { threadpool_destroy(pool, 0); return NULL; } pool->thread_count++; pool->started++; } return pool; } while(false); if (pool != NULL) { threadpool_free(pool); } return NULL; } int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags) { //printf("add to thread pool !\n"); int err = 0; int next; //(void) flags; if(pool == NULL || function == NULL) { return THREADPOOL_INVALID; } if(pthread_mutex_lock(&(pool->lock)) != 0) { return THREADPOOL_LOCK_FAILURE; } next = (pool->tail + 1) % pool->queue_size; do { /* Are we full ? */ if(pool->count == pool->queue_size) { err = THREADPOOL_QUEUE_FULL; break; } /* Are we shutting down ? */ if(pool->shutdown) { err = THREADPOOL_SHUTDOWN; break; } /* Add task to queue */ pool->queue[pool->tail].function = function; pool->queue[pool->tail].argument = argument; pool->tail = next; pool->count += 1; /* pthread_cond_broadcast */ if(pthread_cond_signal(&(pool->notify)) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&pool->lock) != 0) { err = THREADPOOL_LOCK_FAILURE; } return err; } int threadpool_destroy(threadpool_t *pool, int flags) { printf("Thread pool destroy !\n"); int i, err = 0; if(pool == NULL) { return THREADPOOL_INVALID; } if(pthread_mutex_lock(&(pool->lock)) != 0) { return THREADPOOL_LOCK_FAILURE; } do { /* Already shutting down */ if(pool->shutdown) { err = THREADPOOL_SHUTDOWN; break; } pool->shutdown = (flags & THREADPOOL_GRACEFUL) ? graceful_shutdown : immediate_shutdown; /* Wake up all worker threads */ if((pthread_cond_broadcast(&(pool->notify)) != 0) || (pthread_mutex_unlock(&(pool->lock)) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } /* Join all worker thread */ for(i = 0; i < pool->thread_count; ++i) { if(pthread_join(pool->threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); /* Only if everything went well do we deallocate the pool */ if(!err) { threadpool_free(pool); } return err; } int threadpool_free(threadpool_t *pool) { if(pool == NULL || pool->started > 0) { return -1; } /* Did we manage to allocate ? */ if(pool->threads) { free(pool->threads); free(pool->queue); /* Because we allocate pool->threads after initializing the mutex and condition variable, we're sure they're initialized. Let's lock the mutex just in case. */ pthread_mutex_lock(&(pool->lock)); pthread_mutex_destroy(&(pool->lock)); pthread_cond_destroy(&(pool->notify)); } free(pool); return 0; } static void *threadpool_thread(void *threadpool) { threadpool_t *pool = (threadpool_t *)threadpool; threadpool_task_t task; for(;;) { /* Lock must be taken to wait on conditional variable */ pthread_mutex_lock(&(pool->lock)); /* Wait on condition variable, check for spurious wakeups. When returning from pthread_cond_wait(), we own the lock. */ while((pool->count == 0) && (!pool->shutdown)) { pthread_cond_wait(&(pool->notify), &(pool->lock)); } if((pool->shutdown == immediate_shutdown) || ((pool->shutdown == graceful_shutdown) && (pool->count == 0))) { break; } /* Grab our task */ task.function = pool->queue[pool->head].function; task.argument = pool->queue[pool->head].argument; pool->head = (pool->head + 1) % pool->queue_size; pool->count -= 1; /* Unlock */ pthread_mutex_unlock(&(pool->lock)); /* Get to work */ (*(task.function))(task.argument); } --pool->started; pthread_mutex_unlock(&(pool->lock)); pthread_exit(NULL); return(NULL); } ================================================ FILE: old_version/old_version_0.1/threadpool.h ================================================ #ifndef THREADPOOL #define THREADPOOL #include "requestData.h" #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } threadpool_shutdown_t; /** * @struct threadpool_task * @brief the work struct * * @var function Pointer to the function that will perform the task. * @var argument Argument to be passed to the function. */ typedef struct { void (*function)(void *); void *argument; } threadpool_task_t; /** * @struct threadpool * @brief The threadpool struct * * @var notify Condition variable to notify worker threads. * @var threads Array containing worker threads ID. * @var thread_count Number of threads * @var queue Array containing the task queue. * @var queue_size Size of the task queue. * @var head Index of the first element. * @var tail Index of the next element. * @var count Number of pending tasks * @var shutdown Flag indicating if the pool is shutting down * @var started Number of started threads */ struct threadpool_t { pthread_mutex_t lock; pthread_cond_t notify; pthread_t *threads; threadpool_task_t *queue; int thread_count; int queue_size; int head; int tail; int count; int shutdown; int started; }; threadpool_t *threadpool_create(int thread_count, int queue_size, int flags); int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags); int threadpool_destroy(threadpool_t *pool, int flags); int threadpool_free(threadpool_t *pool); static void *threadpool_thread(void *threadpool); #endif ================================================ FILE: old_version/old_version_0.1/util.cpp ================================================ #include "util.h" #include #include #include #include #include ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR || errno == EAGAIN) { nwritten = 0; continue; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if(sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if(flag == -1) return -1; flag |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } ================================================ FILE: old_version/old_version_0.1/util.h ================================================ #ifndef UTIL #define UTIL #include ssize_t readn(int fd, void *buff, size_t n); ssize_t writen(int fd, void *buff, size_t n); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); #endif ================================================ FILE: old_version/old_version_0.2/Makefile ================================================ SOURCE := $(wildcard *.cpp) OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE))) TARGET := myserver CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui INCLUDE:= -I./usr/local/include/opencv CFLAGS := -std=c++11 -g -Wall -O3 $(INCLUDE) CXXFLAGS:= $(CFLAGS) .PHONY : objs clean veryclean rebuild all all : $(TARGET) objs : $(OBJS) rebuild: veryclean all clean : rm -fr *.o veryclean : clean rm -rf $(TARGET) $(TARGET) : $(OBJS) $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) ================================================ FILE: old_version/old_version_0.2/Makefile1 ================================================ cc=g++ -std=c++11 main: main.o requestData.o epoll.o threadpool.o $(cc) -o main main.o requestData.o epoll.o threadpool.o -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml main.o: main.cpp $(cc) -I/usr/local/include/opencv -c main.cpp requestData.o: requestData.h requestData.cpp $(cc) -I/usr/local/include/opencv -c requestData.cpp epoll.o: epoll.h epoll.cpp $(cc) -I/usr/local/include/opencv -c epoll.cpp threadpool.o: threadpool.h threadpool.cpp $(cc) -I/usr/local/include/opencv -c threadpool.cpp .PHONY: clean clean: -rm main *.o .PHONY : everything objs clean veryclean rebuild everything : $(TARGET) all : $(TARGET) objs : $(OBJS) rebuild: veryclean everything clean : rm -fr *.o veryclean : clean rm -fr $(TARGET) ================================================ FILE: old_version/old_version_0.2/epoll.cpp ================================================ #include "epoll.h" #include #include #include "threadpool.h" struct epoll_event* events; int epoll_init() { int epoll_fd = epoll_create(LISTENQ + 1); if(epoll_fd == -1) return -1; //events = (struct epoll_event*)malloc(sizeof(struct epoll_event) * MAXEVENTS); events = new epoll_event[MAXEVENTS]; return epoll_fd; } // 注册新描述符 int epoll_add(int epoll_fd, int fd, void *request, __uint32_t events) { struct epoll_event event; event.data.ptr = request; event.events = events; //printf("add to epoll %d\n", fd); if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); return -1; } return 0; } // 修改描述符状态 int epoll_mod(int epoll_fd, int fd, void *request, __uint32_t events) { struct epoll_event event; event.data.ptr = request; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); return -1; } return 0; } // 从epoll中删除描述符 int epoll_del(int epoll_fd, int fd, void *request, __uint32_t events) { struct epoll_event event; event.data.ptr = request; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); return -1; } return 0; } // 返回活跃事件数 int my_epoll_wait(int epoll_fd, struct epoll_event* events, int max_events, int timeout) { int ret_count = epoll_wait(epoll_fd, events, max_events, timeout); if (ret_count < 0) { perror("epoll wait error"); } return ret_count; } ================================================ FILE: old_version/old_version_0.2/epoll.h ================================================ #pragma once #ifndef EVENTPOLL #define EVENTPOLL #include "requestData.h" const int MAXEVENTS = 5000; const int LISTENQ = 1024; int epoll_init(); int epoll_add(int epoll_fd, int fd, void *request, __uint32_t events); int epoll_mod(int epoll_fd, int fd, void *request, __uint32_t events); int epoll_del(int epoll_fd, int fd, void *request, __uint32_t events); int my_epoll_wait(int epoll_fd, struct epoll_event *events, int max_events, int timeout); #endif ================================================ FILE: old_version/old_version_0.2/improvement.txt ================================================ 1. 指针可以改成shared_ptr,不需要delete。 2. 想法在某些地方写成单例模式。 3. readn 和 writen 改成写到不能写后或读到不能读后加入epoll等待,要记录写到哪或读到哪,维护这样一个状态。比较两者的性能差异。 4. 信号处理部分可以将 epoll_wait 替换为更安全的 epoll_pwait。 踩坑: 1. 对EPOLLONESHOT的误解,原以为当epoll_wait监听到相应的事件触发后,epoll会把与事件关联的fd从epoll描述符中禁止掉并且彻底删除,实际上并不会删除,man手册上的解释如下: EPOLLONESHOT (since Linux 2.6.2) Sets the one-shot behavior for the associated file descriptor. This means that after an event is pulled out with epoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epoll interface. The user must call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask. 另外: Linux Programmer's Manual 中第六个问题: Q6 Will closing a file descriptor cause it to be removed from all epoll sets automatically? A6 Yes, but be aware of the following point. A file descriptor is a reference to an open file description (see open(2)). Whenever a file descriptor is duplicated via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created. An open file description con‐ tinues to exist until all file descriptors referring to it have been closed. A file descriptor is removed from an epoll set only after all the file descriptors referring to the underlying open file description have been closed (or before if the file descrip‐ tor is explicitly removed using epoll_ctl(2) EPOLL_CTL_DEL). This means that even after a file descriptor that is part of an epoll set has been closed, events may be reported for that file descriptor if other file descriptors referring to the same under‐ lying file description remain open. 当调用close()关闭对应的fd时,会使相应的引用计数减一,只有减到0时,epoll才会真的删掉它,所以,比较安全的做法是: 先del掉它,再close它(如果不确定close是否真的关闭了这个文件。)。 ubuntu 配置opencv: vim /etc/ld.so.conf 加一行include /usr/local/opencv/* /sbin/ldconfig –v ================================================ FILE: old_version/old_version_0.2/index.html ================================================ Hello World ! ================================================ FILE: old_version/old_version_0.2/main.cpp ================================================ #include "requestData.h" #include "epoll.h" #include "threadpool.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; const int THREADPOOL_THREAD_NUM = 4; const int QUEUE_SIZE = 65535; const int PORT = 8888; const int ASK_STATIC_FILE = 1; const int ASK_IMAGE_STITCH = 2; const string PATH = "/"; const int TIMER_TIME_OUT = 500; extern struct epoll_event* events; void acceptConnection(int listen_fd, int epoll_fd, const string &path); extern priority_queue, timerCmp> myTimerQueue; int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 1024 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) return -1; // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char*)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) return -1; // 开始监听,最大等待队列长为LISTENQ if(listen(listen_fd, LISTENQ) == -1) return -1; // 无效监听描述符 if(listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } void myHandler(void *args) { requestData *req_data = (requestData*)args; req_data->handleRequest(); } void acceptConnection(int listen_fd, int epoll_fd, const string &path) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_addr_len = 0; int accept_fd = 0; while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0) { /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 设为非阻塞模式 int ret = setSocketNonBlocking(accept_fd); if (ret < 0) { perror("Set non block failed!"); return; } requestData *req_info = new requestData(epoll_fd, accept_fd, path); // 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理 __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; epoll_add(epoll_fd, accept_fd, static_cast(req_info), _epo_event); // 新增时间信息 mytimer *mtimer = new mytimer(req_info, TIMER_TIME_OUT); req_info->addTimer(mtimer); MutexLockGuard(); myTimerQueue.push(mtimer); } //if(accept_fd == -1) // perror("accept"); } // 分发处理函数 void handle_events(int epoll_fd, int listen_fd, struct epoll_event* events, int events_num, const string &path, threadpool_t* tp) { for(int i = 0; i < events_num; i++) { // 获取有事件产生的描述符 requestData* request = (requestData*)(events[i].data.ptr); int fd = request->getFd(); // 有事件发生的描述符为监听描述符 if(fd == listen_fd) { //cout << "This is listen_fd" << endl; acceptConnection(listen_fd, epoll_fd, path); } else { // 排除错误事件 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { printf("error event\n"); delete request; continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 request->seperateTimer(); int rc = threadpool_add(tp, myHandler, events[i].data.ptr, 0); } } } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请requestData节点了,这样可以继续重复利用前面的requestData,减少了一次delete和一次new的时间。 */ void handle_expired_event() { MutexLockGuard(); while (!myTimerQueue.empty()) { mytimer *ptimer_now = myTimerQueue.top(); if (ptimer_now->isDeleted()) { myTimerQueue.pop(); delete ptimer_now; } else if (ptimer_now->isvalid() == false) { myTimerQueue.pop(); delete ptimer_now; } else { break; } } } int main() { handle_for_sigpipe(); int epoll_fd = epoll_init(); if (epoll_fd < 0) { perror("epoll init failed"); return 1; } threadpool_t *threadpool = threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE, 0); int listen_fd = socket_bind_listen(PORT); if (listen_fd < 0) { perror("socket bind failed"); return 1; } if (setSocketNonBlocking(listen_fd) < 0) { perror("set socket non block failed"); return 1; } __uint32_t event = EPOLLIN | EPOLLET; requestData *req = new requestData(); req->setFd(listen_fd); epoll_add(epoll_fd, listen_fd, static_cast(req), event); while (true) { int events_num = my_epoll_wait(epoll_fd, events, MAXEVENTS, -1); //printf("%zu\n", myTimerQueue.size()); if (events_num == 0) continue; printf("%d\n", events_num); //printf("%zu\n", myTimerQueue.size()); // else // cout << "one connection has come!" << endl; // 遍历events数组,根据监听种类及描述符类型分发操作 handle_events(epoll_fd, listen_fd, events, events_num, PATH, threadpool); handle_expired_event(); } return 0; } ================================================ FILE: old_version/old_version_0.2/requestData.cpp ================================================ #include "requestData.h" #include "util.h" #include "epoll.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace cv; //test #include using namespace std; pthread_mutex_t MutexLockGuard::lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t MimeType::lock = PTHREAD_MUTEX_INITIALIZER; std::unordered_map MimeType::mime; std::string MimeType::getMime(const std::string &suffix) { if (mime.size() == 0) { pthread_mutex_lock(&lock); if (mime.size() == 0) { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "application/x-ico"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } pthread_mutex_unlock(&lock); } if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } priority_queue, timerCmp> myTimerQueue; requestData::requestData(): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0), timer(NULL) { cout << "requestData constructed !" << endl; } requestData::requestData(int _epollfd, int _fd, std::string _path): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0), timer(NULL), path(_path), fd(_fd), epollfd(_epollfd) {} requestData::~requestData() { cout << "~requestData()" << endl; struct epoll_event ev; // 超时的一定都是读请求,没有"被动"写。 ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; ev.data.ptr = (void*)this; epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); if (timer != NULL) { timer->clearReq(); timer = NULL; } close(fd); } void requestData::addTimer(mytimer *mtimer) { if (timer == NULL) timer = mtimer; } int requestData::getFd() { return fd; } void requestData::setFd(int _fd) { fd = _fd; } void requestData::reset() { againTimes = 0; content.clear(); file_name.clear(); path.clear(); now_read_pos = 0; state = STATE_PARSE_URI; h_state = h_start; headers.clear(); keep_alive = false; } void requestData::seperateTimer() { if (timer) { timer->clearReq(); timer = NULL; } } void requestData::handleRequest() { char buff[MAX_BUFF]; bool isError = false; while (true) { int read_num = readn(fd, buff, MAX_BUFF); if (read_num < 0) { perror("1"); isError = true; break; } else if (read_num == 0) { // 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因 perror("read_num == 0"); if (errno == EAGAIN) { if (againTimes > AGAIN_MAX_TIMES) isError = true; else ++againTimes; } else if (errno != 0) isError = true; break; } string now_read(buff, buff + read_num); content += now_read; if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) { break; } else if (flag == PARSE_URI_ERROR) { perror("2"); isError = true; break; } } if (state == STATE_PARSE_HEADERS) { int flag = this->parse_Headers(); if (flag == PARSE_HEADER_AGAIN) { break; } else if (flag == PARSE_HEADER_ERROR) { perror("3"); isError = true; break; } if(method == METHOD_POST) { state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) { int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { isError = true; break; } if (content.size() < content_length) continue; state = STATE_ANALYSIS; } if (state == STATE_ANALYSIS) { int flag = this->analysisRequest(); if (flag < 0) { isError = true; break; } else if (flag == ANALYSIS_SUCCESS) { state = STATE_FINISH; break; } else { isError = true; break; } } } if (isError) { delete this; return; } // 加入epoll继续 if (state == STATE_FINISH) { if (keep_alive) { printf("ok\n"); this->reset(); } else { delete this; return; } } // 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。 // 新增时间信息 mytimer *mtimer = new mytimer(this, 500); timer = mtimer; { MutexLockGuard(); myTimerQueue.push(mtimer); } __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; int ret = epoll_mod(epollfd, fd, static_cast(this), _epo_event); if (ret < 0) { // 返回错误处理 delete this; return; } } int requestData::parse_URI() { string &str = content; // 读到完整的请求行再开始解析请求 int pos = str.find('\r', now_read_pos); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method pos = request_line.find("GET"); if (pos < 0) { pos = request_line.find("POST"); if (pos < 0) { return PARSE_URI_ERROR; } else { method = METHOD_POST; } } else { method = METHOD_GET; } //printf("method = %d\n", method); // filename pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { int _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { file_name = request_line.substr(pos + 1, _pos - pos - 1); int __pos = file_name.find('?'); if (__pos >= 0) { file_name = file_name.substr(0, __pos); } } else file_name = "index.html"; } pos = _pos; } //cout << "file_name: " << file_name << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { if (request_line.size() - pos <= 3) { return PARSE_URI_ERROR; } else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPversion = HTTP_10; else if (ver == "1.1") HTTPversion = HTTP_11; else return PARSE_URI_ERROR; } } state = STATE_PARSE_HEADERS; return PARSE_URI_SUCCESS; } int requestData::parse_Headers() { string &str = content; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case h_colon: { if (str[i] == ' ') { h_state = h_spaces_after_colon; } else return PARSE_HEADER_ERROR; break; } case h_spaces_after_colon: { h_state = h_value; value_start = i; break; } case h_value: { if (str[i] == '\r') { h_state = h_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case h_CR: { if (str[i] == '\n') { h_state = h_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case h_LF: { if (str[i] == '\r') { h_state = h_end_CR; } else { key_start = i; h_state = h_key; } break; } case h_end_CR: { if (str[i] == '\n') { h_state = h_end_LF; } else return PARSE_HEADER_ERROR; break; } case h_end_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (h_state == h_end_LF) { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } int requestData::analysisRequest() { if (method == METHOD_POST) { //get content char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } //cout << "content=" << content << endl; // test char* char *send_content = "I have receiced this."; sprintf(header, "%sContent-length: %zu\r\n", header, strlen(send_content)); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } send_len = (size_t)writen(fd, send_content, strlen(send_content)); if(send_len != strlen(send_content)) { perror("Send content failed"); return ANALYSIS_ERROR; } cout << "content size ==" << content.size() << endl; vector data(content.begin(), content.end()); Mat test = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); imwrite("receive.bmp", test); return ANALYSIS_SUCCESS; } else if (method == METHOD_GET) { char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } int dot_pos = file_name.find('.'); const char* filetype; if (dot_pos < 0) filetype = MimeType::getMime("default").c_str(); else filetype = MimeType::getMime(file_name.substr(dot_pos)).c_str(); struct stat sbuf; if (stat(file_name.c_str(), &sbuf) < 0) { handleError(fd, 404, "Not Found!"); return ANALYSIS_ERROR; } sprintf(header, "%sContent-type: %s\r\n", header, filetype); // 通过Content-length返回文件大小 sprintf(header, "%sContent-length: %ld\r\n", header, sbuf.st_size); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } int src_fd = open(file_name.c_str(), O_RDONLY, 0); char *src_addr = static_cast(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0)); close(src_fd); // 发送文件并校验完整性 send_len = writen(fd, src_addr, sbuf.st_size); if(send_len != sbuf.st_size) { perror("Send file failed"); return ANALYSIS_ERROR; } munmap(src_addr, sbuf.st_size); return ANALYSIS_SUCCESS; } else return ANALYSIS_ERROR; } void requestData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[MAX_BUFF]; string body_buff, header_buff; body_buff += "TKeed Error"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-type: text/html\r\n"; header_buff += "Connection: close\r\n"; header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "\r\n"; sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } mytimer::mytimer(requestData *_request_data, int timeout): deleted(false), request_data(_request_data) { //cout << "mytimer()" << endl; struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } mytimer::~mytimer() { cout << "~mytimer()" << endl; if (request_data != NULL) { cout << "request_data=" << request_data << endl; delete request_data; request_data = NULL; } } void mytimer::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } bool mytimer::isvalid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = ((now.tv_sec * 1000) + (now.tv_usec / 1000)); if (temp < expired_time) { return true; } else { this->setDeleted(); return false; } } void mytimer::clearReq() { request_data = NULL; this->setDeleted(); } void mytimer::setDeleted() { deleted = true; } bool mytimer::isDeleted() const { return deleted; } size_t mytimer::getExpTime() const { return expired_time; } bool timerCmp::operator()(const mytimer *a, const mytimer *b) const { return a->getExpTime() > b->getExpTime(); } MutexLockGuard::MutexLockGuard() { pthread_mutex_lock(&lock); } MutexLockGuard::~MutexLockGuard() { pthread_mutex_unlock(&lock); } ================================================ FILE: old_version/old_version_0.2/requestData.h ================================================ #ifndef REQUESTDATA #define REQUESTDATA #include #include const int STATE_PARSE_URI = 1; const int STATE_PARSE_HEADERS = 2; const int STATE_RECV_BODY = 3; const int STATE_ANALYSIS = 4; const int STATE_FINISH = 5; const int MAX_BUFF = 4096; // 有请求出现但是读不到数据,可能是Request Aborted, // 或者来自网络的数据没有达到等原因, // 对这样的请求尝试超过一定的次数就抛弃 const int AGAIN_MAX_TIMES = 200; const int PARSE_URI_AGAIN = -1; const int PARSE_URI_ERROR = -2; const int PARSE_URI_SUCCESS = 0; const int PARSE_HEADER_AGAIN = -1; const int PARSE_HEADER_ERROR = -2; const int PARSE_HEADER_SUCCESS = 0; const int ANALYSIS_ERROR = -2; const int ANALYSIS_SUCCESS = 0; const int METHOD_POST = 1; const int METHOD_GET = 2; const int HTTP_10 = 1; const int HTTP_11 = 2; const int EPOLL_WAIT_TIME = 500; class MimeType { private: static pthread_mutex_t lock; static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); }; enum HeadersState { h_start = 0, h_key, h_colon, h_spaces_after_colon, h_value, h_CR, h_LF, h_end_CR, h_end_LF }; struct mytimer; struct requestData; struct requestData { private: int againTimes; std::string path; int fd; int epollfd; // content的内容用完就清 std::string content; int method; int HTTPversion; std::string file_name; int now_read_pos; int state; int h_state; bool isfinish; bool keep_alive; std::unordered_map headers; mytimer *timer; private: int parse_URI(); int parse_Headers(); int analysisRequest(); public: requestData(); requestData(int _epollfd, int _fd, std::string _path); ~requestData(); void addTimer(mytimer *mtimer); void reset(); void seperateTimer(); int getFd(); void setFd(int _fd); void handleRequest(); void handleError(int fd, int err_num, std::string short_msg); }; struct mytimer { bool deleted; size_t expired_time; requestData *request_data; mytimer(requestData *_request_data, int timeout); ~mytimer(); void update(int timeout); bool isvalid(); void clearReq(); void setDeleted(); bool isDeleted() const; size_t getExpTime() const; }; struct timerCmp { bool operator()(const mytimer *a, const mytimer *b) const; }; class MutexLockGuard { public: explicit MutexLockGuard(); ~MutexLockGuard(); private: static pthread_mutex_t lock; private: MutexLockGuard(const MutexLockGuard&); MutexLockGuard& operator=(const MutexLockGuard&); }; #endif ================================================ FILE: old_version/old_version_0.2/threadpool.cpp ================================================ #include "threadpool.h" threadpool_t *threadpool_create(int thread_count, int queue_size, int flags) { threadpool_t *pool; int i; //(void) flags; do { if(thread_count <= 0 || thread_count > MAX_THREADS || queue_size <= 0 || queue_size > MAX_QUEUE) { return NULL; } if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) { break; } /* Initialize */ pool->thread_count = 0; pool->queue_size = queue_size; pool->head = pool->tail = pool->count = 0; pool->shutdown = pool->started = 0; /* Allocate thread and task queue */ pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count); pool->queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_size); /* Initialize mutex and conditional variable first */ if((pthread_mutex_init(&(pool->lock), NULL) != 0) || (pthread_cond_init(&(pool->notify), NULL) != 0) || (pool->threads == NULL) || (pool->queue == NULL)) { break; } /* Start worker threads */ for(i = 0; i < thread_count; i++) { if(pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool) != 0) { threadpool_destroy(pool, 0); return NULL; } pool->thread_count++; pool->started++; } return pool; } while(false); if (pool != NULL) { threadpool_free(pool); } return NULL; } int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags) { //printf("add to thread pool !\n"); int err = 0; int next; //(void) flags; if(pool == NULL || function == NULL) { return THREADPOOL_INVALID; } if(pthread_mutex_lock(&(pool->lock)) != 0) { return THREADPOOL_LOCK_FAILURE; } next = (pool->tail + 1) % pool->queue_size; do { /* Are we full ? */ if(pool->count == pool->queue_size) { err = THREADPOOL_QUEUE_FULL; break; } /* Are we shutting down ? */ if(pool->shutdown) { err = THREADPOOL_SHUTDOWN; break; } /* Add task to queue */ pool->queue[pool->tail].function = function; pool->queue[pool->tail].argument = argument; pool->tail = next; pool->count += 1; /* pthread_cond_broadcast */ if(pthread_cond_signal(&(pool->notify)) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&pool->lock) != 0) { err = THREADPOOL_LOCK_FAILURE; } return err; } int threadpool_destroy(threadpool_t *pool, int flags) { printf("Thread pool destroy !\n"); int i, err = 0; if(pool == NULL) { return THREADPOOL_INVALID; } if(pthread_mutex_lock(&(pool->lock)) != 0) { return THREADPOOL_LOCK_FAILURE; } do { /* Already shutting down */ if(pool->shutdown) { err = THREADPOOL_SHUTDOWN; break; } pool->shutdown = (flags & THREADPOOL_GRACEFUL) ? graceful_shutdown : immediate_shutdown; /* Wake up all worker threads */ if((pthread_cond_broadcast(&(pool->notify)) != 0) || (pthread_mutex_unlock(&(pool->lock)) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } /* Join all worker thread */ for(i = 0; i < pool->thread_count; ++i) { if(pthread_join(pool->threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); /* Only if everything went well do we deallocate the pool */ if(!err) { threadpool_free(pool); } return err; } int threadpool_free(threadpool_t *pool) { if(pool == NULL || pool->started > 0) { return -1; } /* Did we manage to allocate ? */ if(pool->threads) { free(pool->threads); free(pool->queue); /* Because we allocate pool->threads after initializing the mutex and condition variable, we're sure they're initialized. Let's lock the mutex just in case. */ pthread_mutex_lock(&(pool->lock)); pthread_mutex_destroy(&(pool->lock)); pthread_cond_destroy(&(pool->notify)); } free(pool); return 0; } static void *threadpool_thread(void *threadpool) { threadpool_t *pool = (threadpool_t *)threadpool; threadpool_task_t task; for(;;) { /* Lock must be taken to wait on conditional variable */ pthread_mutex_lock(&(pool->lock)); /* Wait on condition variable, check for spurious wakeups. When returning from pthread_cond_wait(), we own the lock. */ while((pool->count == 0) && (!pool->shutdown)) { pthread_cond_wait(&(pool->notify), &(pool->lock)); } if((pool->shutdown == immediate_shutdown) || ((pool->shutdown == graceful_shutdown) && (pool->count == 0))) { break; } /* Grab our task */ task.function = pool->queue[pool->head].function; task.argument = pool->queue[pool->head].argument; pool->head = (pool->head + 1) % pool->queue_size; pool->count -= 1; /* Unlock */ pthread_mutex_unlock(&(pool->lock)); /* Get to work */ (*(task.function))(task.argument); } --pool->started; pthread_mutex_unlock(&(pool->lock)); pthread_exit(NULL); return(NULL); } ================================================ FILE: old_version/old_version_0.2/threadpool.h ================================================ #ifndef THREADPOOL #define THREADPOOL #include "requestData.h" #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } threadpool_shutdown_t; /** * @struct threadpool_task * @brief the work struct * * @var function Pointer to the function that will perform the task. * @var argument Argument to be passed to the function. */ typedef struct { void (*function)(void *); void *argument; } threadpool_task_t; /** * @struct threadpool * @brief The threadpool struct * * @var notify Condition variable to notify worker threads. * @var threads Array containing worker threads ID. * @var thread_count Number of threads * @var queue Array containing the task queue. * @var queue_size Size of the task queue. * @var head Index of the first element. * @var tail Index of the next element. * @var count Number of pending tasks * @var shutdown Flag indicating if the pool is shutting down * @var started Number of started threads */ struct threadpool_t { pthread_mutex_t lock; pthread_cond_t notify; pthread_t *threads; threadpool_task_t *queue; int thread_count; int queue_size; int head; int tail; int count; int shutdown; int started; }; threadpool_t *threadpool_create(int thread_count, int queue_size, int flags); int threadpool_add(threadpool_t *pool, void (*function)(void *), void *argument, int flags); int threadpool_destroy(threadpool_t *pool, int flags); int threadpool_free(threadpool_t *pool); static void *threadpool_thread(void *threadpool); #endif ================================================ FILE: old_version/old_version_0.2/util.cpp ================================================ #include "util.h" #include #include #include #include #include ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR || errno == EAGAIN) { nwritten = 0; continue; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if(sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if(flag == -1) return -1; flag |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } ================================================ FILE: old_version/old_version_0.2/util.h ================================================ #ifndef UTIL #define UTIL #include ssize_t readn(int fd, void *buff, size_t n); ssize_t writen(int fd, void *buff, size_t n); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); #endif ================================================ FILE: old_version/old_version_0.3/Makefile ================================================ SOURCE := $(wildcard *.cpp) OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE))) TARGET := myserver CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui INCLUDE:= -I./usr/local/include/opencv CFLAGS := -std=c++11 -g -Wall -O3 $(INCLUDE) CXXFLAGS:= $(CFLAGS) .PHONY : objs clean veryclean rebuild all all : $(TARGET) objs : $(OBJS) rebuild: veryclean all clean : rm -fr *.o veryclean : clean rm -rf $(TARGET) $(TARGET) : $(OBJS) $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) ================================================ FILE: old_version/old_version_0.3/Makefile1 ================================================ cc=g++ -std=c++11 main: main.o requestData.o epoll.o threadpool.o $(cc) -o main main.o requestData.o epoll.o threadpool.o -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml main.o: main.cpp $(cc) -I/usr/local/include/opencv -c main.cpp requestData.o: requestData.h requestData.cpp $(cc) -I/usr/local/include/opencv -c requestData.cpp epoll.o: epoll.h epoll.cpp $(cc) -I/usr/local/include/opencv -c epoll.cpp threadpool.o: threadpool.h threadpool.cpp $(cc) -I/usr/local/include/opencv -c threadpool.cpp .PHONY: clean clean: -rm main *.o .PHONY : everything objs clean veryclean rebuild everything : $(TARGET) all : $(TARGET) objs : $(OBJS) rebuild: veryclean everything clean : rm -fr *.o veryclean : clean rm -fr $(TARGET) ================================================ FILE: old_version/old_version_0.3/config.h ================================================ #pragma once ================================================ FILE: old_version/old_version_0.3/epoll.cpp ================================================ #include "epoll.h" #include "threadpool.h" #include "util.h" #include #include #include #include #include #include #include int TIMER_TIME_OUT = 500; extern std::priority_queue, std::deque>, timerCmp> myTimerQueue; epoll_event *Epoll::events; std::unordered_map> Epoll::fd2req; int Epoll::epoll_fd = 0; const std::string Epoll::PATH = "/"; int Epoll::epoll_init(int maxevents, int listen_num) { epoll_fd = epoll_create(listen_num + 1); if(epoll_fd == -1) return -1; //events.reset(new epoll_event[maxevents], [](epoll_event *data){delete [] data;}); events = new epoll_event[maxevents]; return 0; } // 注册新描述符 int Epoll::epoll_add(int fd, std::shared_ptr request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); return -1; } fd2req[fd] = request; return 0; } // 修改描述符状态 int Epoll::epoll_mod(int fd, std::shared_ptr request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); return -1; } fd2req[fd] = request; return 0; } // 从epoll中删除描述符 int Epoll::epoll_del(int fd, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); return -1; } auto fd_ite = fd2req.find(fd); if (fd_ite != fd2req.end()) fd2req.erase(fd_ite); return 0; } // 返回活跃事件数 void Epoll::my_epoll_wait(int listen_fd, int max_events, int timeout) { // printf("fd2req.size()==%d\n", fd2req.size()); int event_count = epoll_wait(epoll_fd, events, max_events, timeout); if (event_count < 0) perror("epoll wait error"); std::vector> req_data = getEventsRequest(listen_fd, event_count, PATH); if (req_data.size() > 0) { for (auto &req: req_data) { if (ThreadPool::threadpool_add(req) < 0) { // 线程池满了或者关闭了等原因,抛弃本次监听到的请求。 break; } } } } #include #include using namespace std; void Epoll::acceptConnection(int listen_fd, int epoll_fd, const std::string path) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); socklen_t client_addr_len = 0; int accept_fd = 0; while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0) { cout << inet_addr(client_addr.sin_addr.s_addr) << endl; cout << client_addr.sin_port << endl; /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 设为非阻塞模式 int ret = setSocketNonBlocking(accept_fd); if (ret < 0) { perror("Set non block failed!"); return; } std::shared_ptr req_info(new requestData(epoll_fd, accept_fd, path)); // 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理 __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; Epoll::epoll_add(accept_fd, req_info, _epo_event); // 新增时间信息 std::shared_ptr mtimer(new mytimer(req_info, TIMER_TIME_OUT)); req_info->addTimer(mtimer); MutexLockGuard lock; myTimerQueue.push(mtimer); } //if(accept_fd == -1) // perror("accept"); } // 分发处理函数 std::vector> Epoll::getEventsRequest(int listen_fd, int events_num, const std::string path) { std::vector> req_data; for(int i = 0; i < events_num; ++i) { // 获取有事件产生的描述符 int fd = events[i].data.fd; // 有事件发生的描述符为监听描述符 if(fd == listen_fd) { //cout << "This is listen_fd" << endl; acceptConnection(listen_fd, epoll_fd, path); } else if (fd < 3) { break; } else { // 排除错误事件 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { //printf("error event\n"); auto fd_ite = fd2req.find(fd); if (fd_ite != fd2req.end()) fd2req.erase(fd_ite); //printf("fd = %d, here\n", fd); continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 std::shared_ptr cur_req(fd2req[fd]); //printf("cur_req.use_count=%d\n", cur_req.use_count()); cur_req->seperateTimer(); req_data.push_back(cur_req); auto fd_ite = fd2req.find(fd); if (fd_ite != fd2req.end()) fd2req.erase(fd_ite); } } return req_data; } ================================================ FILE: old_version/old_version_0.3/epoll.h ================================================ #pragma once #include "requestData.h" #include #include #include #include class Epoll { private: static epoll_event *events; static std::unordered_map> fd2req; static int epoll_fd; static const std::string PATH; public: static int epoll_init(int maxevents, int listen_num); static int epoll_add(int fd, std::shared_ptr request, __uint32_t events); static int epoll_mod(int fd, std::shared_ptr request, __uint32_t events); static int epoll_del(int fd, __uint32_t events); static void my_epoll_wait(int listen_fd, int max_events, int timeout); static void acceptConnection(int listen_fd, int epoll_fd, const std::string path); static std::vector> getEventsRequest(int listen_fd, int events_num, const std::string path); }; ================================================ FILE: old_version/old_version_0.3/improvement.txt ================================================ 1. 指针可以改成shared_ptr,不需要delete。 2. 想法在某些地方写成单例模式。 3. readn 和 writen 改成写到不能写后或读到不能读后加入epoll等待,要记录写到哪或读到哪,维护这样一个状态。比较两者的性能差异。 4. 信号处理部分可以将 epoll_wait 替换为更安全的 epoll_pwait。 踩坑: 1. 对EPOLLONESHOT的误解,原以为当epoll_wait监听到相应的事件触发后,epoll会把与事件关联的fd从epoll描述符中禁止掉并且彻底删除,实际上并不会删除,man手册上的解释如下: EPOLLONESHOT (since Linux 2.6.2) Sets the one-shot behavior for the associated file descriptor. This means that after an event is pulled out with epoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epoll interface. The user must call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask. 另外: Linux Programmer's Manual 中第六个问题: Q6 Will closing a file descriptor cause it to be removed from all epoll sets automatically? A6 Yes, but be aware of the following point. A file descriptor is a reference to an open file description (see open(2)). Whenever a file descriptor is duplicated via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created. An open file description con‐ tinues to exist until all file descriptors referring to it have been closed. A file descriptor is removed from an epoll set only after all the file descriptors referring to the underlying open file description have been closed (or before if the file descrip‐ tor is explicitly removed using epoll_ctl(2) EPOLL_CTL_DEL). This means that even after a file descriptor that is part of an epoll set has been closed, events may be reported for that file descriptor if other file descriptors referring to the same under‐ lying file description remain open. 当调用close()关闭对应的fd时,会使相应的引用计数减一,只有减到0时,epoll才会真的删掉它,所以,比较安全的做法是: 先del掉它,再close它(如果不确定close是否真的关闭了这个文件。)。 ubuntu 配置opencv: vim /etc/ld.so.conf 加一行include /usr/local/opencv/* /sbin/ldconfig –v ================================================ FILE: old_version/old_version_0.3/index.html ================================================ Hello World ! ================================================ FILE: old_version/old_version_0.3/main.cpp ================================================ #include "requestData.h" #include "epoll.h" #include "threadpool.h" #include "util.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const int MAXEVENTS = 5000; static const int LISTENQ = 1024; const int THREADPOOL_THREAD_NUM = 4; const int QUEUE_SIZE = 65535; const int PORT = 8888; const int ASK_STATIC_FILE = 1; const int ASK_IMAGE_STITCH = 2; const string PATH = "/"; const int TIMER_TIME_OUT = 500; void acceptConnection(int listen_fd, int epoll_fd, const string &path); extern std::priority_queue, std::deque>, timerCmp> myTimerQueue; int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 1024 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) return -1; // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char*)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) return -1; // 开始监听,最大等待队列长为LISTENQ if(listen(listen_fd, LISTENQ) == -1) return -1; // 无效监听描述符 if(listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请requestData节点了,这样可以继续重复利用前面的requestData,减少了一次delete和一次new的时间。 */ void handle_expired_event() { MutexLockGuard lock; while (!myTimerQueue.empty()) { shared_ptr ptimer_now = myTimerQueue.top(); if (ptimer_now->isDeleted()) { myTimerQueue.pop(); //delete ptimer_now; } else if (ptimer_now->isvalid() == false) { myTimerQueue.pop(); //delete ptimer_now; } else { break; } } } int main() { handle_for_sigpipe(); if (Epoll::epoll_init(MAXEVENTS, LISTENQ) < 0) { perror("epoll init failed"); return 1; } if (ThreadPool::threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE) < 0) { printf("Threadpool create failed\n"); return 1; } int listen_fd = socket_bind_listen(PORT); if (listen_fd < 0) { perror("socket bind failed"); return 1; } if (setSocketNonBlocking(listen_fd) < 0) { perror("set socket non block failed"); return 1; } shared_ptr request(new requestData()); request->setFd(listen_fd); if (Epoll::epoll_add(listen_fd, request, EPOLLIN | EPOLLET) < 0) { perror("epoll add error"); return 1; } while (true) { Epoll::my_epoll_wait(listen_fd, MAXEVENTS, -1); handle_expired_event(); } return 0; } ================================================ FILE: old_version/old_version_0.3/requestData.cpp ================================================ #include "requestData.h" #include "util.h" #include "epoll.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace cv; //test #include using namespace std; pthread_mutex_t MutexLockGuard::lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t MimeType::lock = PTHREAD_MUTEX_INITIALIZER; std::unordered_map MimeType::mime; std::string MimeType::getMime(const std::string &suffix) { if (mime.size() == 0) { pthread_mutex_lock(&lock); if (mime.size() == 0) { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "application/x-ico"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } pthread_mutex_unlock(&lock); } if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } std::priority_queue, std::deque>, timerCmp> myTimerQueue; requestData::requestData(): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0) { cout << "requestData()" << endl; } requestData::requestData(int _epollfd, int _fd, std::string _path): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0), path(_path), fd(_fd), epollfd(_epollfd) { cout << "requestData()" << endl; } requestData::~requestData() { cout << "~requestData()" << endl; //Epoll::epoll_del(fd, EPOLLIN | EPOLLET | EPOLLONESHOT); //struct epoll_event ev; // 超时的一定都是读请求,没有"被动"写。 //ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; //ev.data.fd = fd; //epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); // if (timer.lock()) // { // shared_ptr my_timer(timer.lock()) // my_timer->clearReq(); // timer.reset(); // } close(fd); } void requestData::addTimer(shared_ptr mtimer) { // shared_ptr重载了bool, 但weak_ptr没有 //if (!timer.lock()) timer = mtimer; } int requestData::getFd() { return fd; } void requestData::setFd(int _fd) { fd = _fd; } void requestData::reset() { againTimes = 0; content.clear(); file_name.clear(); path.clear(); now_read_pos = 0; state = STATE_PARSE_URI; h_state = h_start; headers.clear(); keep_alive = false; if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void requestData::seperateTimer() { if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void requestData::handleRequest() { char buff[MAX_BUFF]; bool isError = false; while (true) { int read_num = readn(fd, buff, MAX_BUFF); if (read_num < 0) { perror("1"); isError = true; break; } else if (read_num == 0) { // 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因 perror("read_num == 0"); if (errno == EAGAIN) { if (againTimes > AGAIN_MAX_TIMES) isError = true; else ++againTimes; } else if (errno != 0) isError = true; break; } string now_read(buff, buff + read_num); content += now_read; if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) { break; } else if (flag == PARSE_URI_ERROR) { perror("2"); isError = true; break; } } if (state == STATE_PARSE_HEADERS) { int flag = this->parse_Headers(); if (flag == PARSE_HEADER_AGAIN) { break; } else if (flag == PARSE_HEADER_ERROR) { perror("3"); isError = true; break; } if(method == METHOD_POST) { state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) { int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { isError = true; break; } if (content.size() < content_length) continue; state = STATE_ANALYSIS; } if (state == STATE_ANALYSIS) { int flag = this->analysisRequest(); if (flag < 0) { isError = true; break; } else if (flag == ANALYSIS_SUCCESS) { state = STATE_FINISH; break; } else { isError = true; break; } } } if (isError) { //delete this; return; } // 加入epoll继续 if (state == STATE_FINISH) { if (keep_alive) { //printf("ok\n"); this->reset(); } else { //delete this; return; } } // 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。 // 新增时间信息 //cout << "shared_from_this().use_count() ==" << shared_from_this().use_count() << endl; shared_ptr mtimer(new mytimer(shared_from_this(), 500)); this->addTimer(mtimer); { MutexLockGuard lock; myTimerQueue.push(mtimer); } __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; int ret = Epoll::epoll_mod(fd, shared_from_this(), _epo_event); //cout << "shared_from_this().use_count() ==" << shared_from_this().use_count() << endl; if (ret < 0) { // 返回错误处理 //delete this; return; } } int requestData::parse_URI() { string &str = content; // 读到完整的请求行再开始解析请求 int pos = str.find('\r', now_read_pos); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method pos = request_line.find("GET"); if (pos < 0) { pos = request_line.find("POST"); if (pos < 0) { return PARSE_URI_ERROR; } else { method = METHOD_POST; } } else { method = METHOD_GET; } //printf("method = %d\n", method); // filename pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { int _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { file_name = request_line.substr(pos + 1, _pos - pos - 1); int __pos = file_name.find('?'); if (__pos >= 0) { file_name = file_name.substr(0, __pos); } } else file_name = "index.html"; } pos = _pos; } //cout << "file_name: " << file_name << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { if (request_line.size() - pos <= 3) { return PARSE_URI_ERROR; } else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPversion = HTTP_10; else if (ver == "1.1") HTTPversion = HTTP_11; else return PARSE_URI_ERROR; } } state = STATE_PARSE_HEADERS; return PARSE_URI_SUCCESS; } int requestData::parse_Headers() { string &str = content; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case h_colon: { if (str[i] == ' ') { h_state = h_spaces_after_colon; } else return PARSE_HEADER_ERROR; break; } case h_spaces_after_colon: { h_state = h_value; value_start = i; break; } case h_value: { if (str[i] == '\r') { h_state = h_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case h_CR: { if (str[i] == '\n') { h_state = h_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case h_LF: { if (str[i] == '\r') { h_state = h_end_CR; } else { key_start = i; h_state = h_key; } break; } case h_end_CR: { if (str[i] == '\n') { h_state = h_end_LF; } else return PARSE_HEADER_ERROR; break; } case h_end_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (h_state == h_end_LF) { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } int requestData::analysisRequest() { if (method == METHOD_POST) { //get content char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } //cout << "content=" << content << endl; // test char* char *send_content = "I have receiced this."; sprintf(header, "%sContent-length: %zu\r\n", header, strlen(send_content)); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } send_len = (size_t)writen(fd, send_content, strlen(send_content)); if(send_len != strlen(send_content)) { perror("Send content failed"); return ANALYSIS_ERROR; } cout << "content size ==" << content.size() << endl; vector data(content.begin(), content.end()); Mat test = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); imwrite("receive.bmp", test); return ANALYSIS_SUCCESS; } else if (method == METHOD_GET) { char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } int dot_pos = file_name.find('.'); const char* filetype; if (dot_pos < 0) filetype = MimeType::getMime("default").c_str(); else filetype = MimeType::getMime(file_name.substr(dot_pos)).c_str(); struct stat sbuf; if (stat(file_name.c_str(), &sbuf) < 0) { handleError(fd, 404, "Not Found!"); return ANALYSIS_ERROR; } sprintf(header, "%sContent-type: %s\r\n", header, filetype); // 通过Content-length返回文件大小 sprintf(header, "%sContent-length: %ld\r\n", header, sbuf.st_size); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } int src_fd = open(file_name.c_str(), O_RDONLY, 0); char *src_addr = static_cast(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0)); close(src_fd); // 发送文件并校验完整性 send_len = writen(fd, src_addr, sbuf.st_size); if(send_len != sbuf.st_size) { perror("Send file failed"); return ANALYSIS_ERROR; } munmap(src_addr, sbuf.st_size); return ANALYSIS_SUCCESS; } else return ANALYSIS_ERROR; } void requestData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[MAX_BUFF]; string body_buff, header_buff; body_buff += "TKeed Error"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-type: text/html\r\n"; header_buff += "Connection: close\r\n"; header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "\r\n"; sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } mytimer::mytimer(shared_ptr _request_data, int timeout): deleted(false), request_data(_request_data) { cout << "mytimer()" << endl; struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } mytimer::~mytimer() { cout << "~mytimer()" << endl; if (request_data) { Epoll::epoll_del(request_data->getFd(), EPOLLIN | EPOLLET | EPOLLONESHOT); } //request_data.reset(); // if (request_data) // { // cout << "request_data=" << request_data << endl; // delete request_data; // request_data = NULL; // } } void mytimer::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } bool mytimer::isvalid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = ((now.tv_sec * 1000) + (now.tv_usec / 1000)); if (temp < expired_time) { return true; } else { this->setDeleted(); return false; } } void mytimer::clearReq() { request_data.reset(); this->setDeleted(); } void mytimer::setDeleted() { deleted = true; } bool mytimer::isDeleted() const { return deleted; } size_t mytimer::getExpTime() const { return expired_time; } bool timerCmp::operator()(shared_ptr &a, shared_ptr &b) const { return a->getExpTime() > b->getExpTime(); } MutexLockGuard::MutexLockGuard() { pthread_mutex_lock(&lock); } MutexLockGuard::~MutexLockGuard() { pthread_mutex_unlock(&lock); } ================================================ FILE: old_version/old_version_0.3/requestData.h ================================================ #pragma once #include #include #include const int STATE_PARSE_URI = 1; const int STATE_PARSE_HEADERS = 2; const int STATE_RECV_BODY = 3; const int STATE_ANALYSIS = 4; const int STATE_FINISH = 5; const int MAX_BUFF = 4096; // 有请求出现但是读不到数据,可能是Request Aborted, // 或者来自网络的数据没有达到等原因, // 对这样的请求尝试超过一定的次数就抛弃 const int AGAIN_MAX_TIMES = 200; const int PARSE_URI_AGAIN = -1; const int PARSE_URI_ERROR = -2; const int PARSE_URI_SUCCESS = 0; const int PARSE_HEADER_AGAIN = -1; const int PARSE_HEADER_ERROR = -2; const int PARSE_HEADER_SUCCESS = 0; const int ANALYSIS_ERROR = -2; const int ANALYSIS_SUCCESS = 0; const int METHOD_POST = 1; const int METHOD_GET = 2; const int HTTP_10 = 1; const int HTTP_11 = 2; const int EPOLL_WAIT_TIME = 500; class MimeType { private: static pthread_mutex_t lock; static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); }; enum HeadersState { h_start = 0, h_key, h_colon, h_spaces_after_colon, h_value, h_CR, h_LF, h_end_CR, h_end_LF }; struct mytimer; class requestData; class requestData : public std::enable_shared_from_this { private: int againTimes; std::string path; int fd; int epollfd; // content的内容用完就清 std::string content; int method; int HTTPversion; std::string file_name; int now_read_pos; int state; int h_state; bool isfinish; bool keep_alive; std::unordered_map headers; std::weak_ptr timer; private: int parse_URI(); int parse_Headers(); int analysisRequest(); public: requestData(); requestData(int _epollfd, int _fd, std::string _path); ~requestData(); void addTimer(std::shared_ptr mtimer); void reset(); void seperateTimer(); int getFd(); void setFd(int _fd); void handleRequest(); void handleError(int fd, int err_num, std::string short_msg); }; struct mytimer { bool deleted; size_t expired_time; std::shared_ptr request_data; mytimer(std::shared_ptr _request_data, int timeout); ~mytimer(); void update(int timeout); bool isvalid(); void clearReq(); void setDeleted(); bool isDeleted() const; size_t getExpTime() const; }; struct timerCmp { bool operator()(std::shared_ptr &a, std::shared_ptr &b) const; }; class MutexLockGuard { public: explicit MutexLockGuard(); ~MutexLockGuard(); private: static pthread_mutex_t lock; private: MutexLockGuard(const MutexLockGuard&); MutexLockGuard& operator=(const MutexLockGuard&); }; ================================================ FILE: old_version/old_version_0.3/threadpool.cpp ================================================ #include "threadpool.h" pthread_mutex_t ThreadPool::lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ThreadPool::notify = PTHREAD_COND_INITIALIZER; std::vector ThreadPool::threads; std::vector ThreadPool::queue; int ThreadPool::thread_count = 0; int ThreadPool::queue_size = 0; int ThreadPool::head = 0; int ThreadPool::tail = 0; int ThreadPool::count = 0; int ThreadPool::shutdown = 0; int ThreadPool::started = 0; int ThreadPool::threadpool_create(int _thread_count, int _queue_size) { bool err = false; do { if(_thread_count <= 0 || _thread_count > MAX_THREADS || _queue_size <= 0 || _queue_size > MAX_QUEUE) { _thread_count = 4; _queue_size = 1024; } thread_count = 0; queue_size = _queue_size; head = tail = count = 0; shutdown = started = 0; threads.resize(_thread_count); queue.resize(_queue_size); /* Start worker threads */ for(int i = 0; i < _thread_count; ++i) { if(pthread_create(&threads[i], NULL, threadpool_thread, (void*)(0)) != 0) { //threadpool_destroy(pool, 0); return -1; } ++thread_count; ++started; } } while(false); if (err) { //threadpool_free(pool); return -1; } return 0; } void myHandler(std::shared_ptr req) { std::shared_ptr request = std::static_pointer_cast(req); request->handleRequest(); } int ThreadPool::threadpool_add(std::shared_ptr args, std::function)> fun) { int next, err = 0; if(pthread_mutex_lock(&lock) != 0) return THREADPOOL_LOCK_FAILURE; do { next = (tail + 1) % queue_size; // 队列满 if(count == queue_size) { err = THREADPOOL_QUEUE_FULL; break; } // 已关闭 if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } queue[tail].fun = fun; queue[tail].args = args; tail = next; ++count; /* pthread_cond_broadcast */ if(pthread_cond_signal(¬ify) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&lock) != 0) err = THREADPOOL_LOCK_FAILURE; return err; } /* int threadpool_destroy(threadpool_t *pool, int flags) { printf("Thread pool destroy !\n"); int i, err = 0; if(pthread_mutex_lock(&(pool->lock)) != 0) { return THREADPOOL_LOCK_FAILURE; } do { if(pool->shutdown) { err = THREADPOOL_SHUTDOWN; break; } pool->shutdown = (flags & THREADPOOL_GRACEFUL) ? graceful_shutdown : immediate_shutdown; if((pthread_cond_broadcast(&(pool->notify)) != 0) || (pthread_mutex_unlock(&(pool->lock)) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } for(i = 0; i < pool->thread_count; ++i) { if(pthread_join(pool->threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); if(!err) { threadpool_free(pool); } return err; } int threadpool_free(threadpool_t *pool) { if(pool == NULL || pool->started > 0) { return -1; } if(pool->threads) { free(pool->threads); free(pool->queue); pthread_mutex_lock(&(pool->lock)); pthread_mutex_destroy(&(pool->lock)); pthread_cond_destroy(&(pool->notify)); } free(pool); return 0; } */ void *ThreadPool::threadpool_thread(void *args) { while (true) { ThreadPoolTask task; pthread_mutex_lock(&lock); while((count == 0) && (!shutdown)) { pthread_cond_wait(¬ify, &lock); } if((shutdown == immediate_shutdown) || ((shutdown == graceful_shutdown) && (count == 0))) { break; } task.fun = queue[head].fun; task.args = queue[head].args; queue[head].fun = NULL; queue[head].args.reset(); head = (head + 1) % queue_size; --count; pthread_mutex_unlock(&lock); (task.fun)(task.args); } --started; pthread_mutex_unlock(&lock); pthread_exit(NULL); return(NULL); } ================================================ FILE: old_version/old_version_0.3/threadpool.h ================================================ #pragma once #include "requestData.h" #include #include #include #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } threadpool_shutdown_t; struct ThreadPoolTask { std::function)> fun; std::shared_ptr args; }; /** * @struct threadpool * @brief The threadpool struct * * @var notify Condition variable to notify worker threads. * @var threads Array containing worker threads ID. * @var thread_count Number of threads * @var queue Array containing the task queue. * @var queue_size Size of the task queue. * @var head Index of the first element. * @var tail Index of the next element. * @var count Number of pending tasks * @var shutdown Flag indicating if the pool is shutting down * @var started Number of started threads */ void myHandler(std::shared_ptr req); class ThreadPool { private: static pthread_mutex_t lock; static pthread_cond_t notify; static std::vector threads; static std::vector queue; static int thread_count; static int queue_size; static int head; // tail 指向尾节点的下一节点 static int tail; static int count; static int shutdown; static int started; public: static int threadpool_create(int _thread_count, int _queue_size); static int threadpool_add(std::shared_ptr args, std::function)> fun = myHandler); static int threadpool_destroy(); static int threadpool_free(); static void *threadpool_thread(void *args); }; ================================================ FILE: old_version/old_version_0.3/util.cpp ================================================ #include "util.h" #include #include #include #include #include ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR || errno == EAGAIN) { nwritten = 0; continue; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if(sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if(flag == -1) return -1; flag |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } ================================================ FILE: old_version/old_version_0.3/util.h ================================================ #pragma once #include ssize_t readn(int fd, void *buff, size_t n); ssize_t writen(int fd, void *buff, size_t n); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); ================================================ FILE: old_version/old_version_0.4/Makefile ================================================ SOURCE := $(wildcard *.cpp) OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE))) TARGET := myserver CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui INCLUDE:= -I./usr/local/include/opencv CFLAGS := -std=c++11 -g -Wall -O3 $(INCLUDE) -D_PTHREADS CXXFLAGS:= $(CFLAGS) .PHONY : objs clean veryclean rebuild all all : $(TARGET) objs : $(OBJS) rebuild: veryclean all clean : rm -fr *.o veryclean : clean rm -rf $(TARGET) $(TARGET) : $(OBJS) $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) ================================================ FILE: old_version/old_version_0.4/Makefile1 ================================================ cc=g++ -std=c++11 main: main.o RequestData.o epoll.o threadpool.o $(cc) -o main main.o RequestData.o epoll.o threadpool.o -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml main.o: main.cpp $(cc) -I/usr/local/include/opencv -c main.cpp RequestData.o: requestData.h RequestData.cpp $(cc) -I/usr/local/include/opencv -c RequestData.cpp epoll.o: epoll.h epoll.cpp $(cc) -I/usr/local/include/opencv -c epoll.cpp threadpool.o: threadpool.h threadpool.cpp $(cc) -I/usr/local/include/opencv -c threadpool.cpp .PHONY: clean clean: -rm main *.o .PHONY : everything objs clean veryclean rebuild everything : $(TARGET) all : $(TARGET) objs : $(OBJS) rebuild: veryclean everything clean : rm -fr *.o veryclean : clean rm -fr $(TARGET) ================================================ FILE: old_version/old_version_0.4/base/mutexLock.hpp ================================================ #pragma once #include "nocopyable.hpp" #include #include class MutexLock: noncopyable { public: MutexLock() { pthread_mutex_init(&mutex, NULL); } ~MutexLock() { pthread_mutex_lock(&mutex); pthread_mutex_destroy(&mutex); } void lock() { pthread_mutex_lock(&mutex); } void unlock() { pthread_mutex_unlock(&mutex); } private: pthread_mutex_t mutex; }; class MutexLockGuard: noncopyable { public: explicit MutexLockGuard(MutexLock &_mutex): mutex(_mutex) { mutex.lock(); } ~MutexLockGuard() { mutex.unlock(); } private: MutexLock &mutex; }; ================================================ FILE: old_version/old_version_0.4/base/nocopyable.hpp ================================================ #pragma once class noncopyable { protected: noncopyable() {} ~noncopyable() {} private: noncopyable(const noncopyable&); const noncopyable& operator=(const noncopyable&); }; ================================================ FILE: old_version/old_version_0.4/config.h ================================================ #pragma once ================================================ FILE: old_version/old_version_0.4/epoll.cpp ================================================ #include "epoll.h" #include "threadpool.h" #include "util.h" #include #include #include #include #include #include #include int TIMER_TIME_OUT = 500; epoll_event *Epoll::events; std::unordered_map> Epoll::fd2req; int Epoll::epoll_fd = 0; const std::string Epoll::PATH = "/"; TimerManager Epoll::timer_manager; int Epoll::epoll_init(int maxevents, int listen_num) { epoll_fd = epoll_create(listen_num + 1); if(epoll_fd == -1) return -1; //events.reset(new epoll_event[maxevents], [](epoll_event *data){delete [] data;}); events = new epoll_event[maxevents]; return 0; } // 注册新描述符 int Epoll::epoll_add(int fd, SP_ReqData request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); return -1; } fd2req[fd] = request; return 0; } // 修改描述符状态 int Epoll::epoll_mod(int fd, SP_ReqData request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); return -1; } fd2req[fd] = request; return 0; } // 从epoll中删除描述符 int Epoll::epoll_del(int fd, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); return -1; } auto fd_ite = fd2req.find(fd); if (fd_ite != fd2req.end()) fd2req.erase(fd_ite); return 0; } // 返回活跃事件数 void Epoll::my_epoll_wait(int listen_fd, int max_events, int timeout) { // printf("fd2req.size()==%d\n", fd2req.size()); int event_count = epoll_wait(epoll_fd, events, max_events, timeout); if (event_count < 0) perror("epoll wait error"); std::vector req_data = getEventsRequest(listen_fd, event_count, PATH); if (req_data.size() > 0) { for (auto &req: req_data) { if (ThreadPool::threadpool_add(req) < 0) { // 线程池满了或者关闭了等原因,抛弃本次监听到的请求。 break; } } } timer_manager.handle_expired_event(); } #include #include using namespace std; void Epoll::acceptConnection(int listen_fd, int epoll_fd, const std::string path) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); //socklen_t client_addr_len = 0; socklen_t client_addr_len = sizeof(client_addr); int accept_fd = 0; while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0) { cout << inet_ntoa(client_addr.sin_addr) << endl; cout << ntohs(client_addr.sin_port) << endl; /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 设为非阻塞模式 int ret = setSocketNonBlocking(accept_fd); if (ret < 0) { perror("Set non block failed!"); return; } SP_ReqData req_info(new RequestData(epoll_fd, accept_fd, path)); // 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理 __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; Epoll::epoll_add(accept_fd, req_info, _epo_event); // 新增时间信息 timer_manager.addTimer(req_info, TIMER_TIME_OUT); } //if(accept_fd == -1) // perror("accept"); } // 分发处理函数 std::vector> Epoll::getEventsRequest(int listen_fd, int events_num, const std::string path) { std::vector req_data; for(int i = 0; i < events_num; ++i) { // 获取有事件产生的描述符 int fd = events[i].data.fd; // 有事件发生的描述符为监听描述符 if(fd == listen_fd) { //cout << "This is listen_fd" << endl; acceptConnection(listen_fd, epoll_fd, path); } else if (fd < 3) { break; } else { // 排除错误事件 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { //printf("error event\n"); auto fd_ite = fd2req.find(fd); if (fd_ite != fd2req.end()) fd2req.erase(fd_ite); continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 SP_ReqData cur_req(fd2req[fd]); //printf("cur_req.use_count=%d\n", cur_req.use_count()); cur_req->seperateTimer(); req_data.push_back(cur_req); auto fd_ite = fd2req.find(fd); if (fd_ite != fd2req.end()) fd2req.erase(fd_ite); } } return req_data; } void Epoll::add_timer(shared_ptr request_data, int timeout) { timer_manager.addTimer(request_data, timeout); } ================================================ FILE: old_version/old_version_0.4/epoll.h ================================================ #pragma once #include "requestData.h" #include "timer.h" #include #include #include #include class Epoll { public: typedef std::shared_ptr SP_ReqData; private: static epoll_event *events; static std::unordered_map fd2req; static int epoll_fd; static const std::string PATH; static TimerManager timer_manager; public: static int epoll_init(int maxevents, int listen_num); static int epoll_add(int fd, SP_ReqData request, __uint32_t events); static int epoll_mod(int fd, SP_ReqData request, __uint32_t events); static int epoll_del(int fd, __uint32_t events = (EPOLLIN | EPOLLET | EPOLLONESHOT)); static void my_epoll_wait(int listen_fd, int max_events, int timeout); static void acceptConnection(int listen_fd, int epoll_fd, const std::string path); static std::vector getEventsRequest(int listen_fd, int events_num, const std::string path); static void add_timer(SP_ReqData request_data, int timeout); }; ================================================ FILE: old_version/old_version_0.4/improvement.txt ================================================ 1. 指针可以改成shared_ptr,不需要delete。 2. 想法在某些地方写成单例模式。 3. readn 和 writen 改成写到不能写后或读到不能读后加入epoll等待,要记录写到哪或读到哪,维护这样一个状态。比较两者的性能差异。 4. 信号处理部分可以将 epoll_wait 替换为更安全的 epoll_pwait。 踩坑: 1. 对EPOLLONESHOT的误解,原以为当epoll_wait监听到相应的事件触发后,epoll会把与事件关联的fd从epoll描述符中禁止掉并且彻底删除,实际上并不会删除,man手册上的解释如下: EPOLLONESHOT (since Linux 2.6.2) Sets the one-shot behavior for the associated file descriptor. This means that after an event is pulled out with epoll_wait(2) the associated file descriptor is internally disabled and no other events will be reported by the epoll interface. The user must call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask. 另外: Linux Programmer's Manual 中第六个问题: Q6 Will closing a file descriptor cause it to be removed from all epoll sets automatically? A6 Yes, but be aware of the following point. A file descriptor is a reference to an open file description (see open(2)). Whenever a file descriptor is duplicated via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created. An open file description con‐ tinues to exist until all file descriptors referring to it have been closed. A file descriptor is removed from an epoll set only after all the file descriptors referring to the underlying open file description have been closed (or before if the file descrip‐ tor is explicitly removed using epoll_ctl(2) EPOLL_CTL_DEL). This means that even after a file descriptor that is part of an epoll set has been closed, events may be reported for that file descriptor if other file descriptors referring to the same under‐ lying file description remain open. 当调用close()关闭对应的fd时,会使相应的引用计数减一,只有减到0时,epoll才会真的删掉它,所以,比较安全的做法是: 先del掉它,再close它(如果不确定close是否真的关闭了这个文件。)。 ubuntu 配置opencv: vim /etc/ld.so.conf 加一行include /usr/local/opencv/* /sbin/ldconfig –v ================================================ FILE: old_version/old_version_0.4/index.html ================================================ Hello World ! ================================================ FILE: old_version/old_version_0.4/main.cpp ================================================ #include "requestData.h" #include "epoll.h" #include "threadpool.h" #include "util.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const int MAXEVENTS = 5000; static const int LISTENQ = 1024; const int THREADPOOL_THREAD_NUM = 4; const int QUEUE_SIZE = 65535; const int PORT = 8888; const int ASK_STATIC_FILE = 1; const int ASK_IMAGE_STITCH = 2; const int TIMER_TIME_OUT = 500; void acceptConnection(int listen_fd, int epoll_fd, const string &path); int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 1024 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) return -1; // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char*)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) return -1; // 开始监听,最大等待队列长为LISTENQ if(listen(listen_fd, LISTENQ) == -1) return -1; // 无效监听描述符 if(listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } int main() { #ifndef _PTHREADS printf("_PTHREADS is not defined !\n"); #endif handle_for_sigpipe(); if (Epoll::epoll_init(MAXEVENTS, LISTENQ) < 0) { perror("epoll init failed"); return 1; } if (ThreadPool::threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE) < 0) { printf("Threadpool create failed\n"); return 1; } int listen_fd = socket_bind_listen(PORT); if (listen_fd < 0) { perror("socket bind failed"); return 1; } if (setSocketNonBlocking(listen_fd) < 0) { perror("set socket non block failed"); return 1; } shared_ptr request(new RequestData()); request->setFd(listen_fd); if (Epoll::epoll_add(listen_fd, request, EPOLLIN | EPOLLET) < 0) { perror("epoll add error"); return 1; } while (true) { //sleep(10); Epoll::my_epoll_wait(listen_fd, MAXEVENTS, -1); //ThreadPool::threadpool_destroy(); //break; } return 0; } ================================================ FILE: old_version/old_version_0.4/requestData.cpp ================================================ #include "requestData.h" #include "util.h" #include "epoll.h" #include #include #include #include #include #include #include #include #include #include #include using namespace cv; //test #include using namespace std; pthread_once_t MimeType::once_control = PTHREAD_ONCE_INIT; std::unordered_map MimeType::mime; void MimeType::init() { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "application/x-ico"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } std::string MimeType::getMime(const std::string &suffix) { pthread_once(&once_control, MimeType::init); if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } RequestData::RequestData(): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0) { cout << "RequestData()" << endl; } RequestData::RequestData(int _epollfd, int _fd, std::string _path): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), againTimes(0), path(_path), fd(_fd), epollfd(_epollfd) { cout << "RequestData()" << endl; } RequestData::~RequestData() { cout << "~RequestData()" << endl; //Epoll::epoll_del(fd, EPOLLIN | EPOLLET | EPOLLONESHOT); //struct epoll_event ev; // 超时的一定都是读请求,没有"被动"写。 //ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; //ev.data.fd = fd; //epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); // if (timer.lock()) // { // shared_ptr my_timer(timer.lock()) // my_timer->clearReq(); // timer.reset(); // } close(fd); } void RequestData::linkTimer(shared_ptr mtimer) { // shared_ptr重载了bool, 但weak_ptr没有 //if (!timer.lock()) timer = mtimer; } int RequestData::getFd() { return fd; } void RequestData::setFd(int _fd) { fd = _fd; } void RequestData::reset() { againTimes = 0; content.clear(); file_name.clear(); path.clear(); now_read_pos = 0; state = STATE_PARSE_URI; h_state = h_start; headers.clear(); keep_alive = false; if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void RequestData::seperateTimer() { if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void RequestData::handleRequest() { char buff[MAX_BUFF]; bool isError = false; while (true) { int read_num = readn(fd, buff, MAX_BUFF); if (read_num < 0) { perror("1"); isError = true; break; } else if (read_num == 0) { // 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因 perror("read_num == 0"); if (errno == EAGAIN) { if (againTimes > AGAIN_MAX_TIMES) isError = true; else ++againTimes; } else if (errno != 0) isError = true; break; } string now_read(buff, buff + read_num); content += now_read; if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) { break; } else if (flag == PARSE_URI_ERROR) { perror("2"); isError = true; break; } } if (state == STATE_PARSE_HEADERS) { int flag = this->parse_Headers(); if (flag == PARSE_HEADER_AGAIN) { break; } else if (flag == PARSE_HEADER_ERROR) { perror("3"); isError = true; break; } if(method == METHOD_POST) { state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) { int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { isError = true; break; } if (content.size() < content_length) continue; state = STATE_ANALYSIS; } if (state == STATE_ANALYSIS) { int flag = this->analysisRequest(); if (flag < 0) { isError = true; break; } else if (flag == ANALYSIS_SUCCESS) { state = STATE_FINISH; break; } else { isError = true; break; } } } if (isError) { //delete this; return; } // 加入epoll继续 if (state == STATE_FINISH) { if (keep_alive) { //printf("ok\n"); this->reset(); } else { //delete this; return; } } // 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。 // 新增时间信息 Epoll::add_timer(shared_from_this(), 500); // shared_ptr mtimer(new TimerNode(shared_from_this(), 500)); // this->linkTimer(mtimer); // { // MutexLockGuard lock; // TimerNodeQueue.push(mtimer); // } __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; int ret = Epoll::epoll_mod(fd, shared_from_this(), _epo_event); //cout << "shared_from_this().use_count() ==" << shared_from_this().use_count() << endl; if (ret < 0) { // 返回错误处理 //delete this; return; } } int RequestData::parse_URI() { string &str = content; // 读到完整的请求行再开始解析请求 int pos = str.find('\r', now_read_pos); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method pos = request_line.find("GET"); if (pos < 0) { pos = request_line.find("POST"); if (pos < 0) { return PARSE_URI_ERROR; } else { method = METHOD_POST; } } else { method = METHOD_GET; } //printf("method = %d\n", method); // filename pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { int _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { file_name = request_line.substr(pos + 1, _pos - pos - 1); int __pos = file_name.find('?'); if (__pos >= 0) { file_name = file_name.substr(0, __pos); } } else file_name = "index.html"; } pos = _pos; } //cout << "file_name: " << file_name << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) { return PARSE_URI_ERROR; } else { if (request_line.size() - pos <= 3) { return PARSE_URI_ERROR; } else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPversion = HTTP_10; else if (ver == "1.1") HTTPversion = HTTP_11; else return PARSE_URI_ERROR; } } state = STATE_PARSE_HEADERS; return PARSE_URI_SUCCESS; } int RequestData::parse_Headers() { string &str = content; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case h_colon: { if (str[i] == ' ') { h_state = h_spaces_after_colon; } else return PARSE_HEADER_ERROR; break; } case h_spaces_after_colon: { h_state = h_value; value_start = i; break; } case h_value: { if (str[i] == '\r') { h_state = h_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case h_CR: { if (str[i] == '\n') { h_state = h_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case h_LF: { if (str[i] == '\r') { h_state = h_end_CR; } else { key_start = i; h_state = h_key; } break; } case h_end_CR: { if (str[i] == '\n') { h_state = h_end_LF; } else return PARSE_HEADER_ERROR; break; } case h_end_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (h_state == h_end_LF) { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } int RequestData::analysisRequest() { if (method == METHOD_POST) { //get content char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } //cout << "content=" << content << endl; // test char* char *send_content = "I have receiced this."; sprintf(header, "%sContent-length: %zu\r\n", header, strlen(send_content)); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } send_len = (size_t)writen(fd, send_content, strlen(send_content)); if(send_len != strlen(send_content)) { perror("Send content failed"); return ANALYSIS_ERROR; } cout << "content size ==" << content.size() << endl; vector data(content.begin(), content.end()); Mat test = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); imwrite("receive.bmp", test); return ANALYSIS_SUCCESS; } else if (method == METHOD_GET) { char header[MAX_BUFF]; sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; sprintf(header, "%sConnection: keep-alive\r\n", header); sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME); } int dot_pos = file_name.find('.'); const char* filetype; if (dot_pos < 0) filetype = MimeType::getMime("default").c_str(); else filetype = MimeType::getMime(file_name.substr(dot_pos)).c_str(); struct stat sbuf; if (stat(file_name.c_str(), &sbuf) < 0) { handleError(fd, 404, "Not Found!"); return ANALYSIS_ERROR; } sprintf(header, "%sContent-type: %s\r\n", header, filetype); // 通过Content-length返回文件大小 sprintf(header, "%sContent-length: %ld\r\n", header, sbuf.st_size); sprintf(header, "%s\r\n", header); size_t send_len = (size_t)writen(fd, header, strlen(header)); if(send_len != strlen(header)) { perror("Send header failed"); return ANALYSIS_ERROR; } int src_fd = open(file_name.c_str(), O_RDONLY, 0); char *src_addr = static_cast(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0)); close(src_fd); // 发送文件并校验完整性 send_len = writen(fd, src_addr, sbuf.st_size); if(send_len != sbuf.st_size) { perror("Send file failed"); return ANALYSIS_ERROR; } munmap(src_addr, sbuf.st_size); return ANALYSIS_SUCCESS; } else return ANALYSIS_ERROR; } void RequestData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[MAX_BUFF]; string body_buff, header_buff; body_buff += "TKeed Error"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-type: text/html\r\n"; header_buff += "Connection: close\r\n"; header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "\r\n"; sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } ================================================ FILE: old_version/old_version_0.4/requestData.h ================================================ #pragma once #include "timer.h" #include #include #include const int STATE_PARSE_URI = 1; const int STATE_PARSE_HEADERS = 2; const int STATE_RECV_BODY = 3; const int STATE_ANALYSIS = 4; const int STATE_FINISH = 5; const int MAX_BUFF = 4096; // 有请求出现但是读不到数据,可能是Request Aborted, // 或者来自网络的数据没有达到等原因, // 对这样的请求尝试超过一定的次数就抛弃 const int AGAIN_MAX_TIMES = 200; const int PARSE_URI_AGAIN = -1; const int PARSE_URI_ERROR = -2; const int PARSE_URI_SUCCESS = 0; const int PARSE_HEADER_AGAIN = -1; const int PARSE_HEADER_ERROR = -2; const int PARSE_HEADER_SUCCESS = 0; const int ANALYSIS_ERROR = -2; const int ANALYSIS_SUCCESS = 0; const int METHOD_POST = 1; const int METHOD_GET = 2; const int HTTP_10 = 1; const int HTTP_11 = 2; const int EPOLL_WAIT_TIME = 500; class MimeType { private: static void init(); static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); private: static pthread_once_t once_control; }; enum HeadersState { h_start = 0, h_key, h_colon, h_spaces_after_colon, h_value, h_CR, h_LF, h_end_CR, h_end_LF }; class TimerNode; class RequestData : public std::enable_shared_from_this { private: int againTimes; std::string path; int fd; int epollfd; // content的内容用完就清 std::string content; int method; int HTTPversion; std::string file_name; int now_read_pos; int state; int h_state; bool isfinish; bool keep_alive; std::unordered_map headers; std::weak_ptr timer; private: int parse_URI(); int parse_Headers(); int analysisRequest(); public: RequestData(); RequestData(int _epollfd, int _fd, std::string _path); ~RequestData(); void linkTimer(std::shared_ptr mtimer); void reset(); void seperateTimer(); int getFd(); void setFd(int _fd); void handleRequest(); void handleError(int fd, int err_num, std::string short_msg); }; ================================================ FILE: old_version/old_version_0.4/threadpool.cpp ================================================ #include "threadpool.h" pthread_mutex_t ThreadPool::lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ThreadPool::notify = PTHREAD_COND_INITIALIZER; std::vector ThreadPool::threads; std::vector ThreadPool::queue; int ThreadPool::thread_count = 0; int ThreadPool::queue_size = 0; int ThreadPool::head = 0; int ThreadPool::tail = 0; int ThreadPool::count = 0; int ThreadPool::shutdown = 0; int ThreadPool::started = 0; int ThreadPool::threadpool_create(int _thread_count, int _queue_size) { bool err = false; do { if(_thread_count <= 0 || _thread_count > MAX_THREADS || _queue_size <= 0 || _queue_size > MAX_QUEUE) { _thread_count = 4; _queue_size = 1024; } thread_count = 0; queue_size = _queue_size; head = tail = count = 0; shutdown = started = 0; threads.resize(_thread_count); queue.resize(_queue_size); /* Start worker threads */ for(int i = 0; i < _thread_count; ++i) { if(pthread_create(&threads[i], NULL, threadpool_thread, (void*)(0)) != 0) { //threadpool_destroy(pool, 0); return -1; } ++thread_count; ++started; } } while(false); if (err) { //threadpool_free(pool); return -1; } return 0; } void myHandler(std::shared_ptr req) { std::shared_ptr request = std::static_pointer_cast(req); request->handleRequest(); } int ThreadPool::threadpool_add(std::shared_ptr args, std::function)> fun) { int next, err = 0; if(pthread_mutex_lock(&lock) != 0) return THREADPOOL_LOCK_FAILURE; do { next = (tail + 1) % queue_size; // 队列满 if(count == queue_size) { err = THREADPOOL_QUEUE_FULL; break; } // 已关闭 if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } queue[tail].fun = fun; queue[tail].args = args; tail = next; ++count; /* pthread_cond_broadcast */ if(pthread_cond_signal(¬ify) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&lock) != 0) err = THREADPOOL_LOCK_FAILURE; return err; } int ThreadPool::threadpool_destroy(ShutDownOption shutdown_option) { printf("Thread pool destroy !\n"); int i, err = 0; if(pthread_mutex_lock(&lock) != 0) { return THREADPOOL_LOCK_FAILURE; } do { if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } shutdown = shutdown_option; if((pthread_cond_broadcast(¬ify) != 0) || (pthread_mutex_unlock(&lock) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } for(i = 0; i < thread_count; ++i) { if(pthread_join(threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); if(!err) { threadpool_free(); } return err; } int ThreadPool::threadpool_free() { if(started > 0) return -1; pthread_mutex_lock(&lock); pthread_mutex_destroy(&lock); pthread_cond_destroy(¬ify); return 0; } void *ThreadPool::threadpool_thread(void *args) { while (true) { ThreadPoolTask task; pthread_mutex_lock(&lock); while((count == 0) && (!shutdown)) { pthread_cond_wait(¬ify, &lock); } if((shutdown == immediate_shutdown) || ((shutdown == graceful_shutdown) && (count == 0))) { break; } task.fun = queue[head].fun; task.args = queue[head].args; queue[head].fun = NULL; queue[head].args.reset(); head = (head + 1) % queue_size; --count; pthread_mutex_unlock(&lock); (task.fun)(task.args); } --started; pthread_mutex_unlock(&lock); printf("This threadpool thread finishs!\n"); pthread_exit(NULL); return(NULL); } ================================================ FILE: old_version/old_version_0.4/threadpool.h ================================================ #pragma once #include "requestData.h" #include #include #include #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } ShutDownOption; struct ThreadPoolTask { std::function)> fun; std::shared_ptr args; }; /** * @struct threadpool * @brief The threadpool struct * * @var notify Condition variable to notify worker threads. * @var threads Array containing worker threads ID. * @var thread_count Number of threads * @var queue Array containing the task queue. * @var queue_size Size of the task queue. * @var head Index of the first element. * @var tail Index of the next element. * @var count Number of pending tasks * @var shutdown Flag indicating if the pool is shutting down * @var started Number of started threads */ void myHandler(std::shared_ptr req); class ThreadPool { private: static pthread_mutex_t lock; static pthread_cond_t notify; static std::vector threads; static std::vector queue; static int thread_count; static int queue_size; static int head; // tail 指向尾节点的下一节点 static int tail; static int count; static int shutdown; static int started; public: static int threadpool_create(int _thread_count, int _queue_size); static int threadpool_add(std::shared_ptr args, std::function)> fun = myHandler); static int threadpool_destroy(ShutDownOption shutdown_option = graceful_shutdown); static int threadpool_free(); static void *threadpool_thread(void *args); }; ================================================ FILE: old_version/old_version_0.4/timer.cpp ================================================ #include "timer.h" #include "epoll.h" #include #include #include #include #include #include #include using namespace std; TimerNode::TimerNode(SP_ReqData _request_data, int timeout): deleted(false), request_data(_request_data) { cout << "TimerNode()" << endl; struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } TimerNode::~TimerNode() { cout << "~TimerNode()" << endl; if (request_data) { Epoll::epoll_del(request_data->getFd()); } //request_data.reset(); // if (request_data) // { // cout << "request_data=" << request_data << endl; // delete request_data; // request_data = NULL; // } } void TimerNode::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } bool TimerNode::isvalid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = ((now.tv_sec * 1000) + (now.tv_usec / 1000)); if (temp < expired_time) { return true; } else { this->setDeleted(); return false; } } void TimerNode::clearReq() { request_data.reset(); this->setDeleted(); } void TimerNode::setDeleted() { deleted = true; } bool TimerNode::isDeleted() const { return deleted; } size_t TimerNode::getExpTime() const { return expired_time; } TimerManager::TimerManager() { } TimerManager::~TimerManager() { } void TimerManager::addTimer(SP_ReqData request_data, int timeout) { SP_TimerNode new_node(new TimerNode(request_data, timeout)); { MutexLockGuard locker(lock); TimerNodeQueue.push(new_node); } request_data->linkTimer(new_node); } void TimerManager::addTimer(SP_TimerNode timer_node) { } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请RequestData节点了,这样可以继续重复利用前面的RequestData,减少了一次delete和一次new的时间。 */ void TimerManager::handle_expired_event() { MutexLockGuard locker(lock); while (!TimerNodeQueue.empty()) { SP_TimerNode ptimer_now = TimerNodeQueue.top(); if (ptimer_now->isDeleted()) { TimerNodeQueue.pop(); //delete ptimer_now; } else if (ptimer_now->isvalid() == false) { TimerNodeQueue.pop(); //delete ptimer_now; } else { break; } } } ================================================ FILE: old_version/old_version_0.4/timer.h ================================================ #pragma once #include "requestData.h" #include "./base/nocopyable.hpp" #include "./base/mutexLock.hpp" #include #include #include #include class RequestData; class TimerNode { typedef std::shared_ptr SP_ReqData; private: bool deleted; size_t expired_time; SP_ReqData request_data; public: TimerNode(SP_ReqData _request_data, int timeout); ~TimerNode(); void update(int timeout); bool isvalid(); void clearReq(); void setDeleted(); bool isDeleted() const; size_t getExpTime() const; }; struct timerCmp { bool operator()(std::shared_ptr &a, std::shared_ptr &b) const { return a->getExpTime() > b->getExpTime(); } }; class TimerManager { typedef std::shared_ptr SP_ReqData; typedef std::shared_ptr SP_TimerNode; private: std::priority_queue, timerCmp> TimerNodeQueue; MutexLock lock; public: TimerManager(); ~TimerManager(); void addTimer(SP_ReqData request_data, int timeout); void addTimer(SP_TimerNode timer_node); void handle_expired_event(); }; ================================================ FILE: old_version/old_version_0.4/util.cpp ================================================ #include "util.h" #include #include #include #include #include ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR || errno == EAGAIN) { nwritten = 0; continue; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if(sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if(flag == -1) return -1; flag |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } ================================================ FILE: old_version/old_version_0.4/util.h ================================================ #pragma once #include ssize_t readn(int fd, void *buff, size_t n); ssize_t writen(int fd, void *buff, size_t n); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); ================================================ FILE: old_version/old_version_0.5/Makefile ================================================ SOURCE := $(wildcard *.cpp) OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE))) TARGET := myserver CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui INCLUDE:= -I./usr/local/include/opencv CFLAGS := -std=c++11 -g -Wall -O0 $(INCLUDE) -D_PTHREADS CXXFLAGS:= $(CFLAGS) .PHONY : objs clean veryclean rebuild all all : $(TARGET) objs : $(OBJS) rebuild: veryclean all clean : rm -fr *.o veryclean : clean rm -rf $(TARGET) $(TARGET) : $(OBJS) $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) ================================================ FILE: old_version/old_version_0.5/Makefile1 ================================================ cc=g++ -std=c++11 main: main.o RequestData.o epoll.o threadpool.o $(cc) -o main main.o RequestData.o epoll.o threadpool.o -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml main.o: main.cpp $(cc) -I/usr/local/include/opencv -c main.cpp RequestData.o: requestData.h RequestData.cpp $(cc) -I/usr/local/include/opencv -c RequestData.cpp epoll.o: epoll.h epoll.cpp $(cc) -I/usr/local/include/opencv -c epoll.cpp threadpool.o: threadpool.h threadpool.cpp $(cc) -I/usr/local/include/opencv -c threadpool.cpp .PHONY: clean clean: -rm main *.o .PHONY : everything objs clean veryclean rebuild everything : $(TARGET) all : $(TARGET) objs : $(OBJS) rebuild: veryclean everything clean : rm -fr *.o veryclean : clean rm -fr $(TARGET) ================================================ FILE: old_version/old_version_0.5/base/condition.hpp ================================================ #pragma once #include "nocopyable.hpp" #include "mutexLock.hpp" #include class Condition: nocopyable { public: explicit Condition(MutexLock &_mutex): mutex(_mutex) { pthread_cond_init(cond, NULL); } ~Condition() { pthread_cond_destroy(cond); } void wait() { pthread_cond_wait(&cond, mutex.get()); } void notify() { pthread_cond_signal(&cond); } void notifyAll() { pthread_cond_broadcast(&cond); } private: MutexLock &mutex; pthread_cond_t cond; } ================================================ FILE: old_version/old_version_0.5/base/mutexLock.hpp ================================================ #pragma once #include "nocopyable.hpp" #include #include class MutexLock: noncopyable { public: MutexLock() { pthread_mutex_init(&mutex, NULL); } ~MutexLock() { pthread_mutex_lock(&mutex); pthread_mutex_destroy(&mutex); } void lock() { pthread_mutex_lock(&mutex); } void unlock() { pthread_mutex_unlock(&mutex); } pthread_mutex_t *get() { return &mutex; } private: pthread_mutex_t mutex; // 友元类不受访问权限影响 private: friend class Condition; }; class MutexLockGuard: noncopyable { public: explicit MutexLockGuard(MutexLock &_mutex): mutex(_mutex) { mutex.lock(); } ~MutexLockGuard() { mutex.unlock(); } private: MutexLock &mutex; }; ================================================ FILE: old_version/old_version_0.5/base/nocopyable.hpp ================================================ #pragma once class noncopyable { protected: noncopyable() {} ~noncopyable() {} private: noncopyable(const noncopyable&); const noncopyable& operator=(const noncopyable&); }; ================================================ FILE: old_version/old_version_0.5/config.h ================================================ #pragma once ================================================ FILE: old_version/old_version_0.5/epoll.cpp ================================================ #include "epoll.h" #include "threadpool.h" #include "util.h" #include #include #include #include #include #include #include #include using namespace std; int TIMER_TIME_OUT = 500; epoll_event *Epoll::events; Epoll::SP_ReqData Epoll::fd2req[MAXFDS]; int Epoll::epoll_fd = 0; const std::string Epoll::PATH = "/"; TimerManager Epoll::timer_manager; int Epoll::epoll_init(int maxevents, int listen_num) { epoll_fd = epoll_create(listen_num + 1); if(epoll_fd == -1) return -1; //events.reset(new epoll_event[maxevents], [](epoll_event *data){delete [] data;}); events = new epoll_event[maxevents]; return 0; } // 注册新描述符 int Epoll::epoll_add(int fd, SP_ReqData request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; fd2req[fd] = request; if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); return -1; } return 0; } // 修改描述符状态 int Epoll::epoll_mod(int fd, SP_ReqData request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; fd2req[fd] = request; if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); fd2req[fd].reset(); return -1; } return 0; } // 从epoll中删除描述符 int Epoll::epoll_del(int fd, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); return -1; } fd2req[fd].reset(); return 0; } // 返回活跃事件数 void Epoll::my_epoll_wait(int listen_fd, int max_events, int timeout) { int event_count = epoll_wait(epoll_fd, events, max_events, timeout); if (event_count < 0) perror("epoll wait error"); std::vector req_data = getEventsRequest(listen_fd, event_count, PATH); if (req_data.size() > 0) { for (auto &req: req_data) { if (ThreadPool::threadpool_add(req) < 0) { // 线程池满了或者关闭了等原因,抛弃本次监听到的请求。 break; } } } timer_manager.handle_expired_event(); } #include #include using namespace std; void Epoll::acceptConnection(int listen_fd, int epoll_fd, const std::string path) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); //socklen_t client_addr_len = 0; socklen_t client_addr_len = sizeof(client_addr); int accept_fd = 0; while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0) { cout << inet_ntoa(client_addr.sin_addr) << endl; cout << ntohs(client_addr.sin_port) << endl; /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 限制服务器的最大并发连接数 if (accept_fd >= MAXFDS) { close(accept_fd); continue; } // 设为非阻塞模式 int ret = setSocketNonBlocking(accept_fd); if (ret < 0) { perror("Set non block failed!"); return; } SP_ReqData req_info(new RequestData(epoll_fd, accept_fd, path)); // 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理 __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; Epoll::epoll_add(accept_fd, req_info, _epo_event); // 新增时间信息 timer_manager.addTimer(req_info, TIMER_TIME_OUT); } //if(accept_fd == -1) // perror("accept"); } // 分发处理函数 std::vector> Epoll::getEventsRequest(int listen_fd, int events_num, const std::string path) { std::vector req_data; for(int i = 0; i < events_num; ++i) { // 获取有事件产生的描述符 int fd = events[i].data.fd; // 有事件发生的描述符为监听描述符 if(fd == listen_fd) { //cout << "This is listen_fd" << endl; acceptConnection(listen_fd, epoll_fd, path); } else if (fd < 3) { printf("fd < 3\n"); break; } else { // 排除错误事件 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)) { printf("error event\n"); if (fd2req[fd]) fd2req[fd]->seperateTimer(); fd2req[fd].reset(); continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 SP_ReqData cur_req = fd2req[fd]; if (cur_req) { if ((events[i].events & EPOLLIN) || (events[i].events & EPOLLPRI)) cur_req->enableRead(); else cur_req->enableWrite(); //printf("cur_req.use_count=%d\n", cur_req.use_count()); cur_req->seperateTimer(); req_data.push_back(cur_req); //cout << "-getEventsRequest fd==" << fd << endl; fd2req[fd].reset(); } else { cout << "SP cur_req is invalid" << endl; } } } return req_data; } void Epoll::add_timer(shared_ptr request_data, int timeout) { timer_manager.addTimer(request_data, timeout); } ================================================ FILE: old_version/old_version_0.5/epoll.h ================================================ #pragma once #include "requestData.h" #include "timer.h" #include #include #include #include class Epoll { public: typedef std::shared_ptr SP_ReqData; private: static const int MAXFDS = 1000; static epoll_event *events; static SP_ReqData fd2req[MAXFDS]; static int epoll_fd; static const std::string PATH; static TimerManager timer_manager; public: static int epoll_init(int maxevents, int listen_num); static int epoll_add(int fd, SP_ReqData request, __uint32_t events); static int epoll_mod(int fd, SP_ReqData request, __uint32_t events); static int epoll_del(int fd, __uint32_t events = (EPOLLIN | EPOLLET | EPOLLONESHOT)); static void my_epoll_wait(int listen_fd, int max_events, int timeout); static void acceptConnection(int listen_fd, int epoll_fd, const std::string path); static std::vector getEventsRequest(int listen_fd, int events_num, const std::string path); static void add_timer(SP_ReqData request_data, int timeout); }; ================================================ FILE: old_version/old_version_0.5/index.html ================================================ Hello World ! ================================================ FILE: old_version/old_version_0.5/main.cpp ================================================ #include "requestData.h" #include "epoll.h" #include "threadpool.h" #include "util.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const int MAXEVENTS = 5000; static const int LISTENQ = 1024; const int THREADPOOL_THREAD_NUM = 4; const int QUEUE_SIZE = 65535; const int PORT = 8888; const int ASK_STATIC_FILE = 1; const int ASK_IMAGE_STITCH = 2; const int TIMER_TIME_OUT = 500; void acceptConnection(int listen_fd, int epoll_fd, const string &path); int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 1024 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) return -1; // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char*)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) return -1; // 开始监听,最大等待队列长为LISTENQ if(listen(listen_fd, LISTENQ) == -1) return -1; // 无效监听描述符 if(listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } int main() { #ifndef _PTHREADS printf("_PTHREADS is not defined !\n"); #endif handle_for_sigpipe(); if (Epoll::epoll_init(MAXEVENTS, LISTENQ) < 0) { perror("epoll init failed"); return 1; } if (ThreadPool::threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE) < 0) { printf("Threadpool create failed\n"); return 1; } int listen_fd = socket_bind_listen(PORT); if (listen_fd < 0) { perror("socket bind failed"); return 1; } if (setSocketNonBlocking(listen_fd) < 0) { perror("set socket non block failed"); return 1; } shared_ptr request(new RequestData()); request->setFd(listen_fd); if (Epoll::epoll_add(listen_fd, request, EPOLLIN | EPOLLET) < 0) { perror("epoll add error"); return 1; } while (true) { //sleep(10); Epoll::my_epoll_wait(listen_fd, MAXEVENTS, -1); //ThreadPool::threadpool_destroy(); //break; } return 0; } ================================================ FILE: old_version/old_version_0.5/requestData.cpp ================================================ #include "requestData.h" #include "util.h" #include "epoll.h" #include #include #include #include #include #include #include #include #include #include using namespace cv; //test #include using namespace std; pthread_once_t MimeType::once_control = PTHREAD_ONCE_INIT; std::unordered_map MimeType::mime; void MimeType::init() { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "application/x-ico"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } std::string MimeType::getMime(const std::string &suffix) { pthread_once(&once_control, MimeType::init); if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } RequestData::RequestData(): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), isAbleRead(true), isAbleWrite(false), events(0), error(false) { cout << "RequestData()" << endl; } RequestData::RequestData(int _epollfd, int _fd, std::string _path): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), path(_path), fd(_fd), epollfd(_epollfd), isAbleRead(true), isAbleWrite(false), events(0), error(false) { cout << "RequestData()" << endl; } RequestData::~RequestData() { cout << "~RequestData()" << endl; close(fd); } void RequestData::linkTimer(shared_ptr mtimer) { // shared_ptr重载了bool, 但weak_ptr没有 //if (!timer.lock()) timer = mtimer; } int RequestData::getFd() { return fd; } void RequestData::setFd(int _fd) { fd = _fd; } void RequestData::reset() { inBuffer.clear(); file_name.clear(); path.clear(); now_read_pos = 0; state = STATE_PARSE_URI; h_state = h_start; headers.clear(); //keep_alive = false; if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void RequestData::seperateTimer() { //cout << "seperateTimer" << endl; if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void RequestData::handleRead() { do { int read_num = readn(fd, inBuffer); //printf("read_num=%d\n", read_num); if (read_num < 0) { perror("1"); error = true; handleError(fd, 400, "Bad Request"); break; } else if (read_num == 0) { // 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因 // 最可能是对端已经关闭了,统一按照对端已经关闭处理 error = true; break; } if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) break; else if (flag == PARSE_URI_ERROR) { perror("2"); error = true; handleError(fd, 400, "Bad Request"); break; } else state = STATE_PARSE_HEADERS; } if (state == STATE_PARSE_HEADERS) { int flag = this->parse_Headers(); if (flag == PARSE_HEADER_AGAIN) break; else if (flag == PARSE_HEADER_ERROR) { perror("3"); error = true; handleError(fd, 400, "Bad Request"); break; } if(method == METHOD_POST) { // POST方法准备 state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) { int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { error = true; handleError(fd, 400, "Bad Request: Lack of argument (Content-length)"); break; } if (inBuffer.size() < content_length) break; state = STATE_ANALYSIS; } if (state == STATE_ANALYSIS) { int flag = this->analysisRequest(); if (flag == ANALYSIS_SUCCESS) { state = STATE_FINISH; break; } else { error = true; break; } } } while (false); if (!error) { if (outBuffer.size() > 0) events |= EPOLLOUT; if (state == STATE_FINISH) { cout << "keep_alive=" << keep_alive << endl; if (keep_alive) { this->reset(); events |= EPOLLIN; } else return; } else events |= EPOLLIN; } } void RequestData::handleWrite() { if (!error) { if (writen(fd, outBuffer) < 0) { perror("writen"); events = 0; error = true; } else if (outBuffer.size() > 0) events |= EPOLLOUT; } } void RequestData::handleConn() { if (!error) { if (events != 0) { // 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。 // 新增时间信息 int timeout = 2000; if (keep_alive) timeout = 5 * 60 * 1000; isAbleRead = false; isAbleWrite = false; Epoll::add_timer(shared_from_this(), timeout); if ((events & EPOLLIN) && (events & EPOLLOUT)) { events = __uint32_t(0); events |= EPOLLOUT; } events |= (EPOLLET | EPOLLONESHOT); __uint32_t _events = events; events = 0; if (Epoll::epoll_mod(fd, shared_from_this(), _events) < 0) { printf("Epoll::epoll_mod error\n"); } } else if (keep_alive) { events |= (EPOLLIN | EPOLLET | EPOLLONESHOT); int timeout = 5 * 60 * 1000; isAbleRead = false; isAbleWrite = false; Epoll::add_timer(shared_from_this(), timeout); __uint32_t _events = events; events = 0; if (Epoll::epoll_mod(fd, shared_from_this(), _events) < 0) { printf("Epoll::epoll_mod error\n"); } } } } int RequestData::parse_URI() { string &str = inBuffer; // 读到完整的请求行再开始解析请求 int pos = str.find('\r', now_read_pos); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method pos = request_line.find("GET"); if (pos < 0) { pos = request_line.find("POST"); if (pos < 0) return PARSE_URI_ERROR; else method = METHOD_POST; } else method = METHOD_GET; //printf("method = %d\n", method); // filename pos = request_line.find("/", pos); if (pos < 0) return PARSE_URI_ERROR; else { int _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { file_name = request_line.substr(pos + 1, _pos - pos - 1); int __pos = file_name.find('?'); if (__pos >= 0) { file_name = file_name.substr(0, __pos); } } else file_name = "index.html"; } pos = _pos; } //cout << "file_name: " << file_name << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) return PARSE_URI_ERROR; else { if (request_line.size() - pos <= 3) return PARSE_URI_ERROR; else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPversion = HTTP_10; else if (ver == "1.1") HTTPversion = HTTP_11; else return PARSE_URI_ERROR; } } return PARSE_URI_SUCCESS; } int RequestData::parse_Headers() { string &str = inBuffer; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case h_colon: { if (str[i] == ' ') { h_state = h_spaces_after_colon; } else return PARSE_HEADER_ERROR; break; } case h_spaces_after_colon: { h_state = h_value; value_start = i; break; } case h_value: { if (str[i] == '\r') { h_state = h_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case h_CR: { if (str[i] == '\n') { h_state = h_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case h_LF: { if (str[i] == '\r') { h_state = h_end_CR; } else { key_start = i; h_state = h_key; } break; } case h_end_CR: { if (str[i] == '\n') { h_state = h_end_LF; } else return PARSE_HEADER_ERROR; break; } case h_end_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (h_state == h_end_LF) { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } int RequestData::analysisRequest() { if (method == METHOD_POST) { //get inBuffer string header; header += string("HTTP/1.1 200 OK\r\n"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { //cout << "headers" << headers["Connection"] << endl; keep_alive = true; header += string("Connection: keep-alive\r\n") + "Keep-Alive: timeout=" + to_string(5 * 60 * 1000) + "\r\n"; } int length = stoi(headers["Content-length"]); vector data(inBuffer.begin(), inBuffer.begin() + length); cout << " data.size()=" << data.size() << endl; Mat src = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); imwrite("receive.bmp", src); cout << "1" << endl; Mat res = stitch(src); cout << "2" << endl; vector data_encode; imencode(".png", res, data_encode); cout << "3" << endl; header += string("Content-length: ") + to_string(data_encode.size()) + "\r\n\r\n"; cout << "4" << endl; outBuffer += header + string(data_encode.begin(), data_encode.end()); cout << "5" << endl; inBuffer = inBuffer.substr(length); return ANALYSIS_SUCCESS; } else if (method == METHOD_GET) { string header; header += "HTTP/1.1 200 OK\r\n"; if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; header += string("Connection: keep-alive\r\n") + "Keep-Alive: timeout=" + to_string(5 * 60 * 1000) + "\r\n"; } int dot_pos = file_name.find('.'); string filetype; if (dot_pos < 0) filetype = MimeType::getMime("default"); else filetype = MimeType::getMime(file_name.substr(dot_pos)); struct stat sbuf; if (stat(file_name.c_str(), &sbuf) < 0) { header.clear(); handleError(fd, 404, "Not Found!"); return ANALYSIS_ERROR; } header += "Content-type: " + filetype + "\r\n"; header += "Content-length: " + to_string(sbuf.st_size) + "\r\n"; // 头部结束 header += "\r\n"; outBuffer += header; // size_t send_len = (size_t)writen(fd, header, strlen(header)); // if(send_len != strlen(header)) // { // perror("Send header failed"); // return ANALYSIS_ERROR; // } int src_fd = open(file_name.c_str(), O_RDONLY, 0); char *src_addr = static_cast(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0)); close(src_fd); outBuffer += src_addr; munmap(src_addr, sbuf.st_size); // send_len = writen(fd, src_addr, sbuf.st_size); // if(send_len != sbuf.st_size) // { // perror("Send file failed"); // return ANALYSIS_ERROR; // } return ANALYSIS_SUCCESS; } else return ANALYSIS_ERROR; } void RequestData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[MAX_BUFF]; string body_buff, header_buff; body_buff += "哎~出错了"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-type: text/html\r\n"; header_buff += "Connection: close\r\n"; header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "\r\n"; // 错误处理不考虑writen不完的情况 sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } void RequestData::disableReadAndWrite() { isAbleRead = false; isAbleWrite = false; } void RequestData::enableRead() { isAbleRead = true; } void RequestData::enableWrite() { isAbleWrite = true; } bool RequestData::canRead() { return isAbleRead; } bool RequestData::canWrite() { return isAbleWrite; } ================================================ FILE: old_version/old_version_0.5/requestData.h ================================================ #pragma once #include "timer.h" #include #include #include #include #include #include #include #include using namespace cv; const int STATE_PARSE_URI = 1; const int STATE_PARSE_HEADERS = 2; const int STATE_RECV_BODY = 3; const int STATE_ANALYSIS = 4; const int STATE_FINISH = 5; const int MAX_BUFF = 4096; // 有请求出现但是读不到数据,可能是Request Aborted, // 或者来自网络的数据没有达到等原因, // 对这样的请求尝试超过一定的次数就抛弃 const int AGAIN_MAX_TIMES = 200; const int PARSE_URI_AGAIN = -1; const int PARSE_URI_ERROR = -2; const int PARSE_URI_SUCCESS = 0; const int PARSE_HEADER_AGAIN = -1; const int PARSE_HEADER_ERROR = -2; const int PARSE_HEADER_SUCCESS = 0; const int ANALYSIS_ERROR = -2; const int ANALYSIS_SUCCESS = 0; const int METHOD_POST = 1; const int METHOD_GET = 2; const int HTTP_10 = 1; const int HTTP_11 = 2; const int EPOLL_WAIT_TIME = 500; class MimeType { private: static void init(); static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); private: static pthread_once_t once_control; }; enum HeadersState { h_start = 0, h_key, h_colon, h_spaces_after_colon, h_value, h_CR, h_LF, h_end_CR, h_end_LF }; class TimerNode; class RequestData : public std::enable_shared_from_this { private: std::string path; int fd; int epollfd; std::string inBuffer; std::string outBuffer; __uint32_t events; bool error; int method; int HTTPversion; std::string file_name; int now_read_pos; int state; int h_state; bool isfinish; bool keep_alive; std::unordered_map headers; std::weak_ptr timer; bool isAbleRead; bool isAbleWrite; private: int parse_URI(); int parse_Headers(); int analysisRequest(); Mat stitch(Mat &src) { return src; } public: RequestData(); RequestData(int _epollfd, int _fd, std::string _path); ~RequestData(); void linkTimer(std::shared_ptr mtimer); void reset(); void seperateTimer(); int getFd(); void setFd(int _fd); void handleRead(); void handleWrite(); void handleError(int fd, int err_num, std::string short_msg); void handleConn(); void disableReadAndWrite(); void enableRead(); void enableWrite(); bool canRead(); bool canWrite(); }; ================================================ FILE: old_version/old_version_0.5/threadpool.cpp ================================================ #include "threadpool.h" pthread_mutex_t ThreadPool::lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ThreadPool::notify = PTHREAD_COND_INITIALIZER; std::vector ThreadPool::threads; std::vector ThreadPool::queue; int ThreadPool::thread_count = 0; int ThreadPool::queue_size = 0; int ThreadPool::head = 0; int ThreadPool::tail = 0; int ThreadPool::count = 0; int ThreadPool::shutdown = 0; int ThreadPool::started = 0; int ThreadPool::threadpool_create(int _thread_count, int _queue_size) { bool err = false; do { if(_thread_count <= 0 || _thread_count > MAX_THREADS || _queue_size <= 0 || _queue_size > MAX_QUEUE) { _thread_count = 4; _queue_size = 1024; } thread_count = 0; queue_size = _queue_size; head = tail = count = 0; shutdown = started = 0; threads.resize(_thread_count); queue.resize(_queue_size); /* Start worker threads */ for(int i = 0; i < _thread_count; ++i) { if(pthread_create(&threads[i], NULL, threadpool_thread, (void*)(0)) != 0) { //threadpool_destroy(pool, 0); return -1; } ++thread_count; ++started; } } while(false); if (err) { //threadpool_free(pool); return -1; } return 0; } void myHandler(std::shared_ptr req) { std::shared_ptr request = std::static_pointer_cast(req); if (request->canWrite()) request->handleWrite(); else if (request->canRead()) request->handleRead(); request->handleConn(); } int ThreadPool::threadpool_add(std::shared_ptr args, std::function)> fun) { int next, err = 0; if(pthread_mutex_lock(&lock) != 0) return THREADPOOL_LOCK_FAILURE; do { next = (tail + 1) % queue_size; // 队列满 if(count == queue_size) { err = THREADPOOL_QUEUE_FULL; break; } // 已关闭 if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } queue[tail].fun = fun; queue[tail].args = args; tail = next; ++count; /* pthread_cond_broadcast */ if(pthread_cond_signal(¬ify) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&lock) != 0) err = THREADPOOL_LOCK_FAILURE; return err; } int ThreadPool::threadpool_destroy(ShutDownOption shutdown_option) { printf("Thread pool destroy !\n"); int i, err = 0; if(pthread_mutex_lock(&lock) != 0) { return THREADPOOL_LOCK_FAILURE; } do { if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } shutdown = shutdown_option; if((pthread_cond_broadcast(¬ify) != 0) || (pthread_mutex_unlock(&lock) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } for(i = 0; i < thread_count; ++i) { if(pthread_join(threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); if(!err) { threadpool_free(); } return err; } int ThreadPool::threadpool_free() { if(started > 0) return -1; pthread_mutex_lock(&lock); pthread_mutex_destroy(&lock); pthread_cond_destroy(¬ify); return 0; } void *ThreadPool::threadpool_thread(void *args) { while (true) { ThreadPoolTask task; pthread_mutex_lock(&lock); while((count == 0) && (!shutdown)) { pthread_cond_wait(¬ify, &lock); } if((shutdown == immediate_shutdown) || ((shutdown == graceful_shutdown) && (count == 0))) { break; } task.fun = queue[head].fun; task.args = queue[head].args; queue[head].fun = NULL; queue[head].args.reset(); head = (head + 1) % queue_size; --count; pthread_mutex_unlock(&lock); (task.fun)(task.args); } --started; pthread_mutex_unlock(&lock); printf("This threadpool thread finishs!\n"); pthread_exit(NULL); return(NULL); } ================================================ FILE: old_version/old_version_0.5/threadpool.h ================================================ #pragma once #include "requestData.h" //#include "condition.hpp" #include #include #include #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } ShutDownOption; struct ThreadPoolTask { std::function)> fun; std::shared_ptr args; }; void myHandler(std::shared_ptr req); class ThreadPool { private: static pthread_mutex_t lock; static pthread_cond_t notify; static std::vector threads; static std::vector queue; static int thread_count; static int queue_size; static int head; // tail 指向尾节点的下一节点 static int tail; static int count; static int shutdown; static int started; public: static int threadpool_create(int _thread_count, int _queue_size); static int threadpool_add(std::shared_ptr args, std::function)> fun = myHandler); static int threadpool_destroy(ShutDownOption shutdown_option = graceful_shutdown); static int threadpool_free(); static void *threadpool_thread(void *args); }; ================================================ FILE: old_version/old_version_0.5/timer.cpp ================================================ #include "timer.h" #include "epoll.h" #include #include #include #include #include #include #include using namespace std; TimerNode::TimerNode(SP_ReqData _request_data, int timeout): deleted(false), request_data(_request_data) { //cout << "TimerNode()" << endl; struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } TimerNode::~TimerNode() { //cout << "~TimerNode()" << endl; if (request_data) { Epoll::epoll_del(request_data->getFd()); } //request_data.reset(); // if (request_data) // { // cout << "request_data=" << request_data << endl; // delete request_data; // request_data = NULL; // } } void TimerNode::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } bool TimerNode::isvalid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = ((now.tv_sec * 1000) + (now.tv_usec / 1000)); if (temp < expired_time) { return true; } else { this->setDeleted(); return false; } } void TimerNode::clearReq() { request_data.reset(); this->setDeleted(); } void TimerNode::setDeleted() { deleted = true; } bool TimerNode::isDeleted() const { return deleted; } size_t TimerNode::getExpTime() const { return expired_time; } TimerManager::TimerManager() { } TimerManager::~TimerManager() { } void TimerManager::addTimer(SP_ReqData request_data, int timeout) { SP_TimerNode new_node(new TimerNode(request_data, timeout)); { MutexLockGuard locker(lock); TimerNodeQueue.push(new_node); } request_data->linkTimer(new_node); } void TimerManager::addTimer(SP_TimerNode timer_node) { } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请RequestData节点了,这样可以继续重复利用前面的RequestData,减少了一次delete和一次new的时间。 */ void TimerManager::handle_expired_event() { MutexLockGuard locker(lock); while (!TimerNodeQueue.empty()) { SP_TimerNode ptimer_now = TimerNodeQueue.top(); if (ptimer_now->isDeleted()) { TimerNodeQueue.pop(); //delete ptimer_now; } else if (ptimer_now->isvalid() == false) { TimerNodeQueue.pop(); //delete ptimer_now; } else { break; } } } ================================================ FILE: old_version/old_version_0.5/timer.h ================================================ #pragma once #include "requestData.h" #include "./base/nocopyable.hpp" #include "./base/mutexLock.hpp" #include #include #include #include class RequestData; class TimerNode { typedef std::shared_ptr SP_ReqData; private: bool deleted; size_t expired_time; SP_ReqData request_data; public: TimerNode(SP_ReqData _request_data, int timeout); ~TimerNode(); void update(int timeout); bool isvalid(); void clearReq(); void setDeleted(); bool isDeleted() const; size_t getExpTime() const; }; struct timerCmp { bool operator()(std::shared_ptr &a, std::shared_ptr &b) const { return a->getExpTime() > b->getExpTime(); } }; class TimerManager { typedef std::shared_ptr SP_ReqData; typedef std::shared_ptr SP_TimerNode; private: std::priority_queue, timerCmp> TimerNodeQueue; MutexLock lock; public: TimerManager(); ~TimerManager(); void addTimer(SP_ReqData request_data, int timeout); void addTimer(SP_TimerNode timer_node); void handle_expired_event(); }; ================================================ FILE: old_version/old_version_0.5/util.cpp ================================================ #include "util.h" #include #include #include #include #include const int MAX_BUFF = 4096; ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t readn(int fd, std::string &inBuffer) { ssize_t nread = 0; ssize_t readSum = 0; while (true) { char buff[MAX_BUFF]; if ((nread = read(fd, buff, MAX_BUFF)) < 0) { if (errno == EINTR) continue; else if (errno == EAGAIN) { return readSum; } else { perror("read error"); return -1; } } else if (nread == 0) break; //printf("before inBuffer.size() = %d\n", inBuffer.size()); //printf("nread = %d\n", nread); readSum += nread; //buff += nread; inBuffer += std::string(buff, buff + nread); //printf("after inBuffer.size() = %d\n", inBuffer.size()); } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR) { nwritten = 0; continue; } else if (errno == EAGAIN) { return writeSum; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } ssize_t writen(int fd, std::string &sbuff) { size_t nleft = sbuff.size(); ssize_t nwritten = 0; ssize_t writeSum = 0; const char *ptr = sbuff.c_str(); while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR) { nwritten = 0; continue; } else if (errno == EAGAIN) break; else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } if (writeSum == sbuff.size()) sbuff.clear(); else sbuff = sbuff.substr(writeSum); return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if(sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if(flag == -1) return -1; flag |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } ================================================ FILE: old_version/old_version_0.5/util.h ================================================ #pragma once #include #include ssize_t readn(int fd, void *buff, size_t n); ssize_t readn(int fd, std::string &inBuffer); ssize_t writen(int fd, void *buff, size_t n); ssize_t writen(int fd, std::string &sbuff); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); ================================================ FILE: old_version/old_version_0.6/AsyncLogging.cpp ================================================ #include "AsyncLogging.h" #include "LogFile.h" #include #include using namespace std; AsyncLogging::AsyncLogging(const string basename, int flushInterval) : flushInterval_(flushInterval), running_(false), basename_(basename), thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"), mutex_(), cond_(mutex_), currentBuffer_(new Buffer), nextBuffer_(new Buffer), buffers_(), latch_(1) { currentBuffer_->bzero(); nextBuffer_->bzero(); buffers_.reserve(16); } void AsyncLogging::append(const char* logline, int len) { MutexLockGuard lock(mutex_); if (currentBuffer_->avail() > len) { currentBuffer_->append(logline, len); } else { buffers_.push_back(currentBuffer_); currentBuffer_.reset(); if (nextBuffer_) { currentBuffer_ = std::move(nextBuffer_); } else { currentBuffer_.reset(new Buffer); // Rarely happens } currentBuffer_->append(logline, len); cond_.notify(); } } #include void AsyncLogging::threadFunc() { assert(running_ == true); latch_.countDown(); LogFile output(basename_); BufferPtr newBuffer1(new Buffer); BufferPtr newBuffer2(new Buffer); newBuffer1->bzero(); newBuffer2->bzero(); BufferVector buffersToWrite; buffersToWrite.reserve(16); while (running_) { assert(newBuffer1 && newBuffer1->length() == 0); assert(newBuffer2 && newBuffer2->length() == 0); assert(buffersToWrite.empty()); { MutexLockGuard lock(mutex_); if (buffers_.empty()) // unusual usage! { cond_.waitForSeconds(flushInterval_); } buffers_.push_back(currentBuffer_); currentBuffer_.reset(); currentBuffer_ = std::move(newBuffer1); buffersToWrite.swap(buffers_); if (!nextBuffer_) { nextBuffer_ = std::move(newBuffer2); } } assert(!buffersToWrite.empty()); if (buffersToWrite.size() > 25) { //char buf[256]; // snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n", // Timestamp::now().toFormattedString().c_str(), // buffersToWrite.size()-2); //fputs(buf, stderr); //output.append(buf, static_cast(strlen(buf))); buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end()); } for (size_t i = 0; i < buffersToWrite.size(); ++i) { // FIXME: use unbuffered stdio FILE ? or use ::writev ? output.append(buffersToWrite[i]->data(), buffersToWrite[i]->length()); } if (buffersToWrite.size() > 2) { // drop non-bzero-ed buffers, avoid trashing buffersToWrite.resize(2); } if (!newBuffer1) { assert(!buffersToWrite.empty()); newBuffer1 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer1->reset(); } if (!newBuffer2) { assert(!buffersToWrite.empty()); newBuffer2 = buffersToWrite.back(); buffersToWrite.pop_back(); newBuffer2->reset(); } buffersToWrite.clear(); output.flush(); } output.flush(); } ================================================ FILE: old_version/old_version_0.6/AsyncLogging.h ================================================ #pragma once #include "CountDownLatch.h" #include "MutexLock.h" #include "Thread.h" #include "LogStream.h" #include "noncopyable.h" #include #include #include // extern const int kSmallBuffer; // extern const int kLargeBuffer; // template class FixedBuffer; // template class FixedBuffer; class AsyncLogging : noncopyable { public: AsyncLogging(const std::string basename, int flushInterval = 2); ~AsyncLogging() { if (running_) { stop(); } } void append(const char* logline, int len); void start() { running_ = true; thread_.start(); latch_.wait(); } void stop() { running_ = false; cond_.notify(); thread_.join(); } private: // declare but not define, prevent compiler-synthesized functions AsyncLogging(const AsyncLogging&); // ptr_container void operator=(const AsyncLogging&); // ptr_container void threadFunc(); typedef FixedBuffer Buffer; typedef std::vector> BufferVector; typedef std::shared_ptr BufferPtr; const int flushInterval_; bool running_; std::string basename_; Thread thread_; MutexLock mutex_; Condition cond_; BufferPtr currentBuffer_; BufferPtr nextBuffer_; BufferVector buffers_; CountDownLatch latch_; }; ================================================ FILE: old_version/old_version_0.6/Condition.h ================================================ #pragma once #include "noncopyable.h" #include "MutexLock.h" #include #include #include #include #include class Condition: noncopyable { public: explicit Condition(MutexLock &_mutex): mutex(_mutex) { pthread_cond_init(&cond, NULL); } ~Condition() { pthread_cond_destroy(&cond); } void wait() { pthread_cond_wait(&cond, mutex.get()); } void notify() { pthread_cond_signal(&cond); } void notifyAll() { pthread_cond_broadcast(&cond); } bool waitForSeconds(int seconds) { struct timespec abstime; clock_gettime(CLOCK_REALTIME, &abstime); abstime.tv_sec += static_cast(seconds); return ETIMEDOUT == pthread_cond_timedwait(&cond, mutex.get(), &abstime); } private: MutexLock &mutex; pthread_cond_t cond; }; ================================================ FILE: old_version/old_version_0.6/CountDownLatch.cpp ================================================ #include "CountDownLatch.h" CountDownLatch::CountDownLatch(int count) : mutex_(), condition_(mutex_), count_(count) { } void CountDownLatch::wait() { MutexLockGuard lock(mutex_); while (count_ > 0) { condition_.wait(); } } void CountDownLatch::countDown() { MutexLockGuard lock(mutex_); --count_; if (count_ == 0) { condition_.notifyAll(); } } int CountDownLatch::getCount() const { MutexLockGuard lock(mutex_); return count_; } ================================================ FILE: old_version/old_version_0.6/CountDownLatch.h ================================================ #pragma once #include "Condition.h" #include "MutexLock.h" #include "noncopyable.h" class CountDownLatch : noncopyable { public: explicit CountDownLatch(int count); void wait(); void countDown(); int getCount() const; private: mutable MutexLock mutex_; Condition condition_; int count_; }; ================================================ FILE: old_version/old_version_0.6/CurrentThread.hpp ================================================ #pragma once #include /* namespace CurrendThread { // internal extern __thread int t_cachedTid; extern __thread char t_tidString[32]; extern __thread int t_tidStringLength; extern __thread const char* t_threadName; void cacheTid(); inline int tid() { if (__builtin_expect(t_cachedTid == 0, 0)) { cacheTid(); } return t_cachedTid; } inline const char* tidString() // for logging { return t_tidString; } inline int tidStringLength() // for logging { return t_tidStringLength; } inline const char* name() { return t_threadName; } } */ ================================================ FILE: old_version/old_version_0.6/FileUtil.cpp ================================================ #include "FileUtil.h" #include #include #include #include #include #include using namespace std; AppendFile::AppendFile(string filename) : fp_(fopen(filename.c_str(), "ae")) { setbuffer(fp_, buffer_, sizeof buffer_); } AppendFile::~AppendFile() { fclose(fp_); } void AppendFile::append(const char* logline, const size_t len) { size_t n = write(logline, len); size_t remain = len - n; while (remain > 0) { size_t x = write(logline + n, remain); if (x == 0) { int err = ferror(fp_); if (err) { fprintf(stderr, "AppendFile::append() failed !\n"); } break; } n += x; remain = len - n; } } void AppendFile::flush() { fflush(fp_); } size_t AppendFile::write(const char* logline, size_t len) { // #undef fwrite_unlocked return ::fwrite_unlocked(logline, 1, len, fp_); } ================================================ FILE: old_version/old_version_0.6/FileUtil.h ================================================ #pragma once #include "noncopyable.h" #include class AppendFile : noncopyable { public: explicit AppendFile(std::string filename); ~AppendFile(); void append(const char* logline, const size_t len); void flush(); private: size_t write(const char* logline, size_t len); FILE* fp_; char buffer_[64*1024]; }; ================================================ FILE: old_version/old_version_0.6/LogFile.cpp ================================================ #include "LogFile.h" #include "FileUtil.h" #include #include #include using namespace std; // "/logfile.log" LogFile::LogFile(const string& basename, int flushEveryN) : basename_(basename), flushEveryN_(flushEveryN), count_(0), mutex_(new MutexLock) { //assert(basename.find('/') >= 0); file_.reset(new AppendFile(basename)); } LogFile::~LogFile() { } void LogFile::append(const char* logline, int len) { MutexLockGuard lock(*mutex_); append_unlocked(logline, len); } void LogFile::flush() { MutexLockGuard lock(*mutex_); file_->flush(); } void LogFile::append_unlocked(const char* logline, int len) { file_->append(logline, len); ++count_; if (count_ >= flushEveryN_) { count_ = 0; file_->flush(); } } ================================================ FILE: old_version/old_version_0.6/LogFile.h ================================================ #pragma once #include "FileUtil.h" #include "MutexLock.h" #include "noncopyable.h" #include #include class LogFile : noncopyable { public: LogFile(const std::string& basename, int flushEveryN = 1024); ~LogFile(); void append(const char* logline, int len); void flush(); bool rollFile(); private: void append_unlocked(const char* logline, int len); const std::string basename_; const int flushEveryN_; int count_; std::unique_ptr mutex_; std::unique_ptr file_; }; ================================================ FILE: old_version/old_version_0.6/LogStream.cpp ================================================ #include "LogStream.h" #include #include #include #include #include #include const char digits[] = "9876543210123456789"; const char* zero = digits + 9; const char digitsHex[] = "0123456789ABCDEF"; // Efficient Integer to String Conversions, by Matthew Wilson. template size_t convert(char buf[], T value) { T i = value; char* p = buf; do { int lsd = static_cast(i % 10); i /= 10; *p++ = zero[lsd]; } while (i != 0); if (value < 0) { *p++ = '-'; } *p = '\0'; std::reverse(buf, p); return p - buf; } template class FixedBuffer; template class FixedBuffer; template void LogStream::formatInteger(T v) { if (buffer_.avail() >= kMaxNumericSize) { size_t len = convert(buffer_.current(), v); buffer_.add(len); } } LogStream& LogStream::operator<<(short v) { *this << static_cast(v); return *this; } LogStream& LogStream::operator<<(unsigned short v) { *this << static_cast(v); return *this; } LogStream& LogStream::operator<<(int v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(unsigned int v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(unsigned long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(long long v) { formatInteger(v); return *this; } LogStream& LogStream::operator<<(unsigned long long v) { formatInteger(v); return *this; } // FIXME: replace this with Grisu3 by Florian Loitsch. LogStream& LogStream::operator<<(double v) { if (buffer_.avail() >= kMaxNumericSize) { int len = snprintf(buffer_.current(), kMaxNumericSize, "%.12g", v); buffer_.add(len); } return *this; } ================================================ FILE: old_version/old_version_0.6/LogStream.h ================================================ #pragma once #include #include #include #include "noncopyable.h" //#include "AsyncLogging.h" class AsyncLogging; const int kSmallBuffer = 4000; const int kLargeBuffer = 4000*1000; template class FixedBuffer :noncopyable { public: FixedBuffer() : cur_(data_) { } ~FixedBuffer() { } void append(const char* buf, size_t len) { if (avail() > len) { memcpy(cur_, buf, len); cur_ += len; } } const char* data() const { return data_; } int length() const { return static_cast(cur_ - data_); } // write to data_ directly char* current() { return cur_; } int avail() const { return static_cast(end() - cur_); } void add(size_t len) { cur_ += len; } void reset() { cur_ = data_; } void bzero() { memset(data_, 0, sizeof data_); } private: const char* end() const { return data_ + sizeof data_; } // Must be outline function for cookies. char data_[SIZE]; char* cur_; }; class LogStream : noncopyable { typedef LogStream self; public: typedef FixedBuffer Buffer; self& operator<<(bool v) { buffer_.append(v ? "1" : "0", 1); return *this; } self& operator<<(short); self& operator<<(unsigned short); self& operator<<(int); self& operator<<(unsigned int); self& operator<<(long); self& operator<<(unsigned long); self& operator<<(long long); self& operator<<(unsigned long long); self& operator<<(const void*); self& operator<<(float v) { *this << static_cast(v); return *this; } self& operator<<(double); // self& operator<<(long double); self& operator<<(char v) { buffer_.append(&v, 1); return *this; } self& operator<<(const char* str) { if (str) { buffer_.append(str, strlen(str)); } else { buffer_.append("(null)", 6); } return *this; } self& operator<<(const unsigned char* str) { return operator<<(reinterpret_cast(str)); } self& operator<<(const std::string& v) { buffer_.append(v.c_str(), v.size()); return *this; } void append(const char* data, int len) { buffer_.append(data, len); } const Buffer& buffer() const { return buffer_; } void resetBuffer() { buffer_.reset(); } private: void staticCheck(); template void formatInteger(T); Buffer buffer_; static const int kMaxNumericSize = 32; }; ================================================ FILE: old_version/old_version_0.6/Logging.cpp ================================================ #include "Logging.h" #include "CurrentThread.hpp" #include "Thread.h" #include #include "AsyncLogging.h" #include using namespace std; static pthread_once_t once_control_ = PTHREAD_ONCE_INIT; static AsyncLogging *AsyncLogger_; void once_init() { AsyncLogger_ = new AsyncLogging(std::string("/linya_web_server.log")); AsyncLogger_->start(); } void output(const char* msg, int len) { pthread_once(&once_control_, once_init); AsyncLogger_->append(msg, len); } Logger::Impl::Impl(const char *fileName, int line) : stream_(), basename_(fileName), line_(line) { //formatTime(); //CurrentThread::tid(); //stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength()); } void Logger::Impl::formatTime() { // int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch(); // time_t seconds = static_cast(microSecondsSinceEpoch / Timestamp::kMicroSecondsPerSecond); // int microseconds = static_cast(microSecondsSinceEpoch % Timestamp::kMicroSecondsPerSecond); // if (seconds != t_lastSecond) // { // t_lastSecond = seconds; // struct tm tm_time; // if (g_logTimeZone.valid()) // { // tm_time = g_logTimeZone.toLocalTime(seconds); // } // else // { // ::gmtime_r(&seconds, &tm_time); // FIXME TimeZone::fromUtcTime // } // int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d", // tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday, // tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); // assert(len == 17); (void)len; // } // if (g_logTimeZone.valid()) // { // Fmt us(".%06d ", microseconds); // assert(us.length() == 8); // stream_ << T(t_time, 17) << T(us.data(), 8); // } // else // { // Fmt us(".%06dZ ", microseconds); // assert(us.length() == 9); // stream_ << T(t_time, 17) << T(us.data(), 9); // } } Logger::Logger(const char *fileName, int line) : impl_(fileName, line) { } Logger::~Logger() { impl_.stream_ << " - " << impl_.basename_ << ':' << impl_.line_ << '\n'; const LogStream::Buffer& buf(stream().buffer()); output(buf.data(), buf.length()); } ================================================ FILE: old_version/old_version_0.6/Logging.h ================================================ #pragma once #include "LogStream.h" #include #include #include #include class AsyncLogging; class Logger { public: ~Logger(); Logger(const char *fileName, int line); LogStream& stream() { return impl_.stream_; } private: class Impl { public: Impl(const char *fileName, int line); void formatTime(); LogStream stream_; int line_; std::string basename_; }; Impl impl_; }; #define LOG Logger(__FILE__, __LINE__).stream() ================================================ FILE: old_version/old_version_0.6/Makefile ================================================ SOURCE := $(wildcard *.cpp) OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE))) TARGET := myserver CC := g++ LIBS := -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui INCLUDE:= -I./usr/local/include/opencv CFLAGS := -std=c++11 -g -Wall -O0 $(INCLUDE) -D_PTHREADS CXXFLAGS:= $(CFLAGS) .PHONY : objs clean veryclean rebuild all all : $(TARGET) objs : $(OBJS) rebuild: veryclean all clean : rm -fr *.o veryclean : clean rm -rf $(TARGET) $(TARGET) : $(OBJS) $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) ================================================ FILE: old_version/old_version_0.6/Makefile1 ================================================ cc=g++ -std=c++11 main: main.o RequestData.o epoll.o threadpool.o $(cc) -o main main.o RequestData.o epoll.o threadpool.o -lpthread -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml main.o: main.cpp $(cc) -I/usr/local/include/opencv -c main.cpp RequestData.o: requestData.h RequestData.cpp $(cc) -I/usr/local/include/opencv -c RequestData.cpp epoll.o: epoll.h epoll.cpp $(cc) -I/usr/local/include/opencv -c epoll.cpp threadpool.o: threadpool.h threadpool.cpp $(cc) -I/usr/local/include/opencv -c threadpool.cpp .PHONY: clean clean: -rm main *.o .PHONY : everything objs clean veryclean rebuild everything : $(TARGET) all : $(TARGET) objs : $(OBJS) rebuild: veryclean everything clean : rm -fr *.o veryclean : clean rm -fr $(TARGET) ================================================ FILE: old_version/old_version_0.6/MutexLock.h ================================================ #pragma once #include "noncopyable.h" #include #include class MutexLock: noncopyable { public: MutexLock() { pthread_mutex_init(&mutex, NULL); } ~MutexLock() { pthread_mutex_lock(&mutex); pthread_mutex_destroy(&mutex); } void lock() { pthread_mutex_lock(&mutex); } void unlock() { pthread_mutex_unlock(&mutex); } pthread_mutex_t *get() { return &mutex; } private: pthread_mutex_t mutex; // 友元类不受访问权限影响 private: friend class Condition; }; class MutexLockGuard: noncopyable { public: explicit MutexLockGuard(MutexLock &_mutex): mutex(_mutex) { mutex.lock(); } ~MutexLockGuard() { mutex.unlock(); } private: MutexLock &mutex; }; ================================================ FILE: old_version/old_version_0.6/Thread.cpp ================================================ #include "Thread.h" #include "CurrentThread.hpp" //#include "Logging.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace CurrentThread { // internal // extern __thread int t_cachedTid; // extern __thread char t_tidString[32]; // extern __thread int t_tidStringLength; // extern __thread const char* t_threadName; __thread int t_cachedTid; __thread char t_tidString[32]; __thread int t_tidStringLength; __thread const char* t_threadName; void cacheTid(); inline int tid() { if (__builtin_expect(t_cachedTid == 0, 0)) { cacheTid(); } return t_cachedTid; } inline const char* tidString() // for logging { return t_tidString; } inline int tidStringLength() // for logging { return t_tidStringLength; } inline const char* name() { return t_threadName; } } pid_t gettid() { return static_cast(::syscall(SYS_gettid)); } // 为了在线程中保留name,tid这些数据 struct ThreadData { typedef Thread::ThreadFunc ThreadFunc; ThreadFunc func_; string name_; pid_t* tid_; CountDownLatch* latch_; ThreadData(const ThreadFunc& func, const string& name, pid_t* tid, CountDownLatch* latch) : func_(func), name_(name), tid_(tid), latch_(latch) { } void runInThread() { *tid_ = CurrentThread::tid(); tid_ = NULL; latch_->countDown(); latch_ = NULL; CurrentThread::t_threadName = name_.empty() ? "Thread" : name_.c_str(); prctl(PR_SET_NAME, CurrentThread::t_threadName); func_(); CurrentThread::t_threadName = "finished"; } }; void* startThread(void* obj) { ThreadData* data = static_cast(obj); data->runInThread(); delete data; return NULL; } void CurrentThread::cacheTid() { if (t_cachedTid == 0) { t_cachedTid = gettid(); t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid); } } Thread::Thread(const ThreadFunc& func, const string& n) : started_(false), joined_(false), pthreadId_(0), tid_(0), func_(func), name_(n), latch_(1) { setDefaultName(); } Thread::~Thread() { if (started_ && !joined_) { pthread_detach(pthreadId_); } } void Thread::setDefaultName() { if (name_.empty()) { char buf[32]; snprintf(buf, sizeof buf, "Thread"); name_ = buf; } } void Thread::start() { assert(!started_); started_ = true; // FIXME: move(func_) ThreadData* data = new ThreadData(func_, name_, &tid_, &latch_); if (pthread_create(&pthreadId_, NULL, &startThread, data)) { started_ = false; delete data; } else { latch_.wait(); assert(tid_ > 0); } } int Thread::join() { assert(started_); assert(!joined_); joined_ = true; return pthread_join(pthreadId_, NULL); } ================================================ FILE: old_version/old_version_0.6/Thread.h ================================================ #pragma once #include "CountDownLatch.h" #include #include "noncopyable.h" #include #include #include class Thread : noncopyable { public: typedef std::function ThreadFunc; explicit Thread(const ThreadFunc&, const std::string& name = std::string()); ~Thread(); void start(); int join(); bool started() const { return started_; } pid_t tid() const { return tid_; } const std::string& name() const { return name_; } private: void setDefaultName(); bool started_; bool joined_; pthread_t pthreadId_; pid_t tid_; ThreadFunc func_; std::string name_; CountDownLatch latch_; }; ================================================ FILE: old_version/old_version_0.6/config.h ================================================ #pragma once ================================================ FILE: old_version/old_version_0.6/epoll.cpp ================================================ #include "epoll.h" #include "threadpool.h" #include "util.h" #include #include #include #include #include #include #include #include using namespace std; int TIMER_TIME_OUT = 500; epoll_event *Epoll::events; Epoll::SP_ReqData Epoll::fd2req[MAXFDS]; int Epoll::epoll_fd = 0; const std::string Epoll::PATH = "/"; TimerManager Epoll::timer_manager; int Epoll::epoll_init(int maxevents, int listen_num) { epoll_fd = epoll_create(listen_num + 1); if(epoll_fd == -1) return -1; //events.reset(new epoll_event[maxevents], [](epoll_event *data){delete [] data;}); events = new epoll_event[maxevents]; return 0; } // 注册新描述符 int Epoll::epoll_add(int fd, SP_ReqData request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; fd2req[fd] = request; if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) { perror("epoll_add error"); return -1; } return 0; } // 修改描述符状态 int Epoll::epoll_mod(int fd, SP_ReqData request, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; fd2req[fd] = request; if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) { perror("epoll_mod error"); fd2req[fd].reset(); return -1; } return 0; } // 从epoll中删除描述符 int Epoll::epoll_del(int fd, __uint32_t events) { struct epoll_event event; event.data.fd = fd; event.events = events; if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &event) < 0) { perror("epoll_del error"); return -1; } fd2req[fd].reset(); return 0; } // 返回活跃事件数 void Epoll::my_epoll_wait(int listen_fd, int max_events, int timeout) { int event_count = epoll_wait(epoll_fd, events, max_events, timeout); if (event_count < 0) perror("epoll wait error"); std::vector req_data = getEventsRequest(listen_fd, event_count, PATH); if (req_data.size() > 0) { for (auto &req: req_data) { if (ThreadPool::threadpool_add(req) < 0) { // 线程池满了或者关闭了等原因,抛弃本次监听到的请求。 break; } } } timer_manager.handle_expired_event(); } #include #include using namespace std; void Epoll::acceptConnection(int listen_fd, int epoll_fd, const std::string path) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(struct sockaddr_in)); //socklen_t client_addr_len = 0; socklen_t client_addr_len = sizeof(client_addr); int accept_fd = 0; while((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len)) > 0) { cout << inet_ntoa(client_addr.sin_addr) << endl; cout << ntohs(client_addr.sin_port) << endl; /* // TCP的保活机制默认是关闭的 int optval = 0; socklen_t len_optval = 4; getsockopt(accept_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, &len_optval); cout << "optval ==" << optval << endl; */ // 限制服务器的最大并发连接数 if (accept_fd >= MAXFDS) { close(accept_fd); continue; } // 设为非阻塞模式 int ret = setSocketNonBlocking(accept_fd); if (ret < 0) { perror("Set non block failed!"); return; } SP_ReqData req_info(new RequestData(epoll_fd, accept_fd, path)); // 文件描述符可以读,边缘触发(Edge Triggered)模式,保证一个socket连接在任一时刻只被一个线程处理 __uint32_t _epo_event = EPOLLIN | EPOLLET | EPOLLONESHOT; Epoll::epoll_add(accept_fd, req_info, _epo_event); // 新增时间信息 timer_manager.addTimer(req_info, TIMER_TIME_OUT); } //if(accept_fd == -1) // perror("accept"); } // 分发处理函数 std::vector> Epoll::getEventsRequest(int listen_fd, int events_num, const std::string path) { std::vector req_data; for(int i = 0; i < events_num; ++i) { // 获取有事件产生的描述符 int fd = events[i].data.fd; // 有事件发生的描述符为监听描述符 if(fd == listen_fd) { //cout << "This is listen_fd" << endl; acceptConnection(listen_fd, epoll_fd, path); } else if (fd < 3) { printf("fd < 3\n"); break; } else { // 排除错误事件 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)) { printf("error event\n"); if (fd2req[fd]) fd2req[fd]->seperateTimer(); fd2req[fd].reset(); continue; } // 将请求任务加入到线程池中 // 加入线程池之前将Timer和request分离 SP_ReqData cur_req = fd2req[fd]; if (cur_req) { // 重新组织 仿照 void Channel::handleEventWithGuard if ((events[i].events & EPOLLIN) || (events[i].events & EPOLLPRI)) cur_req->enableRead(); if (events[i].events & EPOLLOUT) cur_req->enableWrite(); //printf("cur_req.use_count=%d\n", cur_req.use_count()); cur_req->seperateTimer(); req_data.push_back(cur_req); //cout << "-getEventsRequest fd==" << fd << endl; fd2req[fd].reset(); } else { cout << "SP cur_req is invalid" << endl; } } } return req_data; } void Epoll::add_timer(shared_ptr request_data, int timeout) { timer_manager.addTimer(request_data, timeout); } ================================================ FILE: old_version/old_version_0.6/epoll.h ================================================ #pragma once #include "requestData.h" #include "timer.h" #include #include #include #include class Epoll { public: typedef std::shared_ptr SP_ReqData; private: static const int MAXFDS = 1000; static epoll_event *events; static SP_ReqData fd2req[MAXFDS]; static int epoll_fd; static const std::string PATH; static TimerManager timer_manager; public: static int epoll_init(int maxevents, int listen_num); static int epoll_add(int fd, SP_ReqData request, __uint32_t events); static int epoll_mod(int fd, SP_ReqData request, __uint32_t events); static int epoll_del(int fd, __uint32_t events = (EPOLLIN | EPOLLET | EPOLLONESHOT)); static void my_epoll_wait(int listen_fd, int max_events, int timeout); static void acceptConnection(int listen_fd, int epoll_fd, const std::string path); static std::vector getEventsRequest(int listen_fd, int events_num, const std::string path); static void add_timer(SP_ReqData request_data, int timeout); }; ================================================ FILE: old_version/old_version_0.6/index.html ================================================ Hello World ! ================================================ FILE: old_version/old_version_0.6/main.cpp ================================================ #include "requestData.h" #include "epoll.h" #include "threadpool.h" #include "util.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "Logging.h" using namespace std; static const int MAXEVENTS = 5000; static const int LISTENQ = 1024; const int THREADPOOL_THREAD_NUM = 4; const int QUEUE_SIZE = 65535; const int PORT = 8888; const int ASK_STATIC_FILE = 1; const int ASK_IMAGE_STITCH = 2; const int TIMER_TIME_OUT = 500; void acceptConnection(int listen_fd, int epoll_fd, const string &path); int socket_bind_listen(int port) { // 检查port值,取正确区间范围 if (port < 1024 || port > 65535) return -1; // 创建socket(IPv4 + TCP),返回监听描述符 int listen_fd = 0; if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; // 消除bind时"Address already in use"错误 int optval = 1; if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) return -1; // 设置服务器IP和Port,和监听描述副绑定 struct sockaddr_in server_addr; bzero((char*)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((unsigned short)port); if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) return -1; // 开始监听,最大等待队列长为LISTENQ if(listen(listen_fd, LISTENQ) == -1) return -1; // 无效监听描述符 if(listen_fd == -1) { close(listen_fd); return -1; } return listen_fd; } int main() { LOG << "yingyingying"; LOG << 654 << 3.2 << 0 << string("fg") << true; #ifndef _PTHREADS LOG << "_PTHREADS is not defined !"; #endif handle_for_sigpipe(); if (Epoll::epoll_init(MAXEVENTS, LISTENQ) < 0) { perror("epoll init failed"); return 1; } if (ThreadPool::threadpool_create(THREADPOOL_THREAD_NUM, QUEUE_SIZE) < 0) { printf("Threadpool create failed\n"); return 1; } int listen_fd = socket_bind_listen(PORT); if (listen_fd < 0) { perror("socket bind failed"); return 1; } if (setSocketNonBlocking(listen_fd) < 0) { perror("set socket non block failed"); return 1; } shared_ptr request(new RequestData()); request->setFd(listen_fd); if (Epoll::epoll_add(listen_fd, request, EPOLLIN | EPOLLET) < 0) { perror("epoll add error"); return 1; } while (true) { //sleep(10); Epoll::my_epoll_wait(listen_fd, MAXEVENTS, -1); //ThreadPool::threadpool_destroy(); //break; } return 0; } ================================================ FILE: old_version/old_version_0.6/noncopyable.h ================================================ #pragma once class noncopyable { protected: noncopyable() {} ~noncopyable() {} private: noncopyable(const noncopyable&); const noncopyable& operator=(const noncopyable&); }; ================================================ FILE: old_version/old_version_0.6/requestData.cpp ================================================ #include "requestData.h" #include "util.h" #include "epoll.h" #include #include #include #include #include #include #include #include #include #include using namespace cv; //test #include using namespace std; pthread_once_t MimeType::once_control = PTHREAD_ONCE_INIT; std::unordered_map MimeType::mime; void MimeType::init() { mime[".html"] = "text/html"; mime[".avi"] = "video/x-msvideo"; mime[".bmp"] = "image/bmp"; mime[".c"] = "text/plain"; mime[".doc"] = "application/msword"; mime[".gif"] = "image/gif"; mime[".gz"] = "application/x-gzip"; mime[".htm"] = "text/html"; mime[".ico"] = "application/x-ico"; mime[".jpg"] = "image/jpeg"; mime[".png"] = "image/png"; mime[".txt"] = "text/plain"; mime[".mp3"] = "audio/mp3"; mime["default"] = "text/html"; } std::string MimeType::getMime(const std::string &suffix) { pthread_once(&once_control, MimeType::init); if (mime.find(suffix) == mime.end()) return mime["default"]; else return mime[suffix]; } RequestData::RequestData(): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), isAbleRead(true), isAbleWrite(false), events(0), error(false) { cout << "RequestData()" << endl; } RequestData::RequestData(int _epollfd, int _fd, std::string _path): now_read_pos(0), state(STATE_PARSE_URI), h_state(h_start), keep_alive(false), path(_path), fd(_fd), epollfd(_epollfd), isAbleRead(true), isAbleWrite(false), events(0), error(false) { cout << "RequestData()" << endl; } RequestData::~RequestData() { cout << "~RequestData()" << endl; close(fd); } void RequestData::linkTimer(shared_ptr mtimer) { // shared_ptr重载了bool, 但weak_ptr没有 //if (!timer.lock()) timer = mtimer; } int RequestData::getFd() { return fd; } void RequestData::setFd(int _fd) { fd = _fd; } void RequestData::reset() { inBuffer.clear(); file_name.clear(); path.clear(); now_read_pos = 0; state = STATE_PARSE_URI; h_state = h_start; headers.clear(); //keep_alive = false; if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void RequestData::seperateTimer() { //cout << "seperateTimer" << endl; if (timer.lock()) { shared_ptr my_timer(timer.lock()); my_timer->clearReq(); timer.reset(); } } void RequestData::handleRead() { do { int read_num = readn(fd, inBuffer); //printf("read_num=%d\n", read_num); if (read_num < 0) { perror("1"); error = true; handleError(fd, 400, "Bad Request"); break; } else if (read_num == 0) { // 有请求出现但是读不到数据,可能是Request Aborted,或者来自网络的数据没有达到等原因 // 最可能是对端已经关闭了,统一按照对端已经关闭处理 error = true; break; } if (state == STATE_PARSE_URI) { int flag = this->parse_URI(); if (flag == PARSE_URI_AGAIN) break; else if (flag == PARSE_URI_ERROR) { perror("2"); error = true; handleError(fd, 400, "Bad Request"); break; } else state = STATE_PARSE_HEADERS; } if (state == STATE_PARSE_HEADERS) { int flag = this->parse_Headers(); if (flag == PARSE_HEADER_AGAIN) break; else if (flag == PARSE_HEADER_ERROR) { perror("3"); error = true; handleError(fd, 400, "Bad Request"); break; } if(method == METHOD_POST) { // POST方法准备 state = STATE_RECV_BODY; } else { state = STATE_ANALYSIS; } } if (state == STATE_RECV_BODY) { int content_length = -1; if (headers.find("Content-length") != headers.end()) { content_length = stoi(headers["Content-length"]); } else { error = true; handleError(fd, 400, "Bad Request: Lack of argument (Content-length)"); break; } if (inBuffer.size() < content_length) break; state = STATE_ANALYSIS; } if (state == STATE_ANALYSIS) { int flag = this->analysisRequest(); if (flag == ANALYSIS_SUCCESS) { state = STATE_FINISH; break; } else { error = true; break; } } } while (false); if (!error) { if (outBuffer.size() > 0) events |= EPOLLOUT; if (state == STATE_FINISH) { cout << "keep_alive=" << keep_alive << endl; if (keep_alive) { this->reset(); events |= EPOLLIN; } else return; } else events |= EPOLLIN; } } void RequestData::handleWrite() { if (!error) { if (writen(fd, outBuffer) < 0) { perror("writen"); events = 0; error = true; } else if (outBuffer.size() > 0) events |= EPOLLOUT; } } void RequestData::handleConn() { if (!error) { if (events != 0) { // 一定要先加时间信息,否则可能会出现刚加进去,下个in触发来了,然后分离失败后,又加入队列,最后超时被删,然后正在线程中进行的任务出错,double free错误。 // 新增时间信息 int timeout = 2000; if (keep_alive) timeout = 5 * 60 * 1000; isAbleRead = false; isAbleWrite = false; Epoll::add_timer(shared_from_this(), timeout); if ((events & EPOLLIN) && (events & EPOLLOUT)) { events = __uint32_t(0); events |= EPOLLOUT; } events |= (EPOLLET | EPOLLONESHOT); __uint32_t _events = events; events = 0; if (Epoll::epoll_mod(fd, shared_from_this(), _events) < 0) { printf("Epoll::epoll_mod error\n"); } } else if (keep_alive) { events |= (EPOLLIN | EPOLLET | EPOLLONESHOT); int timeout = 5 * 60 * 1000; isAbleRead = false; isAbleWrite = false; Epoll::add_timer(shared_from_this(), timeout); __uint32_t _events = events; events = 0; if (Epoll::epoll_mod(fd, shared_from_this(), _events) < 0) { printf("Epoll::epoll_mod error\n"); } } } } int RequestData::parse_URI() { string &str = inBuffer; // 读到完整的请求行再开始解析请求 int pos = str.find('\r', now_read_pos); if (pos < 0) { return PARSE_URI_AGAIN; } // 去掉请求行所占的空间,节省空间 string request_line = str.substr(0, pos); if (str.size() > pos + 1) str = str.substr(pos + 1); else str.clear(); // Method pos = request_line.find("GET"); if (pos < 0) { pos = request_line.find("POST"); if (pos < 0) return PARSE_URI_ERROR; else method = METHOD_POST; } else method = METHOD_GET; //printf("method = %d\n", method); // filename pos = request_line.find("/", pos); if (pos < 0) return PARSE_URI_ERROR; else { int _pos = request_line.find(' ', pos); if (_pos < 0) return PARSE_URI_ERROR; else { if (_pos - pos > 1) { file_name = request_line.substr(pos + 1, _pos - pos - 1); int __pos = file_name.find('?'); if (__pos >= 0) { file_name = file_name.substr(0, __pos); } } else file_name = "index.html"; } pos = _pos; } //cout << "file_name: " << file_name << endl; // HTTP 版本号 pos = request_line.find("/", pos); if (pos < 0) return PARSE_URI_ERROR; else { if (request_line.size() - pos <= 3) return PARSE_URI_ERROR; else { string ver = request_line.substr(pos + 1, 3); if (ver == "1.0") HTTPversion = HTTP_10; else if (ver == "1.1") HTTPversion = HTTP_11; else return PARSE_URI_ERROR; } } return PARSE_URI_SUCCESS; } int RequestData::parse_Headers() { string &str = inBuffer; int key_start = -1, key_end = -1, value_start = -1, value_end = -1; int now_read_line_begin = 0; bool notFinish = true; for (int i = 0; i < str.size() && notFinish; ++i) { switch(h_state) { case h_start: { if (str[i] == '\n' || str[i] == '\r') break; h_state = h_key; key_start = i; now_read_line_begin = i; break; } case h_key: { if (str[i] == ':') { key_end = i; if (key_end - key_start <= 0) return PARSE_HEADER_ERROR; h_state = h_colon; } else if (str[i] == '\n' || str[i] == '\r') return PARSE_HEADER_ERROR; break; } case h_colon: { if (str[i] == ' ') { h_state = h_spaces_after_colon; } else return PARSE_HEADER_ERROR; break; } case h_spaces_after_colon: { h_state = h_value; value_start = i; break; } case h_value: { if (str[i] == '\r') { h_state = h_CR; value_end = i; if (value_end - value_start <= 0) return PARSE_HEADER_ERROR; } else if (i - value_start > 255) return PARSE_HEADER_ERROR; break; } case h_CR: { if (str[i] == '\n') { h_state = h_LF; string key(str.begin() + key_start, str.begin() + key_end); string value(str.begin() + value_start, str.begin() + value_end); headers[key] = value; now_read_line_begin = i; } else return PARSE_HEADER_ERROR; break; } case h_LF: { if (str[i] == '\r') { h_state = h_end_CR; } else { key_start = i; h_state = h_key; } break; } case h_end_CR: { if (str[i] == '\n') { h_state = h_end_LF; } else return PARSE_HEADER_ERROR; break; } case h_end_LF: { notFinish = false; key_start = i; now_read_line_begin = i; break; } } } if (h_state == h_end_LF) { str = str.substr(now_read_line_begin); return PARSE_HEADER_SUCCESS; } str = str.substr(now_read_line_begin); return PARSE_HEADER_AGAIN; } int RequestData::analysisRequest() { if (method == METHOD_POST) { //get inBuffer string header; header += string("HTTP/1.1 200 OK\r\n"); if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { //cout << "headers" << headers["Connection"] << endl; keep_alive = true; header += string("Connection: keep-alive\r\n") + "Keep-Alive: timeout=" + to_string(5 * 60 * 1000) + "\r\n"; } int length = stoi(headers["Content-length"]); vector data(inBuffer.begin(), inBuffer.begin() + length); cout << " data.size()=" << data.size() << endl; Mat src = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR); imwrite("receive.bmp", src); cout << "1" << endl; Mat res = stitch(src); cout << "2" << endl; vector data_encode; imencode(".png", res, data_encode); cout << "3" << endl; header += string("Content-length: ") + to_string(data_encode.size()) + "\r\n\r\n"; cout << "4" << endl; outBuffer += header + string(data_encode.begin(), data_encode.end()); cout << "5" << endl; inBuffer = inBuffer.substr(length); return ANALYSIS_SUCCESS; } else if (method == METHOD_GET) { string header; header += "HTTP/1.1 200 OK\r\n"; if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive") { keep_alive = true; header += string("Connection: keep-alive\r\n") + "Keep-Alive: timeout=" + to_string(5 * 60 * 1000) + "\r\n"; } int dot_pos = file_name.find('.'); string filetype; if (dot_pos < 0) filetype = MimeType::getMime("default"); else filetype = MimeType::getMime(file_name.substr(dot_pos)); struct stat sbuf; if (stat(file_name.c_str(), &sbuf) < 0) { header.clear(); handleError(fd, 404, "Not Found!"); return ANALYSIS_ERROR; } header += "Content-type: " + filetype + "\r\n"; header += "Content-length: " + to_string(sbuf.st_size) + "\r\n"; // 头部结束 header += "\r\n"; outBuffer += header; // size_t send_len = (size_t)writen(fd, header, strlen(header)); // if(send_len != strlen(header)) // { // perror("Send header failed"); // return ANALYSIS_ERROR; // } int src_fd = open(file_name.c_str(), O_RDONLY, 0); char *src_addr = static_cast(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0)); close(src_fd); outBuffer += src_addr; munmap(src_addr, sbuf.st_size); // send_len = writen(fd, src_addr, sbuf.st_size); // if(send_len != sbuf.st_size) // { // perror("Send file failed"); // return ANALYSIS_ERROR; // } return ANALYSIS_SUCCESS; } else return ANALYSIS_ERROR; } void RequestData::handleError(int fd, int err_num, string short_msg) { short_msg = " " + short_msg; char send_buff[MAX_BUFF]; string body_buff, header_buff; body_buff += "哎~出错了"; body_buff += ""; body_buff += to_string(err_num) + short_msg; body_buff += "
LinYa's Web Server\n"; header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n"; header_buff += "Content-type: text/html\r\n"; header_buff += "Connection: close\r\n"; header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n"; header_buff += "\r\n"; // 错误处理不考虑writen不完的情况 sprintf(send_buff, "%s", header_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); sprintf(send_buff, "%s", body_buff.c_str()); writen(fd, send_buff, strlen(send_buff)); } void RequestData::disableReadAndWrite() { isAbleRead = false; isAbleWrite = false; } void RequestData::enableRead() { isAbleRead = true; } void RequestData::enableWrite() { isAbleWrite = true; } bool RequestData::canRead() { return isAbleRead; } bool RequestData::canWrite() { return isAbleWrite; } ================================================ FILE: old_version/old_version_0.6/requestData.h ================================================ #pragma once #include "timer.h" #include #include #include #include #include #include #include #include using namespace cv; const int STATE_PARSE_URI = 1; const int STATE_PARSE_HEADERS = 2; const int STATE_RECV_BODY = 3; const int STATE_ANALYSIS = 4; const int STATE_FINISH = 5; const int MAX_BUFF = 4096; // 有请求出现但是读不到数据,可能是Request Aborted, // 或者来自网络的数据没有达到等原因, // 对这样的请求尝试超过一定的次数就抛弃 const int AGAIN_MAX_TIMES = 200; const int PARSE_URI_AGAIN = -1; const int PARSE_URI_ERROR = -2; const int PARSE_URI_SUCCESS = 0; const int PARSE_HEADER_AGAIN = -1; const int PARSE_HEADER_ERROR = -2; const int PARSE_HEADER_SUCCESS = 0; const int ANALYSIS_ERROR = -2; const int ANALYSIS_SUCCESS = 0; const int METHOD_POST = 1; const int METHOD_GET = 2; const int HTTP_10 = 1; const int HTTP_11 = 2; const int EPOLL_WAIT_TIME = 500; class MimeType { private: static void init(); static std::unordered_map mime; MimeType(); MimeType(const MimeType &m); public: static std::string getMime(const std::string &suffix); private: static pthread_once_t once_control; }; enum HeadersState { h_start = 0, h_key, h_colon, h_spaces_after_colon, h_value, h_CR, h_LF, h_end_CR, h_end_LF }; class TimerNode; class RequestData : public std::enable_shared_from_this { private: std::string path; int fd; int epollfd; std::string inBuffer; std::string outBuffer; __uint32_t events; bool error; int method; int HTTPversion; std::string file_name; int now_read_pos; int state; int h_state; bool isfinish; bool keep_alive; std::unordered_map headers; std::weak_ptr timer; bool isAbleRead; bool isAbleWrite; private: int parse_URI(); int parse_Headers(); int analysisRequest(); Mat stitch(Mat &src) { return src; } public: RequestData(); RequestData(int _epollfd, int _fd, std::string _path); ~RequestData(); void linkTimer(std::shared_ptr mtimer); void reset(); void seperateTimer(); int getFd(); void setFd(int _fd); void handleRead(); void handleWrite(); void handleError(int fd, int err_num, std::string short_msg); void handleConn(); void disableReadAndWrite(); void enableRead(); void enableWrite(); bool canRead(); bool canWrite(); }; ================================================ FILE: old_version/old_version_0.6/threadpool.cpp ================================================ #include "threadpool.h" pthread_mutex_t ThreadPool::lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t ThreadPool::notify = PTHREAD_COND_INITIALIZER; std::vector ThreadPool::threads; std::vector ThreadPool::queue; int ThreadPool::thread_count = 0; int ThreadPool::queue_size = 0; int ThreadPool::head = 0; int ThreadPool::tail = 0; int ThreadPool::count = 0; int ThreadPool::shutdown = 0; int ThreadPool::started = 0; int ThreadPool::threadpool_create(int _thread_count, int _queue_size) { bool err = false; do { if(_thread_count <= 0 || _thread_count > MAX_THREADS || _queue_size <= 0 || _queue_size > MAX_QUEUE) { _thread_count = 4; _queue_size = 1024; } thread_count = 0; queue_size = _queue_size; head = tail = count = 0; shutdown = started = 0; threads.resize(_thread_count); queue.resize(_queue_size); /* Start worker threads */ for(int i = 0; i < _thread_count; ++i) { if(pthread_create(&threads[i], NULL, threadpool_thread, (void*)(0)) != 0) { //threadpool_destroy(pool, 0); return -1; } ++thread_count; ++started; } } while(false); if (err) { //threadpool_free(pool); return -1; } return 0; } void myHandler(std::shared_ptr req) { std::shared_ptr request = std::static_pointer_cast(req); if (request->canWrite()) request->handleWrite(); else if (request->canRead()) request->handleRead(); request->handleConn(); } int ThreadPool::threadpool_add(std::shared_ptr args, std::function)> fun) { int next, err = 0; if(pthread_mutex_lock(&lock) != 0) return THREADPOOL_LOCK_FAILURE; do { next = (tail + 1) % queue_size; // 队列满 if(count == queue_size) { err = THREADPOOL_QUEUE_FULL; break; } // 已关闭 if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } queue[tail].fun = fun; queue[tail].args = args; tail = next; ++count; /* pthread_cond_broadcast */ if(pthread_cond_signal(¬ify) != 0) { err = THREADPOOL_LOCK_FAILURE; break; } } while(false); if(pthread_mutex_unlock(&lock) != 0) err = THREADPOOL_LOCK_FAILURE; return err; } int ThreadPool::threadpool_destroy(ShutDownOption shutdown_option) { printf("Thread pool destroy !\n"); int i, err = 0; if(pthread_mutex_lock(&lock) != 0) { return THREADPOOL_LOCK_FAILURE; } do { if(shutdown) { err = THREADPOOL_SHUTDOWN; break; } shutdown = shutdown_option; if((pthread_cond_broadcast(¬ify) != 0) || (pthread_mutex_unlock(&lock) != 0)) { err = THREADPOOL_LOCK_FAILURE; break; } for(i = 0; i < thread_count; ++i) { if(pthread_join(threads[i], NULL) != 0) { err = THREADPOOL_THREAD_FAILURE; } } } while(false); if(!err) { threadpool_free(); } return err; } int ThreadPool::threadpool_free() { if(started > 0) return -1; pthread_mutex_lock(&lock); pthread_mutex_destroy(&lock); pthread_cond_destroy(¬ify); return 0; } void *ThreadPool::threadpool_thread(void *args) { while (true) { ThreadPoolTask task; pthread_mutex_lock(&lock); while((count == 0) && (!shutdown)) { pthread_cond_wait(¬ify, &lock); } if((shutdown == immediate_shutdown) || ((shutdown == graceful_shutdown) && (count == 0))) { break; } task.fun = queue[head].fun; task.args = queue[head].args; queue[head].fun = NULL; queue[head].args.reset(); head = (head + 1) % queue_size; --count; pthread_mutex_unlock(&lock); (task.fun)(task.args); } --started; pthread_mutex_unlock(&lock); printf("This threadpool thread finishs!\n"); pthread_exit(NULL); return(NULL); } ================================================ FILE: old_version/old_version_0.6/threadpool.h ================================================ #pragma once #include "requestData.h" //#include "Condition.h" #include #include #include #include const int THREADPOOL_INVALID = -1; const int THREADPOOL_LOCK_FAILURE = -2; const int THREADPOOL_QUEUE_FULL = -3; const int THREADPOOL_SHUTDOWN = -4; const int THREADPOOL_THREAD_FAILURE = -5; const int THREADPOOL_GRACEFUL = 1; const int MAX_THREADS = 1024; const int MAX_QUEUE = 65535; typedef enum { immediate_shutdown = 1, graceful_shutdown = 2 } ShutDownOption; struct ThreadPoolTask { std::function)> fun; std::shared_ptr args; }; void myHandler(std::shared_ptr req); class ThreadPool { private: static pthread_mutex_t lock; static pthread_cond_t notify; static std::vector threads; static std::vector queue; static int thread_count; static int queue_size; static int head; // tail 指向尾节点的下一节点 static int tail; static int count; static int shutdown; static int started; public: static int threadpool_create(int _thread_count, int _queue_size); static int threadpool_add(std::shared_ptr args, std::function)> fun = myHandler); static int threadpool_destroy(ShutDownOption shutdown_option = graceful_shutdown); static int threadpool_free(); static void *threadpool_thread(void *args); }; ================================================ FILE: old_version/old_version_0.6/timer.cpp ================================================ #include "timer.h" #include "epoll.h" #include #include #include #include #include #include #include using namespace std; TimerNode::TimerNode(SP_ReqData _request_data, int timeout): deleted(false), request_data(_request_data) { //cout << "TimerNode()" << endl; struct timeval now; gettimeofday(&now, NULL); // 以毫秒计 expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } TimerNode::~TimerNode() { //cout << "~TimerNode()" << endl; if (request_data) { Epoll::epoll_del(request_data->getFd()); } //request_data.reset(); // if (request_data) // { // cout << "request_data=" << request_data << endl; // delete request_data; // request_data = NULL; // } } void TimerNode::update(int timeout) { struct timeval now; gettimeofday(&now, NULL); expired_time = ((now.tv_sec * 1000) + (now.tv_usec / 1000)) + timeout; } bool TimerNode::isvalid() { struct timeval now; gettimeofday(&now, NULL); size_t temp = ((now.tv_sec * 1000) + (now.tv_usec / 1000)); if (temp < expired_time) { return true; } else { this->setDeleted(); return false; } } void TimerNode::clearReq() { request_data.reset(); this->setDeleted(); } void TimerNode::setDeleted() { deleted = true; } bool TimerNode::isDeleted() const { return deleted; } size_t TimerNode::getExpTime() const { return expired_time; } TimerManager::TimerManager() { } TimerManager::~TimerManager() { } void TimerManager::addTimer(SP_ReqData request_data, int timeout) { SP_TimerNode new_node(new TimerNode(request_data, timeout)); { MutexLockGuard locker(lock); TimerNodeQueue.push(new_node); } request_data->linkTimer(new_node); } void TimerManager::addTimer(SP_TimerNode timer_node) { } /* 处理逻辑是这样的~ 因为(1) 优先队列不支持随机访问 (2) 即使支持,随机删除某节点后破坏了堆的结构,需要重新更新堆结构。 所以对于被置为deleted的时间节点,会延迟到它(1)超时 或 (2)它前面的节点都被删除时,它才会被删除。 一个点被置为deleted,它最迟会在TIMER_TIME_OUT时间后被删除。 这样做有两个好处: (1) 第一个好处是不需要遍历优先队列,省时。 (2) 第二个好处是给超时时间一个容忍的时间,就是设定的超时时间是删除的下限(并不是一到超时时间就立即删除),如果监听的请求在超时后的下一次请求中又一次出现了, 就不用再重新申请RequestData节点了,这样可以继续重复利用前面的RequestData,减少了一次delete和一次new的时间。 */ void TimerManager::handle_expired_event() { MutexLockGuard locker(lock); while (!TimerNodeQueue.empty()) { SP_TimerNode ptimer_now = TimerNodeQueue.top(); if (ptimer_now->isDeleted()) { TimerNodeQueue.pop(); //delete ptimer_now; } else if (ptimer_now->isvalid() == false) { TimerNodeQueue.pop(); //delete ptimer_now; } else { break; } } } ================================================ FILE: old_version/old_version_0.6/timer.h ================================================ #pragma once #include "requestData.h" #include "noncopyable.h" #include "MutexLock.h" #include #include #include #include class RequestData; class TimerNode { typedef std::shared_ptr SP_ReqData; private: bool deleted; size_t expired_time; SP_ReqData request_data; public: TimerNode(SP_ReqData _request_data, int timeout); ~TimerNode(); void update(int timeout); bool isvalid(); void clearReq(); void setDeleted(); bool isDeleted() const; size_t getExpTime() const; }; struct timerCmp { bool operator()(std::shared_ptr &a, std::shared_ptr &b) const { return a->getExpTime() > b->getExpTime(); } }; class TimerManager { typedef std::shared_ptr SP_ReqData; typedef std::shared_ptr SP_TimerNode; private: std::priority_queue, timerCmp> TimerNodeQueue; MutexLock lock; public: TimerManager(); ~TimerManager(); void addTimer(SP_ReqData request_data, int timeout); void addTimer(SP_TimerNode timer_node); void handle_expired_event(); }; ================================================ FILE: old_version/old_version_0.6/util.cpp ================================================ #include "util.h" #include #include #include #include #include const int MAX_BUFF = 4096; ssize_t readn(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nread = 0; ssize_t readSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else if (errno == EAGAIN) { return readSum; } else { return -1; } } else if (nread == 0) break; readSum += nread; nleft -= nread; ptr += nread; } return readSum; } ssize_t readn(int fd, std::string &inBuffer) { ssize_t nread = 0; ssize_t readSum = 0; while (true) { char buff[MAX_BUFF]; if ((nread = read(fd, buff, MAX_BUFF)) < 0) { if (errno == EINTR) continue; else if (errno == EAGAIN) { return readSum; } else { perror("read error"); return -1; } } else if (nread == 0) break; //printf("before inBuffer.size() = %d\n", inBuffer.size()); //printf("nread = %d\n", nread); readSum += nread; //buff += nread; inBuffer += std::string(buff, buff + nread); //printf("after inBuffer.size() = %d\n", inBuffer.size()); } return readSum; } ssize_t writen(int fd, void *buff, size_t n) { size_t nleft = n; ssize_t nwritten = 0; ssize_t writeSum = 0; char *ptr = (char*)buff; while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR) { nwritten = 0; continue; } else if (errno == EAGAIN) { return writeSum; } else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } return writeSum; } ssize_t writen(int fd, std::string &sbuff) { size_t nleft = sbuff.size(); ssize_t nwritten = 0; ssize_t writeSum = 0; const char *ptr = sbuff.c_str(); while (nleft > 0) { if ((nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0) { if (errno == EINTR) { nwritten = 0; continue; } else if (errno == EAGAIN) break; else return -1; } } writeSum += nwritten; nleft -= nwritten; ptr += nwritten; } if (writeSum == sbuff.size()) sbuff.clear(); else sbuff = sbuff.substr(writeSum); return writeSum; } void handle_for_sigpipe() { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; if(sigaction(SIGPIPE, &sa, NULL)) return; } int setSocketNonBlocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if(flag == -1) return -1; flag |= O_NONBLOCK; if(fcntl(fd, F_SETFL, flag) == -1) return -1; return 0; } ================================================ FILE: old_version/old_version_0.6/util.h ================================================ #pragma once #include #include ssize_t readn(int fd, void *buff, size_t n); ssize_t readn(int fd, std::string &inBuffer); ssize_t writen(int fd, void *buff, size_t n); ssize_t writen(int fd, std::string &sbuff); void handle_for_sigpipe(); int setSocketNonBlocking(int fd); ================================================ FILE: 并发模型.md ================================================ # 并发模型 程序使用Reactor模型,并使用多线程提高并发度。为避免线程频繁创建和销毁带来的开销,使用线程池,在程序的开始创建固定数量的线程。使用epoll作为IO多路复用的实现方式。 ## 线程 一般而言,多线程服务器中的线程可分为以下几类: * IO线程(负责网络IO) * 计算线程(负责复杂计算) * 第三方库所用线程 本程序中的Log线程属于第三种,其它线程属于IO线程,因为Web静态服务器计算量较小,所以没有分配计算线程,减少跨线程分配的开销,让IO线程兼顾计算任务。除Log线程外,每个线程一个事件循环,遵循One loop per thread。 ## 并发模型 本程序使用的并发模型如下图所示: ![并发模型](https://github.com/linyacool/WebServer/blob/master/datum/model.png) MainReactor只有一个,负责响应client的连接请求,并建立连接,它使用一个NIO Selector。在建立连接后用Round Robin的方式分配给某个SubReactor,因为涉及到跨线程任务分配,需要加锁,这里的锁由某个特定线程中的loop创建,只会被该线程和主线程竞争。 SubReactor可以有一个或者多个,每个subReactor都会在一个独立线程中运行,并且维护一个独立的NIO Selector。 当主线程把新连接分配给了某个SubReactor,该线程此时可能正阻塞在多路选择器(epoll)的等待中,怎么得知新连接的到来呢?这里使用了eventfd进行异步唤醒,线程会从epoll_wait中醒来,得到活跃事件,进行处理。 我学习了muduo库中的runInLoop和queueInLoop的设计方法,这两个方法主要用来执行用户的某个回调函数,queueInLoop是跨进程调用的精髓所在,具有极大的灵活性,我们只需要绑定好回调函数就可以了,我仿照muduo实现了这一点。 ## epoll工作模式 epoll的触发模式在这里我选择了ET模式,muduo使用的是LT,这两者IO处理上有很大的不同。ET模式要比LE复杂许多,它对用户提出了更高的要求,即每次读,必须读到不能再读(出现EAGAIN),每次写,写到不能再写(出现EAGAIN)。而LT则简单的多,可以选择也这样做,也可以为编程方便,比如每次只read一次(muduo就是这样做的,这样可以减少系统调用次数)。 ## 定时器 每个SubReactor持有一个定时器,用于处理超时请求和长时间不活跃的连接。muduo中介绍了时间轮的实现和用stl里set的实现,这里我的实现直接使用了stl里的priority_queue,底层是小根堆,并采用惰性删除的方式,时间的到来不会唤醒线程,而是每次循环的最后进行检查,如果超时了再删,因为这里对超时的要求并不会很高,如果此时线程忙,那么检查时间队列的间隔也会短,如果不忙,也给了超时请求更长的等待时间。 ## 核心结构 程序中的每一个类和结构体当然都必不可少,其中能体现并发模型和整体架构的,我认为是有两个: * Channel类:Channel是Reactor结构中的“事件”,它自始至终都属于一个EventLoop,负责一个文件描述符的IO事件,在Channel类中保存这IO事件的类型以及对应的回调函数,当IO事件发生时,最终会调用到Channel类中的回调函数。因此,程序中所有带有读写时间的对象都会和一个Channel关联,包括loop中的eventfd,listenfd,HttpData等。 * EventLoop:One loop per thread意味着每个线程只能有一个EventLoop对象,EventLoop即是时间循环,每次从poller里拿活跃事件,并给到Channel里分发处理。EventLoop中的loop函数会在最底层(Thread)中被真正调用,开始无限的循环,直到某一轮的检查到退出状态后从底层一层一层的退出。 ## Log Log的实现了学习了muduo,Log的实现分为前端和后端,前端往后端写,后端往磁盘写。为什么要这样区分前端和后端呢?因为只要涉及到IO,无论是网络IO还是磁盘IO,肯定是慢的,慢就会影响其它操作,必须让它快才行。 这里的Log前端是前面所述的IO线程,负责产生log,后端是Log线程,设计了多个缓冲区,负责收集前端产生的log,集中往磁盘写。这样,Log写到后端是没有障碍的,把慢的动作交给后端去做好了。 后端主要是由多个缓冲区构成的,集满了或者时间到了就向文件写一次。采用了muduo介绍了“双缓冲区”的思想,实际采用4个多的缓冲区(为什么说多呢?为什么4个可能不够用啊,要有备无患)。4个缓冲区分两组,每组的两个一个主要的,另一个防止第一个写满了没地方写,写满或者时间到了就和另外两个交换**指针**,然后把满的往文件里写。 与Log相关的类包括FileUtil、LogFile、AsyncLogging、LogStream、Logging。 其中前4个类每一个类都含有一个append函数,Log的设计也是主要围绕这个**append**函数展开的。 * FileUtil是最底层的文件类,封装了Log文件的打开、写入并在类析构的时候关闭文件,底层使用了标准IO,该append函数直接向文件写。 * LogFile进一步封装了FileUtil,并设置了一个循环次数,每过这么多次就flush一次。 * AsyncLogging是核心,它负责启动一个log线程,专门用来将log写入LogFile,应用了“双缓冲技术”,其实有4个以上的缓冲区,但思想是一样的。AsyncLogging负责(定时到或被填满时)将缓冲区中的数据写入LogFile中。 * LogStream主要用来格式化输出,重载了<<运算符,同时也有自己的一块缓冲区,这里缓冲区的存在是为了缓存一行,把多个<<的结果连成一块。 * Logging是对外接口,Logging类内涵一个LogStream对象,主要是为了每次打log的时候在log之前和之后加上固定的格式化的信息,比如打log的行、文件名等信息。 ================================================ FILE: 测试及改进.md ================================================ # 测试及改进 ## 测试环境 * OS:Ubuntu 14.04 * 内存:8G * CPU:I7-4720HQ ## 测试方法 * 理想的测试环境是两台计算机,带宽无限,现在的网卡虽然都是千兆网卡,但是普通家用的网线都是5类双绞线,最高100Mbps,在linux下用ethtool可以看到网卡的速度被限制为100Mbsp,无法更改为更高的,经测试很容易跑满带宽,因此退而选择本地环境。 * 使用工具Webbench,开启1000客户端进程,时间为60s * 分别测试短连接和长连接的情况 * 关闭所有的输出及Log * 为避免磁盘IO对测试结果的影响,测试响应为内存中的"Hello World"字符加上必要的HTTP头 * 我的最终版本中很多方面借鉴了muduo的思路,muduo中也提供了一个简单的HTTP echo测试,因此我将与muduo进行一个小小的对比,我修改了muduo测试的代码,使其echo相同的内容,关闭muduo的所有输出及Log * 线程池开启4线程 * 因为发送的内容很少,为避免发送可能的延迟,关闭Nagle算法 ## 测试结果及分析 测试截图放在最后 | 服务器 | 短连接QPS | 长连接QPS | | - | :-: | -: | | WebServer | 126798| 335338 | | Muduo | 88430 | 358302 | * 首先很明显的一点是长链接能处理的请求数是短连接的三四倍,因为没有了连接建立和断开的开销,不需要频繁accept和shutdown\close等系统调用,也不需要频繁建立和销毁对应的结构体。 * 我的服务器在最后的版本中,没有改进输入输出Buffer,用了效率低下的string,muduo用的是设计良好的vector,我将在后续改进这一点。这也造成了在长连接的情况下,我的server逊于muduo。虽说边沿触发效率高一点,但是还是比不过在Buffer上性能的优化的。 * 短链接的情况下,我的服务器要超过Muduo很多。原因在于:Muduo采用水平触发方式(Linux下用epoll),并且做法是每次Acceptor只accept一次就返回,面对突然的并发量,必然会因为频繁的epoll_wait耽误大量的时间,而我的做法是用while包裹accept,一直accept到不能再accept。当然,如果同时连接的请求很少,陈硕在书中也提到过,假如一次只有一个连接,那么我的方式就会多一次accpet才能跳出循环,但是这样的代价似乎微不足道啊,换来的效率却高了不少。 * 空闲时,Server几乎不占CPU,短连接时,各线程的CPU负载比较均衡,长连接时,主线程负载0,线程池的线程负载接近100%,因为没有新的连接需要处理。各种情况均正常。 * 没有严格的考证,测试时发现,HTTP的header解析的结果用map比用unordered_map快,网上的博客里有很多人做了测试,我在做实验的时候大致也发现了。主要是因为数据量太小,一个HTTP请求头才几个头部字段,建立unordered_map的成本要比map高,数据量小,复杂度根本体现不出来。 ## 测试结果截图 * WebServer短连接测试 ![shortWeb](https://github.com/linyacool/WebServer/blob/master/datum/WebServer.png) * muduo短连接测试 ![shortMuduo](https://github.com/linyacool/WebServer/blob/master/datum/muduo.png) * WebServer长连接测试 ![keepWeb](https://github.com/linyacool/WebServer/blob/master/datum/WebServerk.png) * muduo长连接测试 ![keepMuduo](https://github.com/linyacool/WebServer/blob/master/datum/muduok.png) * WebServer空闲负载 ![idle](https://github.com/linyacool/WebServer/blob/master/datum/idle.png) * WebServer短连接CPU负载 ![short](https://github.com/linyacool/WebServer/blob/master/datum/close.png) * WebServer长连接CPU负载 ![keep](https://github.com/linyacool/WebServer/blob/master/datum/keepalive.png) ================================================ FILE: 版本历史.md ================================================ # 版本历史 从0.1最初形成到一点一点改进到0.6,到最终看了muduo,痛下决心重写,最终的版本完全从头再来了,但有了前面的经验,写起来顺畅了不少,但花了比之前所有加起来还要长的时间 ## 0.1 第一版是看了很多Github上别人写的服务器,以及博客上的一些总结,结合自己的理解写出来的。模型结构如下: * 使用了epoll边沿触发+EPOLLONESHOT+非阻塞IO * 使用了一个固定线程数的线程池 * 实现了一个任务队列,由条件变量触发通知新任务的到来 * 实现了一个小根堆的定时器及时剔除超时请求,使用了STL的优先队列来管理定时器 * 解析了HTTP的get、post请求,支持长短连接 * mime设计为单例模式 * 线程的工作分配为: * 主线程负责等待epoll中的事件,并把到来的事件放进任务队列,在每次循环的结束剔除超时请求和被置为删除的时间结点 * 工作线程阻塞在条件变量的等待中,新任务到来后,某一工作线程会被唤醒,执行具体的IO操作和计算任务,如果需要继续监听,会添加到epoll中 * 锁的使用有两处: * 第一处是任务队列的添加和取操作,都需要加锁,并配合条件变量,跨越了多个线程。 * 第二处是定时器结点的添加和删除,需要加锁,主线程和工作线程都要操作定时器队列。 第一版的服务器已经相对较完整了,该有的功能都已经具备了 ## 0.2 在第一版的基础上,优化了代码结构,自己设计了RAII锁机制,使锁能够自动释放,并修复了一些小的bug ## 0.3 * 几乎全部的裸指针被智能指针替代 * 利用weak_ptr解决时间结点和HTTP类互指的问题 * 任务队列的任务结构从函数指针+参数指针转换为C++11的function 这一版还是花了不少时间的,毕竟对象的生命周期不由自己控制了 ## 0.4 这个时候买了陈硕的《Linux多线程服务端编程》书,看了一部分,从前面几章获得启发 * 为不提供拷贝构造和赋值运算符的类添加了noncopyable基类 * 重写了RAII机制的锁,学习muduo中的做法 * 重写了单例模式,将双重加锁改为更简单而安全的pthread_once() ## 0.5 * 修复了一些bug,稍微调整了类的结构 * 封装了条件变量 ## 0.6 * 仿照muduo,写了4个缓冲区的异步Log日志,没有区分优先级,其它基本都具备了 ## WebServer(重构) 不知道该给自己的服务器取什么名字好,随便叫个吧……最后一版被我改的面目全非,也是下了很大的决心。之前的版本无非修修补补,算是自我检讨的过程,但是闭门造车并不可取,于是我把陈硕的《Linux多线程服务端编程》看完了,书上虽然贴了部分源码,但我看的还是朦朦胧胧,很多地方不明白,花了几天时间把源码看了,不懂的地方再回过来看书,总算是弄明白了。看了大牛的代码,再看自己的……哎我重写总行了吧 顺便吐槽一下自己,之前在知乎上看到陈硕一直推销自己的书,我还觉得这人好功利,后来被师兄推荐,看了一下目录,觉得可以参考一下就买了。没想到书就一点一点这么看完了……还看了好几遍,源码也是看了好几遍…… 当然,这不是最终篇,可改进的地方还有很多,绝对不敢说看了几本书就敢说自己写的东西比大牛写的好,我还会继续改进自己的server的 最后版本的东西没有在这里介绍,写在了模型结构里,这里只想写一下自己的心路历程,记录一下小白成长之路~ ================================================ FILE: 连接的维护.md ================================================ # 连接维护(针对非阻塞IO) ### 建立连接 * 建立连接的过程 连接的建立比较简单,server端通过socket(),bind(),listen(),并使用epoll ET模式监听listenfd的读请求,当TCP连接完成3次握手后,会触发listenfd的读事件,应用程序调用accept(),会检查已完成的连接队列,如果队列里有连接,就返回这个连接,出错或连接为空时返回-1。此时,已经可以进行正常的读写操作了。 当然,因为是ET模式,accept()要一直循环到就绪连接为空。 * 分析 之所以说建立连接的过程比较简单,是因为数据的通信已经由操作系统帮我们完成了,这里的通信是指3次握手的过程,这个过程不需要应用程序参与,当应用程序感知到连接时,此时该连接已经完成了3次握手的过程,accept就好了。另一个原因是一般情况下,连接的建立都是client发起的,server端被动建立连接就好了,也不会出现同时建立的情况。 * 限制 假设server只监听一个端口,一个连接就是一个四元组(原ip,原port,对端ip, 对端port),那么理论上可以建立2^48个连接,可是,fd可没有这么多(操作系统限制、用户进程限制)。当连接满了,如果空等而不连接,那么就绪队列也满了后,会导致新连接无法建立。这里的做法我参考了muduo,准备一个空的文件描述符,accept()后直接close(),这样对端不会收到RST,至少可以知道服务器正在运行。 ### 关闭连接 相对于连接的建立,关闭连接则复杂的多,远不是一个close()那么简单,关闭连接要优雅。 ##### 什么时候关闭连接? 通常server和client都可以主动发Fin来关闭连接 * 对于client(非Keep-Alive),发送完请求后就可以shutdown()写端,然后收到server发来的应答,最后close掉连接。也可以不shutdown()写,等读完直接close。对于Keep-Alive的情况,就要看client的心情了,收到消息后可以断,也可以不断,server应该保证不主动断开。 * 对于server端,毫无疑问应该谨慎处理以上所有情况。具体说来: > * 出现各种关于连接的错误时,可以直接close()掉 > * 短连接超时的请求,可以close(),也可以不关 > * 长连接对方长时间没有请求(如果没有保活机制),可以close(),也可以不关 > * client发出Fin,server会收到0字节,通常不能判断client是close了还是shutdown,这时server应当把消息发完,然后才可以close(),如果对方调用的是close,会收到RST,server能感知到,就可以立即close了 > * 短连接正常结束,server可以close,也可以不close,大多数的实现是不close的(对HTTP1.1而言) ##### EPOLLIN触发但是read()返回0的情况 这种情况通常有两个原因: > * 对端已经关闭了连接,这时再写该fd会出错,此时应该关闭连接 > * 对端只是shutdown()了写端,告诉server我已经写完了,但是还可以接收信息。server应该在写完所有的信息后再关闭连接。更优雅的做法是透明的传递这个行为,即server顺着关闭读端,然后发完数据后关闭。 ================================================ FILE: 遇到的困难.md ================================================ # 遇到的困难 ## 1. 如何设计各个线程个任务 其实我觉的实现上的困难都不算真正的困难吧,毕竟都能写出来,无非是解决bug花的时间的长短。 我遇到的最大的问题是不太理解One loop per thread这句话吧,翻译出来不就是每个线程一个循环,我最开始写的也是一个线程一个循环啊,muduo的实现和我的有什么区别呢?还有怎么设计才能减少竞态? 带着这些问题我看了《Linux多线程服务端编程》,并看完了muduo的源码,这些问题自然而然就解决了 ## 2. 异步Log几秒钟才写一次磁盘,要是coredump了,这段时间内产生的log我去哪找啊? 其实这个问题非常简单了,也没花多少时间去解决,但我觉的非常好玩。coredump了自然会保存在core文件里了,无非就是把它找出来的问题了,在这里记录一下。 当然这里不管coredump的原因是什么,我只想看丢失的log。所以模拟的话在某个地方abort()就行 多线程调试嘛,先看线程信息,info thread,找到我的异步打印线程,切换进去看bt调用栈,正常是阻塞在条件变量是wait条件中的,frame切换到threadFunc(这个函数是我的异步log里面的循环的函数名),剩下的就是print啦~不过,我的Buffer是用智能指针shared_ptr包裹的,直接->不行,gdb不识别,优化完.get()不让用,可能被inline掉了,只能直接从shared_ptr源码中找到_M_ptr成员来打印。 ![gdb](https://github.com/linyacool/WebServer/blob/master/datum/gdb.png) ================================================ FILE: 项目目的.md ================================================ # 项目目的 --- ### 最初的想法 本项目是我在三星电子(中国)研发中心实习期间利用晚上和周末的时间完成的,实习期间我负责4K分辨率双鱼眼摄像头视频拼接的算法设计与实现,我希望能把其中的图像拼接部分的成果通过Web的方式展示出来,但因为涉及保密协议,不得不放弃这一想法。 ### Web服务器能够很好的贯穿所学的知识 但是,Web服务器能够很好的贯穿之前所学的知识,之前看过的《C++ Primer》、《Effevtive C++》、《STL源码剖析》、《深度探索C++对象模型》、《TCP\IP详解卷1》、APUE、UNP,还包括了《后台开发核心技术与应用实践》等书,涵盖了 * TCP、HTTP协议 * 多进程多线程 * IO * 锁 * 通信 * C++语法 * 编程规范 * Linux环境下各种工具的使用 * 版本控制Git * Makefile和CMakeLists文件的编写 * 自动化构建工具Travis CI的使用 最终的版本在很多方面学习了muduo网络库,在看完陈硕的《Linux多线程服务端编程》后,对照着书把muduo的源码读了几遍,并重构了自己的服务器,最终的很多想法借鉴了muduo的思想。