├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── img ├── 1.png ├── 2.png └── 3.png ├── include ├── Acceptor.h ├── AsyncLogging.h ├── Buffer.h ├── Callbacks.h ├── Channel.h ├── ConsistenHash.h ├── CurrentThread.h ├── EPollPoller.h ├── EventLoop.h ├── EventLoopThread.h ├── EventLoopThreadPool.h ├── FileUtil.h ├── FixedBuffer.h ├── InetAddress.h ├── LFU.h ├── LogFile.h ├── LogStream.h ├── Logger.h ├── Poller.h ├── RICachePolicy.h ├── Socket.h ├── TcpConnection.h ├── TcpServer.h ├── Thread.h ├── Timer.h ├── TimerQueue.h ├── Timestamp.h ├── memoryPool.h └── noncopyable.h ├── lib ├── liblog_lib.so ├── libmemory_lib.so └── libsrc_lib.so ├── log ├── AsyncLogging.cc ├── CMakeLists.txt ├── CurrentThread.cc ├── FileUtil.cc ├── LogFile.cc └── LogStream.cc ├── memory ├── CMakeLists.txt └── memoryPool.cc └── src ├── Acceptor.cc ├── Buffer.cc ├── CMakeLists.txt ├── Channel.cc ├── CurrentThread.cc ├── DefaultPoller.cc ├── EPollPoller.cc ├── EventLoop.cc ├── EventLoopThread.cc ├── EventLoopThreadPool.cc ├── InetAddress.cc ├── Logger.cc ├── Poller.cc ├── Socket.cc ├── TcpConnection.cc ├── TcpServer.cc ├── Thread.cc ├── Timer.cc ├── TimerQueue.cc ├── Timestamp.cc └── main.cc /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | bin/ 3 | *.o 4 | *.out 5 | *.log 6 | .vscode/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Top-level CMake, set global link libraries, add subdirectories, and project header file locations 2 | # Set minimum version and project name 3 | cmake_minimum_required(VERSION 3.0) 4 | project(ronald-webserver) 5 | 6 | # Set global C++ standard 7 | set (CMAKE_CXX_STANDARD 11) 8 | set (CMAKE_CXX_STANDARD_REQUIRED True) 9 | 10 | # Set header file directory for all subprojects 11 | include_directories(${CMAKE_SOURCE_DIR}/include) 12 | 13 | # Set output directories 14 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) 15 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) 16 | 17 | # Set global link libraries 18 | set(LIBS 19 | pthread 20 | ) 21 | 22 | # Add subdirectories 23 | add_subdirectory(src) 24 | add_subdirectory(memory) 25 | add_subdirectory(log) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ronald-webserver 2 | 3 | Project Introduction 4 | 5 | This project is a high-performance WEB server implemented in C++. The project's underlying architecture adopts the core design philosophy of the muduo library, featuring a multi-threaded multi-Reactor network model. Additionally, it incorporates a memory pool, an efficient double-buffered asynchronous logging system, and LFU caching. 6 | 7 | ## Development Environment 8 | 9 | * linux kernel version5.15.0-113-generic (ubuntu 22.04.6) 10 | * gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 11 | * cmake version 3.22 12 | 13 | ## Directory Structure 14 | 15 | ```shell 16 | ronald-webserver/ 17 | ├── img/ # Directory for images 18 | ├── include/ # Location for all header files (.h) 19 | ├── lib/ # Directory for shared libraries 20 | | 21 | ├── log/ # Logging management module 22 | │ ├── log.cc # Logging implementation 23 | ├── memory/ # Memory management module 24 | │ ├── memory.cc # Memory management implementation 25 | ├── src/ # Source code directory 26 | │ ├── main.cpp # Main program entry 27 | │ ├── ... # Other source files 28 | | 29 | ├── CMakeLists.txt # CMake build file 30 | ├── LICENSE # License file 31 | └── README.md # Project documentation 32 | ``` 33 | 34 | ## Prerequisites 35 | 36 | Install basic tools 37 | 38 | ```bash 39 | sudo apt-get update 40 | sudo apt-get install -y wget cmake build-essential unzip git 41 | ``` 42 | 43 | ## Compilation Instructions 44 | 45 | 1. Clone the project: 46 | 47 | ```bash 48 | git clone https://github.com/Trapped-Bird/Ronald-webserver.git 49 | cd Ronald-webserver 50 | ``` 51 | 52 | 2. Create build directory and compile: 53 | 54 | ```bash 55 | mkdir build && 56 | cd build && 57 | cmake .. && 58 | make -j ${nproc} 59 | ``` 60 | 61 | 3. After building, enter the bin directory: 62 | 63 | ```bash 64 | cd bin 65 | ``` 66 | 67 | 4. Start the main executable: 68 | 69 | ```bash 70 | ./main 71 | ``` 72 | 73 | **Note**: You need to open a new window and run `nc 127.0.0.1 8080` to start our client and connect to the web server started by the main executable. 74 | 75 | ## Running Results 76 | 77 | By running the main executable in the bin directory, you will see the following results: 78 | 79 | The log files will be stored in the `logs` directory under the bin directory. Each time the program runs, a new log file will be generated to record the program's running status and error information. 80 | 81 | - Server running results as shown: 82 | 83 | ![img](./img/1.png) 84 | 85 | - Client running results as shown: 86 | 87 | ![img](./img/2.png) 88 | 89 | **Note**: The test results still use the echo server test, focusing on the implementation of the architecture. 90 | 91 | --- 92 | 93 | ### Simple Analysis of Log Core Content: 94 | 95 | First, the log results are shown as: 96 | ![img](./img/3.png) 97 | 98 | 1. File Descriptor Statistics 99 | 100 | ```bash 101 | 2025/05/13 13:44:522120 INFO fd total count:3 - EPollPoller.cc:32 102 | ``` 103 | 104 | - Description: After a poll or event processing, print the number of monitored FDs in the current epoll - 3. 105 | - Usually includes: 106 | Listen socket (listen_fd) 107 | One or more connection socket(client_fd) 108 | Internal pipeline, wake-up fd (for example, for eventfd, timerfd, pipe, etc.) 109 | 110 | 2. Event Triggering 111 | 112 | ```bash 113 | 2025/05/13 13:44:185295 INFO events happend1 - EPollPoller.cc:40 114 | 2025/05/13 13:44:185343 INFO channel handleEvent revents:1 - Channel.cc:73 115 | ``` 116 | 117 | - An event occurred (events happend1), possibly a client socket close event. 118 | - revents:1 indicates the triggered event type is EPOLLIN, meaning the peer closed the connection or sent data. 119 | 120 | 3. Connection Close Handling 121 | 122 | ```bash 123 | 2025/05/13 13:44:185377 INFO TcpConnection::handleClose fd=13state=2 - TcpConnection.cc:241 124 | 2025/05/13 13:44:185390 INFO func =>fd13events=0index=1 - EPollPoller.cc:66 125 | 2025/05/13 13:44:185404 INFO Connection DOWN :127.0.0.1:33174 - main.cc:44 126 | ``` 127 | 128 | - TcpConnection::handleClose: Connection with file descriptor fd=13 closed, current state state=2 (possibly indicating "connected" state). 129 | - Connection DOWN: Connection with client 127.0.0.1:33174 disconnected. 130 | - events=0: Indicates this file descriptor is no longer listening for any events. 131 | 132 | 4. Removing Connection from Server 133 | 134 | ```bash 135 | 2025/05/13 13:44:185476 INFO TcpServer::removeConnectionInLoop [EchoServer] - connection %sEchoServer-127.0.0.1:8080#1 - TcpServer.cc:114 136 | 2025/05/13 13:44:185618 INFO removeChannel fd=13 - EPollPoller.cc:102 137 | ``` 138 | 139 | - TcpServer::removeConnectionInLoop: Server internally removes binding with connection 127.0.0.1:47376. 140 | - removeChannel: Removed file descriptor fd=13 from EPoll's event listening list. 141 | 142 | 5. Resource Cleanup 143 | 144 | ```bash 145 | 2025/05/13 13:44:185631 INFO TcpConnection::dtor[EchoServer-127.0.0.1:8080#1]at fd=13state=0 - TcpConnection.cc:58 146 | ``` 147 | 148 | - TcpConnection destructor (dtor) called, releasing connection-related resources. 149 | - State state=0 indicates connection is completely closed, file descriptor fd=13 is destroyed. 150 | 151 | ## Functional Module Division 152 | 153 | ### Network Module 154 | 155 | - **Event Polling and Distribution Module**: `EventLoop.*`, `Channel.*`, `Poller.*`, `EPollPoller.*` responsible for event polling detection and implementing event distribution processing. `EventLoop` polls `Poller`, and `Poller` is implemented by `EPollPoller`. 156 | - **Thread and Event Binding Module**: `Thread.*`, `EventLoopThread.*`, `EventLoopThreadPool.*` bind threads with event loops, completing the `one loop per thread` model. 157 | - **Network Connection Module**: `TcpServer.*`, `TcpConnection.*`, `Acceptor.*`, `Socket.*` implement `mainloop` response to network connections and distribute to various `subloop`s. 158 | - **Buffer Module**: `Buffer.*` provides auto-expanding buffer to ensure ordered data arrival. 159 | 160 | ### Logging Module 161 | 162 | - The logging module is responsible for recording important information during server operation, helping developers with debugging and performance analysis. Log files are stored in the `bin/logs/` directory. 163 | 164 | ### Memory Management 165 | 166 | - The memory management module is responsible for dynamic memory allocation and deallocation, ensuring server stability and performance under high load conditions. 167 | 168 | ### LFU Cache Module 169 | 170 | - Used to decide which content to delete to free up space when cache capacity is insufficient. The core idea of LFU is to prioritize removing the least frequently used cache items. 171 | -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronald-debugging/Ronald-webserver/5bc27cb51466a32851dc6888e7d354c60cf09206/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronald-debugging/Ronald-webserver/5bc27cb51466a32851dc6888e7d354c60cf09206/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronald-debugging/Ronald-webserver/5bc27cb51466a32851dc6888e7d354c60cf09206/img/3.png -------------------------------------------------------------------------------- /include/Acceptor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "noncopyable.h" 6 | #include "Socket.h" 7 | #include "Channel.h" 8 | 9 | class EventLoop; 10 | class InetAddress; 11 | 12 | class Acceptor : noncopyable 13 | { 14 | public: 15 | using NewConnectionCallback = std::function; 16 | 17 | Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport); 18 | ~Acceptor(); 19 | // Set the callback function for new connections 20 | void setNewConnectionCallback(const NewConnectionCallback &cb) { NewConnectionCallback_ = cb; } 21 | // Check if listening 22 | bool listenning() const { return listenning_; } 23 | // Listen on local port 24 | void listen(); 25 | 26 | private: 27 | void handleRead(); // Handle new user connection event 28 | 29 | EventLoop *loop_; // The baseLoop defined by the user, also called mainLoop 30 | Socket acceptSocket_; // Dedicated socket for receiving new connections 31 | Channel acceptChannel_; // Dedicated channel for listening to new connections 32 | NewConnectionCallback NewConnectionCallback_; // Callback function for new connections 33 | bool listenning_; // Whether it is listening 34 | }; -------------------------------------------------------------------------------- /include/AsyncLogging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "noncopyable.h" 4 | #include "Thread.h" 5 | #include "FixedBuffer.h" 6 | #include "LogStream.h" 7 | #include "LogFile.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class AsyncLogging 15 | { 16 | public: 17 | AsyncLogging(const std::string &basename, off_t rollSize, int flushInterval=3); 18 | ~AsyncLogging() 19 | { 20 | if (running_) 21 | { 22 | stop(); 23 | } 24 | } 25 | // Frontend calls append to write logs 26 | void append(const char *logline, int len); 27 | void start() 28 | { 29 | running_ = true; 30 | thread_.start(); 31 | } 32 | void stop() 33 | { 34 | running_ = false; 35 | cond_.notify_one(); 36 | } 37 | 38 | private: 39 | using LargeBuffer = FixedBuffer; 40 | using BufferVector = std::vector>; 41 | // BufferVector::value_type is the element type of std::vector>, which is std::unique_ptr. 42 | using BufferPtr = BufferVector::value_type; 43 | void threadFunc(); 44 | const int flushInterval_; // Log flush interval 45 | std::atomic running_; 46 | const std::string basename_; 47 | const off_t rollSize_; 48 | Thread thread_; 49 | std::mutex mutex_; 50 | std::condition_variable cond_; 51 | 52 | BufferPtr currentBuffer_; 53 | BufferPtr nextBuffer_; 54 | BufferVector buffers_; 55 | }; -------------------------------------------------------------------------------- /include/Buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Definition of the underlying buffer type for the network library 9 | class Buffer 10 | { 11 | public: 12 | static const size_t kCheapPrepend = 8; // Initial reserved prependable space size 13 | static const size_t kInitialSize = 1024; 14 | 15 | explicit Buffer(size_t initalSize = kInitialSize) 16 | : buffer_(kCheapPrepend + initalSize) 17 | , readerIndex_(kCheapPrepend) 18 | , writerIndex_(kCheapPrepend) 19 | { 20 | } 21 | 22 | size_t readableBytes() const { return writerIndex_ - readerIndex_; } 23 | size_t writableBytes() const { return buffer_.size() - writerIndex_; } 24 | size_t prependableBytes() const { return readerIndex_; } 25 | 26 | // Return the starting address of readable data in the buffer 27 | const char *peek() const { return begin() + readerIndex_; } 28 | void retrieve(size_t len) 29 | { 30 | if (len < readableBytes()) 31 | { 32 | readerIndex_ += len; // Indicates that the application only read part of the readable buffer data, which is of length len. The data from readerIndex_ + len to writerIndex_ is not read yet 33 | } 34 | else // len == readableBytes() 35 | { 36 | retrieveAll(); 37 | } 38 | } 39 | void retrieveAll() 40 | { 41 | readerIndex_ = kCheapPrepend; 42 | writerIndex_ = kCheapPrepend; 43 | } 44 | 45 | // Convert the Buffer data reported by onMessage function to string type and return 46 | std::string retrieveAllAsString() { return retrieveAsString(readableBytes()); } 47 | std::string retrieveAsString(size_t len) 48 | { 49 | std::string result(peek(), len); 50 | retrieve(len); // The above line has already read the readable data from the buffer, so the buffer must be reset here 51 | return result; 52 | } 53 | 54 | // buffer_.size - writerIndex_ 55 | void ensureWritableBytes(size_t len) 56 | { 57 | if (writableBytes() < len) 58 | { 59 | makeSpace(len); // Expand 60 | } 61 | } 62 | 63 | // Add data in memory [data, data+len] to the writable buffer 64 | void append(const char *data, size_t len) 65 | { 66 | ensureWritableBytes(len); 67 | std::copy(data, data+len, beginWrite()); 68 | writerIndex_ += len; 69 | } 70 | char *beginWrite() { return begin() + writerIndex_; } 71 | const char *beginWrite() const { return begin() + writerIndex_; } 72 | 73 | // Read data from fd 74 | ssize_t readFd(int fd, int *saveErrno); 75 | // Send data through fd 76 | ssize_t writeFd(int fd, int *saveErrno); 77 | 78 | private: 79 | // The address of the first element of the underlying array of vector, which is the starting address of the array 80 | char *begin() { return &*buffer_.begin(); } 81 | const char *begin() const { return &*buffer_.begin(); } 82 | 83 | void makeSpace(size_t len) 84 | { 85 | /** 86 | * | kCheapPrepend |xxx| reader | writer | // xxx indicates the read part in reader 87 | * | kCheapPrepend | reader | len | 88 | **/ 89 | if (writableBytes() + prependableBytes() < len + kCheapPrepend) // That is, len > remaining space before xxx + writer part 90 | { 91 | buffer_.resize(writerIndex_ + len); 92 | } 93 | else // Here len <= xxx + writer, move reader to start from xxx to make continuous space after xxx 94 | { 95 | size_t readable = readableBytes(); // readable = length of reader 96 | // Copy the data from readerIndex_ to writerIndex_ in the current buffer 97 | // to the position kCheapPrepend at the beginning of the buffer, so as to make more writable space 98 | std::copy(begin() + readerIndex_, 99 | begin() + writerIndex_, 100 | begin() + kCheapPrepend); 101 | readerIndex_ = kCheapPrepend; 102 | writerIndex_ = readerIndex_ + readable; 103 | } 104 | } 105 | 106 | std::vector buffer_; 107 | size_t readerIndex_; 108 | size_t writerIndex_; 109 | }; 110 | -------------------------------------------------------------------------------- /include/Callbacks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Buffer; 7 | class TcpConnection; 8 | class Timestamp; 9 | 10 | using TcpConnectionPtr = std::shared_ptr; 11 | using ConnectionCallback = std::function; 12 | using CloseCallback = std::function; 13 | using WriteCompleteCallback = std::function; 14 | using HighWaterMarkCallback = std::function; 15 | 16 | using MessageCallback = std::function; -------------------------------------------------------------------------------- /include/Channel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "noncopyable.h" 7 | #include "Timestamp.h" 8 | 9 | class EventLoop; 10 | 11 | /** 12 | * Clarify the relationship between EventLoop, Channel, and Poller. In the Reactor model, they correspond to the event dispatcher. 13 | * Channel can be understood as a conduit that encapsulates sockfd and its interested events such as EPOLLIN, EPOLLOUT, and also binds the specific events returned by poller. 14 | **/ 15 | class Channel : noncopyable 16 | { 17 | public: 18 | using EventCallback = std::function; // muduo still uses typedef 19 | using ReadEventCallback = std::function; 20 | 21 | Channel(EventLoop *loop, int fd); 22 | ~Channel(); 23 | 24 | // After fd is notified by Poller, handle the event. handleEvent is called in EventLoop::loop() 25 | void handleEvent(Timestamp receiveTime); 26 | 27 | // Set callback function object 28 | void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); } 29 | void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); } 30 | void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); } 31 | void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); } 32 | 33 | // Prevent the channel from being manually removed while still executing callback operations 34 | void tie(const std::shared_ptr &); 35 | 36 | int fd() const { return fd_; } 37 | int events() const { return events_; } 38 | void set_revents(int revt) { revents_ = revt; } 39 | 40 | // Set the event status of fd, equivalent to epoll_ctl add/delete 41 | void enableReading() { events_ |= kReadEvent; update(); } 42 | void disableReading() { events_ &= ~kReadEvent; update(); } 43 | void enableWriting() { events_ |= kWriteEvent; update(); } 44 | void disableWriting() { events_ &= ~kWriteEvent; update(); } 45 | void disableAll() { events_ = kNoneEvent; update(); } 46 | 47 | // Return the current event status of fd 48 | bool isNoneEvent() const { return events_ == kNoneEvent; } 49 | bool isWriting() const { return events_ & kWriteEvent; } 50 | bool isReading() const { return events_ & kReadEvent; } 51 | 52 | int index() { return index_; } 53 | void set_index(int idx) { index_ = idx; } 54 | 55 | // one loop per thread 56 | EventLoop *ownerLoop() { return loop_; } 57 | void remove(); 58 | private: 59 | 60 | void update(); 61 | void handleEventWithGuard(Timestamp receiveTime); 62 | 63 | static const int kNoneEvent; 64 | static const int kReadEvent; 65 | static const int kWriteEvent; 66 | 67 | EventLoop *loop_; // Event loop 68 | const int fd_; // fd, the object Poller listens to 69 | int events_; // Registered events of interest for fd 70 | int revents_; // Specific events returned by Poller 71 | int index_; 72 | 73 | std::weak_ptr tie_; 74 | bool tied_; 75 | 76 | // Since the channel can know the specific events that occurred on fd, it is responsible for calling the corresponding event callback operations 77 | ReadEventCallback readCallback_; 78 | EventCallback writeCallback_; 79 | EventCallback closeCallback_; 80 | EventCallback errorCallback_; 81 | }; -------------------------------------------------------------------------------- /include/ConsistenHash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * @class ConsistentHash 12 | * @brief Class implementing the consistent hashing algorithm. 13 | * 14 | * Consistent hashing is a distributed hashing technique designed to minimize key redistribution when nodes are added or removed. 15 | * Commonly used in distributed cache systems and distributed database sharding. 16 | */ 17 | class ConsistentHash { 18 | public: 19 | /** 20 | * @brief Constructor 21 | * @param numReplicas Number of virtual nodes per physical node. Increasing virtual nodes can improve load balancing. 22 | * @param hashFunc Optional custom hash function, defaults to std::hash. 23 | */ 24 | ConsistentHash(size_t numReplicas, std::function hashFunc = std::hash()) 25 | : numReplicas_(numReplicas), hashFunction_(hashFunc) {} 26 | 27 | /** 28 | * @brief Add a node to the hash ring. 29 | * 30 | * Each node is replicated into several virtual nodes. Each virtual node calculates a unique hash value using `node + index`. 31 | * These hash values are stored on the hash ring and sorted for efficient lookup. 32 | * 33 | * @param node Name of the node to add (e.g., server address). 34 | */ 35 | void addNode(const std::string& node) { 36 | std::lock_guard lock(mtx_); // Ensure thread safety 37 | for (size_t i = 0; i < numReplicas_; ++i) { 38 | // Calculate a unique hash value for each virtual node 39 | size_t hash = hashFunction_(node +"_0"+std::to_string(i)); 40 | circle_[hash] = node; // Hash value maps to node 41 | sortedHashes_.push_back(hash); // Add to sorted list 42 | } 43 | // Sort the hash values 44 | std::sort(sortedHashes_.begin(), sortedHashes_.end()); 45 | } 46 | 47 | /** 48 | * @brief Remove a node from the hash ring. 49 | * 50 | * Delete all virtual nodes of the node and their corresponding hash values. 51 | * 52 | * @param node Name of the node to remove. 53 | */ 54 | void removeNode(const std::string& node) { 55 | std::lock_guard lock(mtx_); // Ensure thread safety 56 | for (size_t i = 0; i < numReplicas_; ++i) { 57 | // Calculate the hash value of the virtual node 58 | size_t hash = hashFunction_(node + std::to_string(i)); 59 | circle_.erase(hash); // Remove the hash from the hash ring 60 | auto it = std::find(sortedHashes_.begin(), sortedHashes_.end(), hash); 61 | if (it != sortedHashes_.end()) { 62 | sortedHashes_.erase(it); // Remove from sorted list 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * @brief Find the node responsible for handling the given key. 69 | * 70 | * Find the first node in the hash ring whose hash value is greater than or equal to the key's hash value. 71 | * If not found (i.e., exceeds the maximum value of the hash ring), wrap around to the first node. 72 | * 73 | * @param key The key to look up (e.g., data identifier). 74 | * @return The name of the node responsible for the key. 75 | * @throws std::runtime_error If the hash ring is empty (no nodes). 76 | */ 77 | size_t getNode(const std::string& key) { 78 | std::lock_guard lock(mtx_); // Ensure thread safety 79 | if (circle_.empty()) { 80 | throw std::runtime_error("No nodes in consistent hash"); // Throw exception if ring is empty 81 | } 82 | size_t hash = hashFunction_(key); // Calculate the hash value of the key 83 | // Find the first position in the sorted hash list greater than the key's hash value 84 | auto it = std::upper_bound(sortedHashes_.begin(), sortedHashes_.end(), hash); 85 | if (it == sortedHashes_.end()) { 86 | // If it exceeds the maximum value of the ring, wrap around to the first node 87 | it = sortedHashes_.begin(); 88 | } 89 | return *it; // Return the corresponding hash value 90 | } 91 | 92 | private: 93 | size_t numReplicas_; // Number of virtual nodes per physical node 94 | std::function hashFunction_; // User-defined or default hash function 95 | std::unordered_map circle_; // Mapping from hash value to node name 96 | std::vector sortedHashes_; // Sorted list of hash values for efficient lookup 97 | std::mutex mtx_; // Mutex to protect the hash ring and ensure thread safety 98 | }; 99 | -------------------------------------------------------------------------------- /include/CurrentThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | namespace CurrentThread 6 | { 7 | extern thread_local int t_cachedTid; // Cache tid because system calls are very time-consuming, save tid after getting it 8 | 9 | void cacheTid(); 10 | 11 | inline int tid() // Inline function only works in current file 12 | { 13 | if (__builtin_expect(t_cachedTid == 0, 0)) // __builtin_expect is a low-level optimization, this statement means if tid hasn't been obtained yet, enter if and get tid through cacheTid() system call 14 | { 15 | cacheTid(); 16 | } 17 | return t_cachedTid; 18 | } 19 | } -------------------------------------------------------------------------------- /include/EPollPoller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Poller.h" 7 | #include "Timestamp.h" 8 | 9 | /** 10 | * Usage of epoll: 11 | * 1. epoll_create 12 | * 2. epoll_ctl (add, mod, del) 13 | * 3. epoll_wait 14 | **/ 15 | 16 | class Channel; 17 | 18 | class EPollPoller : public Poller 19 | { 20 | public: 21 | EPollPoller(EventLoop *loop); 22 | ~EPollPoller() override; 23 | 24 | // Override the abstract method of base class Poller 25 | Timestamp poll(int timeoutMs, ChannelList *activeChannels) override; 26 | void updateChannel(Channel *channel) override; 27 | void removeChannel(Channel *channel) override; 28 | 29 | private: 30 | static const int kInitEventListSize = 16; 31 | 32 | // Fill active connections 33 | void fillActiveChannels(int numEvents, ChannelList *activeChannels) const; 34 | // Update channel, actually calls epoll_ctl 35 | void update(int operation, Channel *channel); 36 | 37 | using EventList = std::vector; // In C++, you can omit struct and just write epoll_event 38 | 39 | int epollfd_; // The fd returned by epoll_create is saved in epollfd_ 40 | EventList events_; // Used to store all file descriptor event sets returned by epoll_wait 41 | }; -------------------------------------------------------------------------------- /include/EventLoop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "noncopyable.h" 10 | #include "Timestamp.h" 11 | #include "CurrentThread.h" 12 | #include "TimerQueue.h" 13 | class Channel; 14 | class Poller; 15 | 16 | // Event loop class, mainly contains two major modules: Channel and Poller (epoll abstraction) 17 | class EventLoop : noncopyable 18 | { 19 | public: 20 | using Functor = std::function; 21 | 22 | EventLoop(); 23 | ~EventLoop(); 24 | 25 | // Start event loop 26 | void loop(); 27 | // Exit event loop 28 | void quit(); 29 | 30 | Timestamp pollReturnTime() const { return pollRetureTime_; } 31 | 32 | // Execute in the current loop 33 | void runInLoop(Functor cb); 34 | // Put the upper-level registered callback function cb into the queue and wake up the thread where the loop is located to execute cb 35 | void queueInLoop(Functor cb); 36 | 37 | // Wake up the thread where the loop is located through eventfd 38 | void wakeup(); 39 | 40 | // EventLoop's methods => Poller's methods 41 | void updateChannel(Channel *channel); 42 | void removeChannel(Channel *channel); 43 | bool hasChannel(Channel *channel); 44 | 45 | // Determine whether the EventLoop object is in its own thread 46 | bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); } // threadId_ is the thread id when EventLoop is created, CurrentThread::tid() is the current thread id 47 | /** 48 | * Timer task related functions 49 | */ 50 | void runAt(Timestamp timestamp, Functor &&cb) 51 | { 52 | timerQueue_->addTimer(std::move(cb), timestamp, 0.0); 53 | } 54 | 55 | void runAfter(double waitTime, Functor &&cb) 56 | { 57 | Timestamp time(addTime(Timestamp::now(), waitTime)); 58 | runAt(time, std::move(cb)); 59 | } 60 | 61 | void runEvery(double interval, Functor &&cb) 62 | { 63 | Timestamp timestamp(addTime(Timestamp::now(), interval)); 64 | timerQueue_->addTimer(std::move(cb), timestamp, interval); 65 | } 66 | 67 | private: 68 | void handleRead(); // Event callback bound to the file descriptor wakeupFd_ returned by eventfd. When wakeup() is called, i.e., when an event occurs, handleRead() reads 8 bytes from wakeupFd_ and wakes up the blocked epoll_wait 69 | void doPendingFunctors(); // Execute upper-level callbacks 70 | 71 | using ChannelList = std::vector; 72 | 73 | std::atomic_bool looping_; // Atomic operation, implemented by CAS at the bottom 74 | std::atomic_bool quit_; // Flag to exit loop 75 | 76 | const pid_t threadId_; // Record which thread id created the current EventLoop, i.e., identifies the thread id to which the current EventLoop belongs 77 | 78 | Timestamp pollRetureTime_; // The time when Poller returns the Channels where events occurred 79 | std::unique_ptr poller_; 80 | std::unique_ptr timerQueue_; 81 | int wakeupFd_; // Function: When mainLoop gets a new user's Channel, it needs to select a subLoop through polling algorithm and wake up subLoop to process the Channel through this member 82 | std::unique_ptr wakeupChannel_; 83 | 84 | ChannelList activeChannels_; // Return the list of all Channels where events are currently detected by Poller 85 | 86 | std::atomic_bool callingPendingFunctors_; // Indicates whether the current loop has callback operations to execute 87 | std::vector pendingFunctors_; // Store all callback operations that the loop needs to execute 88 | std::mutex mutex_; // Mutex to protect thread-safe operations on the above vector container 89 | }; -------------------------------------------------------------------------------- /include/EventLoopThread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "noncopyable.h" 9 | #include "Thread.h" 10 | 11 | class EventLoop; 12 | 13 | class EventLoopThread : noncopyable 14 | { 15 | public: 16 | using ThreadInitCallback = std::function; 17 | 18 | EventLoopThread(const ThreadInitCallback &cb = ThreadInitCallback(), 19 | const std::string &name = std::string()); 20 | ~EventLoopThread(); 21 | 22 | EventLoop *startLoop(); 23 | 24 | private: 25 | void threadFunc(); 26 | 27 | EventLoop *loop_; 28 | bool exiting_; 29 | Thread thread_; 30 | std::mutex mutex_; // Mutex 31 | std::condition_variable cond_; // Condition variable 32 | ThreadInitCallback callback_; 33 | }; -------------------------------------------------------------------------------- /include/EventLoopThreadPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "noncopyable.h" 9 | #include "ConsistenHash.h" 10 | class EventLoop; 11 | class EventLoopThread; 12 | 13 | class EventLoopThreadPool : noncopyable 14 | { 15 | public: 16 | using ThreadInitCallback = std::function; 17 | 18 | EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg); 19 | ~EventLoopThreadPool(); 20 | 21 | void setThreadNum(int numThreads) { numThreads_ = numThreads; } 22 | 23 | void start(const ThreadInitCallback &cb = ThreadInitCallback()); 24 | 25 | // If working in multithreading, baseLoop_ (mainLoop) will assign Channel to subLoop in a round-robin way by default 26 | EventLoop *getNextLoop(const std::string& key); 27 | 28 | std::vector getAllLoops(); // Get all EventLoops 29 | 30 | bool started() const { return started_; } // Whether it has started 31 | const std::string name() const { return name_; } // Get name 32 | 33 | private: 34 | EventLoop *baseLoop_; // The loop created by the user using muduo. If the number of threads is 1, use the user-created loop directly; otherwise, create multiple EventLoops 35 | std::string name_; // Thread pool name, usually specified by the user. The name of EventLoopThread in the thread pool depends on the thread pool name. 36 | bool started_; // Whether it has started 37 | int numThreads_; // Number of threads in the thread pool 38 | int next_; // The index of the EventLoop selected when a new connection arrives 39 | std::vector> threads_; // List of IO threads 40 | std::vector loops_; // List of EventLoops in the thread pool, pointing to EventLoop objects created by the EventLoopThread thread function. 41 | ConsistentHash hash_; // Consistent hash object 42 | }; -------------------------------------------------------------------------------- /include/FileUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include//off_t 5 | /** 6 | * @brief File utility class for handling file write operations 7 | * This class encapsulates basic file operations, including writing data and flushing the buffer 8 | */ 9 | class FileUtil 10 | { 11 | public: 12 | /** 13 | * @brief Constructor 14 | * @param file_name Name of the file to open 15 | */ 16 | FileUtil(std::string& file_name); 17 | 18 | /** 19 | * @brief Destructor 20 | * Responsible for closing the file and cleaning up resources 21 | */ 22 | ~FileUtil(); 23 | 24 | /** 25 | * @brief Write data to file 26 | * @param data Pointer to the data to write 27 | * @param len Length of the data to write 28 | */ 29 | void append(const char* data, size_t len); 30 | 31 | /** 32 | * @brief Flush file buffer 33 | * Immediately write the data in the buffer to the file 34 | */ 35 | void flush(); 36 | 37 | /** 38 | * @brief Get the number of bytes written 39 | * @return Returns the total number of bytes written to the file 40 | */ 41 | off_t writtenBytes() const { return writtenBytes_; } 42 | 43 | private: 44 | size_t write(const char* data, size_t len); 45 | FILE* file_; // File pointer for file operations 46 | char buffer_[64*1024]; // Buffer for file operations, 64KB in size, used to improve write efficiency 47 | off_t writtenBytes_; // Records the total number of bytes written to the file, off_t type for large file support 48 | }; -------------------------------------------------------------------------------- /include/FixedBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | // Forward declaration of class 5 | class AsyncLogging; 6 | constexpr int kSmallBufferSize = 4000; 7 | constexpr int kLargeBufferSize = 4000 * 1000; 8 | 9 | // Fixed buffer class for managing log data storage 10 | // This class provides a fixed-size buffer that allows data to be appended to the buffer and provides related operations 11 | template 12 | class FixedBuffer : noncopyable 13 | { 14 | public: 15 | // Constructor, initialize current pointer to buffer start position 16 | FixedBuffer() 17 | : cur_(data_), size_(0) 18 | { 19 | } 20 | 21 | // Append data of specified length to buffer 22 | // If there is enough available space in the buffer, the data is copied to the current pointer position and the current pointer is updated 23 | void append(const char *buf, size_t len) 24 | { 25 | if (avail() > len) 26 | { 27 | memcpy(cur_, buf, len); // Copy data to buffer 28 | add(len); 29 | } 30 | } 31 | 32 | // Return the starting address of the buffer 33 | const char *data() const { return data_; } 34 | 35 | // Return the length of the current valid data in the buffer 36 | int length() const { return size_; } 37 | 38 | // Return the position of the current pointer 39 | char *current() { return cur_; } 40 | 41 | // Return the size of the remaining available space in the buffer 42 | size_t avail() const { return static_cast(buffer_size - size_); } 43 | 44 | // Update the current pointer, increase the specified length 45 | void add(size_t len) 46 | { 47 | cur_ += len; 48 | size_ += len; 49 | } 50 | // Reset the current pointer to the start of the buffer 51 | void reset() 52 | { 53 | cur_ = data_; 54 | size_ = 0; 55 | } 56 | 57 | // Clear the data in the buffer 58 | void bzero() { ::bzero(data_, sizeof(data_)); } 59 | 60 | // Convert the data in the buffer to std::string type and return 61 | std::string toString() const { return std::string(data_, length()); } 62 | 63 | private: 64 | char data_[buffer_size]; // Define fixed-size buffer 65 | char *cur_; 66 | int size_; 67 | }; 68 | -------------------------------------------------------------------------------- /include/InetAddress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Encapsulate socket address type 8 | class InetAddress 9 | { 10 | public: 11 | explicit InetAddress(uint16_t port = 0, std::string ip = "127.0.0.1"); 12 | explicit InetAddress(const sockaddr_in &addr) 13 | : addr_(addr) 14 | { 15 | } 16 | 17 | std::string toIp() const; 18 | std::string toIpPort() const; 19 | uint16_t toPort() const; 20 | 21 | const sockaddr_in *getSockAddr() const { return &addr_; } 22 | void setSockAddr(const sockaddr_in &addr) { addr_ = addr; } 23 | 24 | private: 25 | sockaddr_in addr_; 26 | }; -------------------------------------------------------------------------------- /include/LFU.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "RICachePolicy.h" 11 | 12 | namespace RonaldCache 13 | { 14 | 15 | template class RLfuCache; 16 | 17 | template 18 | class FreqList 19 | { 20 | private: 21 | struct Node 22 | { 23 | int freq; // Access frequency 24 | Key key; 25 | Value value; 26 | std::shared_ptr pre; // Previous node 27 | std::shared_ptr next; 28 | 29 | Node() 30 | : freq(1), pre(nullptr), next(nullptr) {} 31 | Node(Key key, Value value) 32 | : freq(1), key(key), value(value), pre(nullptr), next(nullptr) {} 33 | }; 34 | 35 | using NodePtr = std::shared_ptr; 36 | int freq_; // Access frequency 37 | NodePtr head_; // Dummy head node 38 | NodePtr tail_; // Dummy tail node 39 | 40 | public: 41 | explicit FreqList(int n) 42 | : freq_(n) 43 | { 44 | head_ = std::make_shared(); 45 | tail_ = std::make_shared(); 46 | head_->next = tail_; 47 | tail_->pre = head_; 48 | } 49 | 50 | bool isEmpty() const 51 | { 52 | return head_->next == tail_; 53 | } 54 | 55 | // Node management method 56 | void addNode(NodePtr node) 57 | { 58 | if (!node || !head_ || !tail_) 59 | return; 60 | 61 | node->pre = tail_->pre; 62 | node->next = tail_; 63 | tail_->pre->next = node; 64 | tail_->pre = node; 65 | } 66 | 67 | void removeNode(NodePtr node) 68 | { 69 | if (!node || !head_ || !tail_) 70 | return; 71 | if (!node->pre || !node->next) 72 | return; 73 | 74 | node->pre->next = node->next; 75 | node->next->pre = node->pre; 76 | node->pre = nullptr; 77 | node->next = nullptr; 78 | } 79 | 80 | NodePtr getFirstNode() const { return head_->next; } 81 | 82 | friend class RLfuCache; 83 | //friend class RArcCache; 84 | }; 85 | 86 | template 87 | class RLfuCache : public RICachePolicy 88 | { 89 | public: 90 | using Node = typename FreqList::Node; 91 | using NodePtr = std::shared_ptr; 92 | using NodeMap = std::unordered_map; 93 | 94 | RLfuCache(int capacity, int maxAverageNum = 10) 95 | : capacity_(capacity), minFreq_(INT8_MAX), maxAverageNum_(maxAverageNum), 96 | curAverageNum_(0), curTotalNum_(0) 97 | {} 98 | 99 | ~RLfuCache() override = default; 100 | 101 | void put(Key key, Value value) override 102 | { 103 | if (capacity_ == 0) 104 | return; 105 | 106 | std::lock_guard lock(mutex_); 107 | auto it = nodeMap_.find(key); 108 | if (it != nodeMap_.end()) 109 | { 110 | // Reset its value 111 | it->second->value = value; 112 | // If found, just adjust it directly, no need to search again in get, but the impact is not significant 113 | getInternal(it->second, value); 114 | return; 115 | } 116 | 117 | putInternal(key, value); 118 | } 119 | 120 | // value is an output parameter 121 | bool get(Key key, Value& value) override 122 | { 123 | std::lock_guard lock(mutex_); 124 | auto it = nodeMap_.find(key); 125 | if (it != nodeMap_.end()) 126 | { 127 | getInternal(it->second, value); 128 | return true; 129 | } 130 | 131 | return false; 132 | } 133 | 134 | Value get(Key key) override 135 | { 136 | Value value; 137 | get(key, value); 138 | return value; 139 | } 140 | 141 | // Clear cache and reclaim resources 142 | void purge() 143 | { 144 | nodeMap_.clear(); 145 | freqToFreqList_.clear(); 146 | } 147 | 148 | private: 149 | void putInternal(Key key, Value value); // Add to cache 150 | void getInternal(NodePtr node, Value& value); // Get from cache 151 | 152 | void kickOut(); // Remove expired data from cache 153 | 154 | void removeFromFreqList(NodePtr node); // Remove node from frequency list 155 | void addToFreqList(NodePtr node); // Add to frequency list 156 | 157 | void addFreqNum(); // Increase average access frequency 158 | void decreaseFreqNum(int num); // Decrease average access frequency 159 | void handleOverMaxAverageNum(); // Handle case when current average access frequency exceeds limit 160 | void updateMinFreq(); 161 | 162 | private: 163 | int capacity_; // Cache capacity 164 | int minFreq_; // Minimum access frequency (used to find node with minimum access frequency) 165 | int maxAverageNum_; // Maximum average access frequency 166 | int curAverageNum_; // Current average access frequency 167 | int curTotalNum_; // Total number of accesses to all cache 168 | std::mutex mutex_; // Mutex for synchronization 169 | NodeMap nodeMap_; // Mapping from key to cache node 170 | std::unordered_map*> freqToFreqList_; // Mapping from access frequency to frequency list 171 | }; 172 | 173 | template 174 | void RLfuCache::getInternal(NodePtr node, Value& value) 175 | { 176 | // After finding it, remove it from the low-frequency list and add it to the list with frequency +1, 177 | // Access frequency +1, then return the value 178 | value = node->value; 179 | // Remove node from the original frequency list 180 | removeFromFreqList(node); 181 | node->freq++; 182 | addToFreqList(node); 183 | // If the current node's access frequency equals minFreq+1 and its predecessor list is empty, then 184 | // the freqToFreqList_[node->freq - 1] list is empty due to node migration, so the minimum access frequency needs to be updated 185 | if (node->freq - 1 == minFreq_ && freqToFreqList_[node->freq - 1]->isEmpty()) 186 | minFreq_++; 187 | 188 | // The total access frequency and the current average access frequency both increase accordingly 189 | addFreqNum(); 190 | } 191 | 192 | template 193 | void RLfuCache::putInternal(Key key, Value value) 194 | { 195 | // If not in the cache, need to check whether the cache is full 196 | if (nodeMap_.size() == capacity_) 197 | { 198 | // If the cache is full, delete the least frequently used node and update the current average and total access frequency 199 | kickOut(); 200 | } 201 | 202 | // Create a new node, add it, and update the minimum access frequency 203 | NodePtr node = std::make_shared(key, value); 204 | nodeMap_[key] = node; 205 | addToFreqList(node); 206 | addFreqNum(); 207 | minFreq_ = std::min(minFreq_, 1); 208 | } 209 | 210 | template 211 | void RLfuCache::kickOut() 212 | { 213 | NodePtr node = freqToFreqList_[minFreq_]->getFirstNode(); 214 | removeFromFreqList(node); 215 | nodeMap_.erase(node->key); 216 | decreaseFreqNum(node->freq); 217 | } 218 | 219 | template 220 | void RLfuCache::removeFromFreqList(NodePtr node) 221 | { 222 | // Check if node is null 223 | if (!node) 224 | return; 225 | 226 | auto freq = node->freq; 227 | freqToFreqList_[freq]->removeNode(node); 228 | } 229 | 230 | template 231 | void RLfuCache::addToFreqList(NodePtr node) 232 | { 233 | // Check if node is null 234 | if (!node) 235 | return; 236 | 237 | // Add to frequency list 238 | auto freq = node->freq; 239 | if (freqToFreqList_.find(node->freq) == freqToFreqList_.end()) 240 | { 241 | // If it doesn't exist, create it 242 | freqToFreqList_[node->freq] = new FreqList(node->freq); 243 | } 244 | 245 | freqToFreqList_[freq]->addNode(node); 246 | } 247 | 248 | template 249 | void RLfuCache::addFreqNum() 250 | { 251 | curTotalNum_++; 252 | if (nodeMap_.empty()) 253 | curAverageNum_ = 0; 254 | else 255 | curAverageNum_ = curTotalNum_ / nodeMap_.size(); 256 | 257 | if (curAverageNum_ > maxAverageNum_) 258 | { 259 | handleOverMaxAverageNum(); 260 | } 261 | } 262 | 263 | template 264 | void RLfuCache::decreaseFreqNum(int num) 265 | { 266 | // Decrease average access frequency 267 | curTotalNum_ -= num; 268 | if (nodeMap_.empty()) 269 | curAverageNum_ = 0; 270 | else 271 | curAverageNum_ = curTotalNum_ / nodeMap_.size(); 272 | } 273 | 274 | template 275 | void RLfuCache::handleOverMaxAverageNum() 276 | { 277 | if (nodeMap_.empty()) 278 | return; 279 | 280 | // Current average access frequency already exceeds limit, all node access frequencies - (maxAverageNum_ / 2) 281 | for (auto it = nodeMap_.begin(); it != nodeMap_.end(); ++it) 282 | { 283 | // Check if node is null 284 | if (!it->second) 285 | continue; 286 | 287 | NodePtr node = it->second; 288 | 289 | // Remove from current frequency list 290 | removeFromFreqList(node); 291 | 292 | // Decrease frequency 293 | node->freq -= maxAverageNum_ / 2; 294 | if (node->freq < 1) node->freq = 1; 295 | 296 | // Add to new frequency list 297 | addToFreqList(node); 298 | } 299 | 300 | // Update minimum frequency 301 | updateMinFreq(); 302 | } 303 | 304 | template 305 | void RLfuCache::updateMinFreq() 306 | { 307 | minFreq_ = INT8_MAX; 308 | for (const auto& pair : freqToFreqList_) 309 | { 310 | if (pair.second && !pair.second->isEmpty()) 311 | { 312 | minFreq_ = std::min(minFreq_, pair.first); 313 | } 314 | } 315 | if (minFreq_ == INT8_MAX) 316 | minFreq_ = 1; 317 | } 318 | 319 | // The total access frequency and the current average access frequency both increase accordingly 320 | // It does not sacrifice space for time, but shards the original cache size. 321 | template 322 | class RHashLfuCache 323 | { 324 | public: 325 | RHashLfuCache(size_t capacity, int sliceNum, int maxAverageNum = 10) 326 | : sliceNum_(sliceNum > 0 ? sliceNum : std::thread::hardware_concurrency()) 327 | , capacity_(capacity) 328 | { 329 | size_t sliceSize = std::ceil(capacity_ / static_cast(sliceNum_)); // Capacity of each lfu shard 330 | for (int i = 0; i < sliceNum_; ++i) 331 | { 332 | lfuSliceCaches_.emplace_back(new RLfuCache(sliceSize, maxAverageNum)); 333 | } 334 | } 335 | 336 | void put(Key key, Value value) 337 | { 338 | // Find the corresponding lfu shard according to the key 339 | size_t sliceIndex = Hash(key) % sliceNum_; 340 | return lfuSliceCaches_[sliceIndex]->put(key, value); 341 | } 342 | 343 | bool get(Key key, Value& value) 344 | { 345 | // Find the corresponding lfu shard according to the key 346 | size_t sliceIndex = Hash(key) % sliceNum_; 347 | return lfuSliceCaches_[sliceIndex]->get(key, value); 348 | } 349 | 350 | Value get(Key key) 351 | { 352 | Value value; 353 | get(key, value); 354 | return value; 355 | } 356 | 357 | // Clear cache 358 | void purge() 359 | { 360 | for (auto& lfuSliceCache : lfuSliceCaches_) 361 | { 362 | lfuSliceCache->purge(); 363 | } 364 | } 365 | 366 | private: 367 | // Calculate the corresponding hash value for the key 368 | size_t Hash(Key key) 369 | { 370 | std::hash hashFunc; 371 | return hashFunc(key); 372 | } 373 | 374 | private: 375 | size_t capacity_; // Total cache capacity 376 | int sliceNum_; // Number of cache shards 377 | std::vector>> lfuSliceCaches_; // Container for lfu cache shards 378 | }; 379 | 380 | } // namespace RonaldCache 381 | -------------------------------------------------------------------------------- /include/LogFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "FileUtil.h" 3 | #include 4 | #include 5 | #include 6 | /** 7 | * @brief Log file management class 8 | * Responsible for log file creation, writing, rolling and flushing operations 9 | * Supports automatic log file rolling by size and time 10 | */ 11 | class LogFile 12 | { 13 | public: 14 | /** 15 | * @brief Constructor 16 | * @param basename Basic name of log file 17 | * @param rollsize When log file size reaches this many bytes, roll the file, unit: bytes 18 | * @param flushInterval Log flush interval time, default 3 seconds 19 | * @param checkEveryN_ Check if rolling is needed after writing checkEveryN_ times, default 1024 times 20 | */ 21 | LogFile(const std::string &basename, 22 | off_t rollsize, 23 | int flushInterval = 3, 24 | int checkEveryN_ = 1024); 25 | ~LogFile(); 26 | /** 27 | * @brief Append data to log file 28 | * @param data Data to be written 29 | * @param len Data length 30 | */ 31 | void append(const char *data,int len); 32 | 33 | /** 34 | * @brief Force flush buffer data to disk 35 | */ 36 | void flush(); 37 | 38 | /** 39 | * @brief Roll log file 40 | * Create new log file when log file size exceeds rollsize_ or time exceeds one day 41 | * @return Whether roll log file successfully 42 | */ 43 | bool rollFile(); 44 | 45 | private: 46 | /** 47 | * @brief Disable destructor, use smart pointer management 48 | */ 49 | 50 | 51 | /** 52 | * @brief Generate log file name 53 | * @param basename Basic name of log file 54 | * @param now Current time pointer 55 | * @return Complete log file name, format: basename.YYYYmmdd-HHMMSS.log 56 | */ 57 | static std::string getLogFileName(const std::string &basename, time_t *now); 58 | 59 | /** 60 | * @brief Append data in locked state 61 | * @param data Data to be written 62 | * @param len Data length 63 | */ 64 | void appendInlock(const char *data, int len); 65 | 66 | const std::string basename_; 67 | const off_t rollsize_; // Roll file size 68 | const int flushInterval_; // Flush time limit, default 3s 69 | const int checkEveryN_; // Write data count limit, default 1024 70 | 71 | int count_; // Write data count, cleared when exceeding limit checkEveryN_, then recount 72 | 73 | std::mutex mutex_; 74 | time_t startOfPeriod_;// Start time of current log writing period (seconds) 75 | time_t lastRoll_;// Last log file roll time (seconds) 76 | time_t lastFlush_; // Last log file flush time (seconds) 77 | std::unique_ptr file_; 78 | const static int kRollPerSeconds_ = 60*60*24; 79 | }; 80 | -------------------------------------------------------------------------------- /include/LogStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "noncopyable.h" 5 | #include "FixedBuffer.h" 6 | class GeneralTemplate : noncopyable 7 | { 8 | public: 9 | GeneralTemplate() 10 | : data_(nullptr), 11 | len_(0) 12 | {} 13 | 14 | explicit GeneralTemplate(const char* data, int len) 15 | : data_(data), 16 | len_(len) 17 | {} 18 | 19 | const char* data_; 20 | int len_; 21 | }; 22 | // LogStream class is used to manage log output stream, overloads the output stream operator << to write various types of values into internal buffer 23 | class LogStream : noncopyable 24 | { 25 | public: 26 | // Define a Buffer type, using fixed-size buffer 27 | using Buffer = FixedBuffer; 28 | 29 | // Append specified length of character data to buffer 30 | void append(const char *buffer, int len) 31 | { 32 | buffer_.append(buffer, len); // Call Buffer's append method 33 | } 34 | 35 | // Return constant reference to current buffer 36 | const Buffer &buffer() const 37 | { 38 | return buffer_; // Return current buffer 39 | } 40 | 41 | // Reset buffer, reset current pointer to buffer start 42 | void reset_buffer() 43 | { 44 | buffer_.reset(); // Call Buffer's reset method 45 | } 46 | 47 | // Overload output stream operator <<, used to write boolean value to buffer 48 | LogStream &operator<<(bool express); 49 | 50 | // Overload output stream operator <<, used to write short integer to buffer 51 | LogStream &operator<<(short number); 52 | // Overload output stream operator <<, used to write unsigned short integer to buffer 53 | LogStream &operator<<(unsigned short); 54 | // Overload output stream operator <<, used to write integer to buffer 55 | LogStream &operator<<(int); 56 | // Overload output stream operator <<, used to write unsigned integer to buffer 57 | LogStream &operator<<(unsigned int); 58 | // Overload output stream operator <<, used to write long integer to buffer 59 | LogStream &operator<<(long); 60 | // Overload output stream operator <<, used to write unsigned long integer to buffer 61 | LogStream &operator<<(unsigned long); 62 | // Overload output stream operator <<, used to write long long integer to buffer 63 | LogStream &operator<<(long long); 64 | // Overload output stream operator <<, used to write unsigned long long integer to buffer 65 | LogStream &operator<<(unsigned long long); 66 | 67 | // Overload output stream operator <<, used to write float number to buffer 68 | LogStream &operator<<(float number); 69 | // Overload output stream operator <<, used to write double precision float number to buffer 70 | LogStream &operator<<(double); 71 | 72 | // Overload output stream operator <<, used to write character to buffer 73 | LogStream &operator<<(char str); 74 | // Overload output stream operator <<, used to write C-style string to buffer 75 | LogStream &operator<<(const char *); 76 | // Overload output stream operator <<, used to write unsigned character pointer to buffer 77 | LogStream &operator<<(const unsigned char *); 78 | // Overload output stream operator <<, used to write std::string object to buffer 79 | LogStream &operator<<(const std::string &); 80 | // (const char*, int) overload 81 | LogStream& operator<<(const GeneralTemplate& g); 82 | private: 83 | // Define maximum number size constant 84 | static constexpr int kMaxNumberSize = 32; 85 | 86 | // For integer types, special processing is needed, template function used to format integer 87 | template 88 | void formatInteger(T num); 89 | 90 | // Internal buffer object 91 | Buffer buffer_; 92 | }; 93 | -------------------------------------------------------------------------------- /include/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "LogStream.h" 9 | #include 10 | #include "Timestamp.h" 11 | 12 | #define OPEN_LOGGING 13 | 14 | // SourceFile's purpose is to extract the file name 15 | class SourceFile 16 | { 17 | public: 18 | explicit SourceFile(const char* filename) 19 | : data_(filename) 20 | { 21 | /** 22 | * Find the last occurrence of / in data to get the specific file name 23 | * 2022/10/26/test.log 24 | */ 25 | const char* slash = strrchr(filename, '/'); 26 | if (slash) 27 | { 28 | data_ = slash + 1; 29 | } 30 | size_ = static_cast(strlen(data_)); 31 | } 32 | 33 | const char* data_; 34 | int size_; 35 | }; 36 | class Logger 37 | { 38 | public: 39 | enum LogLevel 40 | { 41 | TRACE, 42 | DEBUG, 43 | INFO, 44 | WARN, 45 | ERROR, 46 | FATAL, 47 | LEVEL_COUNT, 48 | }; 49 | Logger(const char *filename, int line, LogLevel level); 50 | ~Logger(); 51 | // Stream can be changed 52 | LogStream& stream() { return impl_.stream_; } 53 | 54 | 55 | // Output function and flush buffer function 56 | using OutputFunc = std::function; 57 | using FlushFunc = std::function; 58 | static void setOutput(OutputFunc); 59 | static void setFlush(FlushFunc); 60 | 61 | private: 62 | class Impl 63 | { 64 | public: 65 | using LogLevel=Logger::LogLevel; 66 | Impl(LogLevel level,int savedErrno,const char *filename, int line); 67 | void formatTime(); 68 | void finish(); // Add a suffix to a log message 69 | 70 | Timestamp time_; 71 | LogStream stream_; 72 | LogLevel level_; 73 | int line_; 74 | SourceFile basename_; 75 | }; 76 | 77 | private: 78 | Impl impl_; 79 | }; 80 | 81 | // Get errno information 82 | const char* getErrnoMsg(int savedErrno); 83 | /** 84 | * Only output when log level is less than the corresponding level 85 | * For example, if level is set to FATAL, then logLevel is greater than DEBUG and INFO, so DEBUG and INFO level logs won't be output 86 | */ 87 | #ifdef OPEN_LOGGING 88 | #define LOG_DEBUG Logger(__FILE__, __LINE__, Logger::DEBUG).stream() 89 | #define LOG_INFO Logger(__FILE__, __LINE__, Logger::INFO).stream() 90 | #define LOG_WARN Logger(__FILE__, __LINE__, Logger::WARN).stream() 91 | #define LOG_ERROR Logger(__FILE__, __LINE__, Logger::ERROR).stream() 92 | #define LOG_FATAL Logger(__FILE__, __LINE__, Logger::FATAL).stream() 93 | #else 94 | #define LOG(level) LogStream() 95 | #endif -------------------------------------------------------------------------------- /include/Poller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "noncopyable.h" 7 | #include "Timestamp.h" 8 | 9 | class Channel; 10 | class EventLoop; 11 | 12 | // Core IO multiplexing module for event dispatcher in muduo library 13 | class Poller 14 | { 15 | public: 16 | using ChannelList = std::vector; 17 | 18 | Poller(EventLoop *loop); 19 | virtual ~Poller() = default; 20 | 21 | // Preserve unified interface for all IO multiplexing 22 | virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0; 23 | virtual void updateChannel(Channel *channel) = 0; 24 | virtual void removeChannel(Channel *channel) = 0; 25 | 26 | // Check if the parameter channel is in the current Poller 27 | bool hasChannel(Channel *channel) const; 28 | 29 | // EventLoop can get the specific implementation of default IO multiplexing through this interface 30 | static Poller *newDefaultPoller(EventLoop *loop); 31 | 32 | protected: 33 | // map's key: sockfd value: channel type that sockfd belongs to 34 | using ChannelMap = std::unordered_map; 35 | ChannelMap channels_; 36 | 37 | private: 38 | EventLoop *ownerLoop_; // Define the EventLoop that Poller belongs to 39 | }; -------------------------------------------------------------------------------- /include/RICachePolicy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace RonaldCache 4 | { 5 | 6 | template 7 | class RICachePolicy 8 | { 9 | public: 10 | virtual ~RICachePolicy() {}; 11 | 12 | // Add cache interface 13 | virtual void put(Key key, Value value) = 0; 14 | 15 | // key is the input parameter, accessed value is returned as an output parameter | returns true if access is successful 16 | virtual bool get(Key key, Value& value) = 0; 17 | // If key is found in cache, return value directly 18 | virtual Value get(Key key) = 0; 19 | 20 | }; 21 | 22 | } // namespace RonaldCache -------------------------------------------------------------------------------- /include/Socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "noncopyable.h" 4 | 5 | class InetAddress; 6 | 7 | // Encapsulate socket fd 8 | class Socket : noncopyable 9 | { 10 | public: 11 | explicit Socket(int sockfd) 12 | : sockfd_(sockfd) 13 | { 14 | } 15 | ~Socket(); 16 | 17 | int fd() const { return sockfd_; } 18 | void bindAddress(const InetAddress &localaddr); 19 | void listen(); 20 | int accept(InetAddress *peeraddr); 21 | 22 | void shutdownWrite(); 23 | 24 | void setTcpNoDelay(bool on); 25 | void setReuseAddr(bool on); 26 | void setReusePort(bool on); 27 | void setKeepAlive(bool on); 28 | 29 | private: 30 | const int sockfd_; 31 | }; 32 | -------------------------------------------------------------------------------- /include/TcpConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "noncopyable.h" 8 | #include "InetAddress.h" 9 | #include "Callbacks.h" 10 | #include "Buffer.h" 11 | #include "Timestamp.h" 12 | 13 | class Channel; 14 | class EventLoop; 15 | class Socket; 16 | 17 | /** 18 | * TcpServer => Acceptor => A new user connection is obtained through the accept function to get connfd 19 | * => TcpConnection sets callback => sets to Channel => Poller => Channel callback 20 | **/ 21 | 22 | class TcpConnection : noncopyable, public std::enable_shared_from_this 23 | { 24 | public: 25 | TcpConnection(EventLoop *loop, 26 | const std::string &nameArg, 27 | int sockfd, 28 | const InetAddress &localAddr, 29 | const InetAddress &peerAddr); 30 | ~TcpConnection(); 31 | 32 | EventLoop *getLoop() const { return loop_; } 33 | const std::string &name() const { return name_; } 34 | const InetAddress &localAddress() const { return localAddr_; } 35 | const InetAddress &peerAddress() const { return peerAddr_; } 36 | 37 | bool connected() const { return state_ == kConnected; } 38 | 39 | // Send data 40 | void send(const std::string &buf); 41 | void sendFile(int fileDescriptor, off_t offset, size_t count); 42 | 43 | // Close half connection 44 | void shutdown(); 45 | 46 | void setConnectionCallback(const ConnectionCallback &cb) 47 | { connectionCallback_ = cb; } 48 | void setMessageCallback(const MessageCallback &cb) 49 | { messageCallback_ = cb; } 50 | void setWriteCompleteCallback(const WriteCompleteCallback &cb) 51 | { writeCompleteCallback_ = cb; } 52 | void setCloseCallback(const CloseCallback &cb) 53 | { closeCallback_ = cb; } 54 | void setHighWaterMarkCallback(const HighWaterMarkCallback &cb, size_t highWaterMark) 55 | { highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; } 56 | 57 | // Connection established 58 | void connectEstablished(); 59 | // Connection destroyed 60 | void connectDestroyed(); 61 | 62 | private: 63 | enum StateE 64 | { 65 | kDisconnected, // Already disconnected 66 | kConnecting, // Connecting 67 | kConnected, // Connected 68 | kDisconnecting // Disconnecting 69 | }; 70 | void setState(StateE state) { state_ = state; } 71 | 72 | void handleRead(Timestamp receiveTime); 73 | void handleWrite();//Handle write event 74 | void handleClose(); 75 | void handleError(); 76 | 77 | void sendInLoop(const void *data, size_t len); 78 | void shutdownInLoop(); 79 | void sendFileInLoop(int fileDescriptor, off_t offset, size_t count); 80 | EventLoop *loop_; // Here is baseloop or subloop determined by the number of threads created in TcpServer. If it is multi-Reactor, this loop_ points to subloop. If it is single-Reactor, this loop_ points to baseloop 81 | const std::string name_; 82 | std::atomic_int state_; 83 | bool reading_;//Whether the connection is listening for read events 84 | 85 | // Socket Channel here is similar to Acceptor. Acceptor => mainloop TcpConnection => subloop 86 | std::unique_ptr socket_; 87 | std::unique_ptr channel_; 88 | 89 | const InetAddress localAddr_; 90 | const InetAddress peerAddr_; 91 | 92 | // These callbacks are also in TcpServer. Users register by writing to TcpServer. TcpServer then passes the registered callbacks to TcpConnection, and TcpConnection registers the callbacks to Channel 93 | ConnectionCallback connectionCallback_; // Callback when there is a new connection 94 | MessageCallback messageCallback_; // Callback when there is a read/write message 95 | WriteCompleteCallback writeCompleteCallback_; // Callback after the message is sent 96 | HighWaterMarkCallback highWaterMarkCallback_; // High water mark callback 97 | CloseCallback closeCallback_; // Callback for closing connection 98 | size_t highWaterMark_; // High water mark threshold 99 | 100 | // Data buffer 101 | Buffer inputBuffer_; // Buffer for receiving data 102 | Buffer outputBuffer_; // Buffer for sending data. User sends to outputBuffer_ 103 | }; 104 | -------------------------------------------------------------------------------- /include/TcpServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * User uses muduo to write server programs 5 | **/ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "EventLoop.h" 14 | #include "Acceptor.h" 15 | #include "InetAddress.h" 16 | #include "noncopyable.h" 17 | #include "EventLoopThreadPool.h" 18 | #include "Callbacks.h" 19 | #include "TcpConnection.h" 20 | #include "Buffer.h" 21 | 22 | // Class used for server programming 23 | class TcpServer 24 | { 25 | public: 26 | using ThreadInitCallback = std::function; 27 | 28 | enum Option 29 | { 30 | kNoReusePort,// Do not allow reuse of local port 31 | kReusePort,// Allow reuse of local port 32 | }; 33 | 34 | TcpServer(EventLoop *loop, 35 | const InetAddress &listenAddr, 36 | const std::string &nameArg, 37 | Option option = kNoReusePort); 38 | ~TcpServer(); 39 | 40 | void setThreadInitCallback(const ThreadInitCallback &cb) { threadInitCallback_ = cb; } 41 | void setConnectionCallback(const ConnectionCallback &cb) { connectionCallback_ = cb; } 42 | void setMessageCallback(const MessageCallback &cb) { messageCallback_ = cb; } 43 | void setWriteCompleteCallback(const WriteCompleteCallback &cb) { writeCompleteCallback_ = cb; } 44 | 45 | // Set the number of underlying subloops 46 | void setThreadNum(int numThreads); 47 | /** 48 | * If not listening, start the server (listen). 49 | * Multiple calls have no side effects. 50 | * Thread safe. 51 | */ 52 | void start(); 53 | 54 | private: 55 | void newConnection(int sockfd, const InetAddress &peerAddr); 56 | void removeConnection(const TcpConnectionPtr &conn); 57 | void removeConnectionInLoop(const TcpConnectionPtr &conn); 58 | 59 | using ConnectionMap = std::unordered_map; 60 | 61 | EventLoop *loop_; // baseloop user-defined loop 62 | 63 | const std::string ipPort_; 64 | const std::string name_; 65 | 66 | std::unique_ptr acceptor_; // Runs in mainloop, task is to listen for new connection events 67 | 68 | std::shared_ptr threadPool_; // one loop per thread 69 | 70 | ConnectionCallback connectionCallback_; // Callback when there is a new connection 71 | MessageCallback messageCallback_; // Callback when read/write events occur 72 | WriteCompleteCallback writeCompleteCallback_; // Callback after message is sent 73 | 74 | ThreadInitCallback threadInitCallback_; // Callback for loop thread initialization 75 | int numThreads_;// Number of threads in the thread pool 76 | std::atomic_int started_; 77 | int nextConnId_; 78 | ConnectionMap connections_; // Store all connections 79 | }; -------------------------------------------------------------------------------- /include/Thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "noncopyable.h" 11 | 12 | class Thread : noncopyable 13 | { 14 | public: 15 | using ThreadFunc = std::function; 16 | 17 | explicit Thread(ThreadFunc, const std::string &name = std::string()); 18 | ~Thread(); 19 | 20 | void start(); 21 | void join(); 22 | 23 | bool started() { return started_; } 24 | pid_t tid() const { return tid_; } 25 | const std::string &name() const { return name_; } 26 | 27 | static int numCreated() { return numCreated_; } 28 | 29 | private: 30 | void setDefaultName(); 31 | 32 | bool started_; 33 | bool joined_; 34 | std::shared_ptr thread_; 35 | pid_t tid_; // Will be bound when thread is created 36 | ThreadFunc func_; // Thread callback function 37 | std::string name_; 38 | static std::atomic_int numCreated_; 39 | }; -------------------------------------------------------------------------------- /include/Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H 2 | #define TIMER_H 3 | 4 | #include "noncopyable.h" 5 | #include "Timestamp.h" 6 | #include 7 | 8 | /** 9 | * Timer is used to describe a timer 10 | * Timer callback function, next timeout moment, time interval for repeating timers, etc. 11 | */ 12 | class Timer : noncopyable 13 | { 14 | public: 15 | using TimerCallback = std::function; 16 | 17 | Timer(TimerCallback cb, Timestamp when, double interval) 18 | : callback_(move(cb)), 19 | expiration_(when), 20 | interval_(interval), 21 | repeat_(interval > 0.0) // Set to 0 for one-time timer 22 | { 23 | } 24 | 25 | void run() const 26 | { 27 | callback_(); 28 | } 29 | 30 | Timestamp expiration() const { return expiration_; } 31 | bool repeat() const { return repeat_; } 32 | 33 | // Restart timer (if it's a non-repeating event, set expiration time to 0) 34 | void restart(Timestamp now); 35 | 36 | private: 37 | const TimerCallback callback_; // Timer callback function 38 | Timestamp expiration_; // Next timeout moment 39 | const double interval_; // Timeout interval, if it's a one-time timer, this value is 0 40 | const bool repeat_; // Whether to repeat (false means it's a one-time timer) 41 | }; 42 | 43 | #endif // TIMER_H -------------------------------------------------------------------------------- /include/TimerQueue.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_QUEUE_H 2 | #define TIMER_QUEUE_H 3 | 4 | #include "Timestamp.h" 5 | #include "Channel.h" 6 | 7 | #include 8 | #include 9 | 10 | class EventLoop; 11 | class Timer; 12 | 13 | class TimerQueue 14 | { 15 | public: 16 | using TimerCallback = std::function; 17 | 18 | explicit TimerQueue(EventLoop* loop); 19 | ~TimerQueue(); 20 | 21 | // Insert timer (callback function, expiration time, whether to repeat) 22 | void addTimer(TimerCallback cb, 23 | Timestamp when, 24 | double interval); 25 | 26 | private: 27 | using Entry = std::pair; // Use timestamp as key to get timer 28 | using TimerList = std::set; // Underlying implementation uses red-black tree, automatically sorted by timestamp 29 | 30 | // Add timer in this loop 31 | // Thread safe 32 | void addTimerInLoop(Timer* timer); 33 | 34 | // Function triggered by timer read event 35 | void handleRead(); 36 | 37 | // Reset timerfd_ 38 | void resetTimerfd(int timerfd_, Timestamp expiration); 39 | 40 | // Remove all expired timers 41 | // 1. Get expired timers 42 | // 2. Reset these timers (destroy or repeat timer tasks) 43 | std::vector getExpired(Timestamp now); 44 | void reset(const std::vector& expired, Timestamp now); 45 | 46 | // Internal method to insert timer 47 | bool insert(Timer* timer); 48 | 49 | EventLoop* loop_; // The EventLoop it belongs to 50 | const int timerfd_; // timerfd is the timer interface provided by Linux 51 | Channel timerfdChannel_; // Encapsulates timerfd_ file descriptor 52 | // Timer list sorted by expiration 53 | TimerList timers_; // Timer queue (internal implementation is red-black tree) 54 | 55 | bool callingExpiredTimers_; // Indicates that expired timers are being retrieved 56 | }; 57 | 58 | #endif // TIMER_QUEUE_H -------------------------------------------------------------------------------- /include/Timestamp.h: -------------------------------------------------------------------------------- 1 | #ifndef TIME_STAMP_H 2 | #define TIME_STAMP_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Timestamp 9 | { 10 | public: 11 | Timestamp() 12 | : microSecondsSinceEpoch_(0) 13 | { 14 | } 15 | 16 | explicit Timestamp(int64_t microSecondsSinceEpoch) 17 | : microSecondsSinceEpoch_(microSecondsSinceEpoch) 18 | { 19 | } 20 | 21 | // Get current timestamp 22 | static Timestamp now(); 23 | std::string toString()const; 24 | 25 | // Format: "%4d year %02d month %02d day Week %d %02d:%02d:%02d.%06d", hours:minutes:seconds.microseconds 26 | std::string toFormattedString(bool showMicroseconds = false) const; 27 | 28 | // Return microseconds of current timestamp 29 | int64_t microSecondsSinceEpoch() const { return microSecondsSinceEpoch_; } 30 | // Return seconds of current timestamp 31 | time_t secondsSinceEpoch() const 32 | { 33 | return static_cast(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); 34 | } 35 | 36 | // Invalid timestamp, returns a Timestamp with value 0 37 | static Timestamp invalid() 38 | { 39 | return Timestamp(); 40 | } 41 | 42 | // 1 second = 1000*1000 microseconds 43 | static const int kMicroSecondsPerSecond = 1000 * 1000; 44 | 45 | private: 46 | // Represents microseconds of timestamp (microseconds elapsed since epoch) 47 | int64_t microSecondsSinceEpoch_; 48 | }; 49 | 50 | /** 51 | * Timer needs to compare timestamps, so operators need to be overloaded 52 | */ 53 | inline bool operator<(Timestamp lhs, Timestamp rhs) 54 | { 55 | return lhs.microSecondsSinceEpoch() < rhs.microSecondsSinceEpoch(); 56 | } 57 | 58 | inline bool operator==(Timestamp lhs, Timestamp rhs) 59 | { 60 | return lhs.microSecondsSinceEpoch() == rhs.microSecondsSinceEpoch(); 61 | } 62 | 63 | // If it's a repeating timer task, this timestamp will be increased 64 | inline Timestamp addTime(Timestamp timestamp, double seconds) 65 | { 66 | // Convert delay seconds to microseconds 67 | int64_t delta = static_cast(seconds * Timestamp::kMicroSecondsPerSecond); 68 | // Return timestamp after adding time 69 | return Timestamp(timestamp.microSecondsSinceEpoch() + delta); 70 | } 71 | 72 | #endif // TIME_STAMP_H -------------------------------------------------------------------------------- /include/memoryPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace memoryPool 10 | { 11 | #define MEMORY_POOL_NUM 64 12 | #define SLOT_BASE_SIZE 8 13 | #define MAX_SLOT_SIZE 512 14 | 15 | 16 | /* The slot size of a specific memory pool cannot be determined, because each memory pool has different slot sizes (multiples of 8) 17 | So the sizeof of this slot structure is not the actual slot size */ 18 | struct Slot 19 | { 20 | Slot* next; 21 | }; 22 | 23 | class MemoryPool 24 | { 25 | public: 26 | MemoryPool(size_t BlockSize = 4096); 27 | ~MemoryPool(); 28 | 29 | void init(size_t); 30 | 31 | void* allocate(); 32 | void deallocate(void*); 33 | private: 34 | void allocateNewBlock(); 35 | size_t padPointer(char* p, size_t align); 36 | 37 | private: 38 | int BlockSize_; // Memory block size 39 | int SlotSize_; // Slot size 40 | Slot* firstBlock_; // Points to the first actual memory block managed by the memory pool 41 | Slot* curSlot_; // Points to the current unused slot 42 | Slot* freeList_; // Points to free slots (slots that have been used and then released) 43 | Slot* lastSlot_; // As a position identifier for the last element that can be stored in the current memory block 44 | std::mutex mutexForFreeList_; // Ensure atomicity of freeList_ in multi-threaded operations 45 | std::mutex mutexForBlock_; // Ensure unnecessary repeated memory allocation in multi-threaded situations 46 | }; 47 | 48 | 49 | class HashBucket 50 | { 51 | public: 52 | static void initMemoryPool(); 53 | static MemoryPool& getMemoryPool(int index); 54 | 55 | static void* useMemory(size_t size) 56 | { 57 | if (size <= 0) 58 | return nullptr; 59 | if (size > MAX_SLOT_SIZE) // For memory larger than 512 bytes, use new 60 | return operator new(size); 61 | 62 | // Equivalent to size / 8 rounded up (because allocated memory can only be larger, not smaller) 63 | return getMemoryPool(((size + 7) / SLOT_BASE_SIZE) - 1).allocate(); 64 | } 65 | 66 | static void freeMemory(void* ptr, size_t size) 67 | { 68 | if (!ptr) 69 | return; 70 | if (size > MAX_SLOT_SIZE) 71 | { 72 | operator delete(ptr); 73 | return; 74 | } 75 | 76 | getMemoryPool(((size + 7) / SLOT_BASE_SIZE) - 1).deallocate(ptr); 77 | } 78 | 79 | template 80 | friend T* newElement(Args&&... args); 81 | 82 | template 83 | friend void deleteElement(T* p); 84 | }; 85 | 86 | template 87 | T* newElement(Args&&... args) 88 | { 89 | T* p = nullptr; 90 | // Select appropriate memory pool to allocate memory based on element size 91 | if ((p = reinterpret_cast(HashBucket::useMemory(sizeof(T)))) != nullptr) 92 | // Construct object on allocated memory 93 | new(p) T(std::forward(args)...); 94 | 95 | return p; 96 | } 97 | 98 | template 99 | void deleteElement(T* p) 100 | { 101 | // Object destruction 102 | if (p) 103 | { 104 | p->~T(); 105 | // Memory recycling 106 | HashBucket::freeMemory(reinterpret_cast(p), sizeof(T)); 107 | } 108 | } 109 | 110 | } // namespace memoryPool -------------------------------------------------------------------------------- /include/noncopyable.h: -------------------------------------------------------------------------------- 1 | #pragma once // Prevent header file from being included multiple times 2 | 3 | /** 4 | * After inheriting from noncopyable, derived class objects can be constructed and destructed normally, but cannot be copy constructed or assignment constructed 5 | **/ 6 | class noncopyable 7 | { 8 | public: 9 | noncopyable(const noncopyable &) = delete; 10 | noncopyable &operator=(const noncopyable &) = delete; 11 | // void operator=(const noncopyable &) = delete; // muduo changes return value to void, which is actually fine 12 | protected: 13 | noncopyable() = default; 14 | ~noncopyable() = default; 15 | }; -------------------------------------------------------------------------------- /lib/liblog_lib.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronald-debugging/Ronald-webserver/5bc27cb51466a32851dc6888e7d354c60cf09206/lib/liblog_lib.so -------------------------------------------------------------------------------- /lib/libmemory_lib.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronald-debugging/Ronald-webserver/5bc27cb51466a32851dc6888e7d354c60cf09206/lib/libmemory_lib.so -------------------------------------------------------------------------------- /lib/libsrc_lib.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronald-debugging/Ronald-webserver/5bc27cb51466a32851dc6888e7d354c60cf09206/lib/libsrc_lib.so -------------------------------------------------------------------------------- /log/AsyncLogging.cc: -------------------------------------------------------------------------------- 1 | #include "AsyncLogging.h" 2 | #include 3 | AsyncLogging::AsyncLogging(const std::string &basename, off_t rollSize, int flushInterval) 4 | : 5 | flushInterval_(flushInterval), 6 | running_(false), 7 | basename_(basename), 8 | rollSize_(rollSize), 9 | thread_(std::bind(&AsyncLogging::threadFunc, this), "Logging"), 10 | mutex_(), 11 | cond_(), 12 | currentBuffer_(new LargeBuffer), 13 | nextBuffer_(new LargeBuffer), 14 | buffers_() 15 | { 16 | currentBuffer_->bzero(); 17 | nextBuffer_->bzero(); 18 | buffers_.reserve(16); // Only maintain queue length of 2~16 19 | } 20 | // This function is called to handle the process where frontend passes LOG_XXX<<"..." to backend, and backend writes log messages to log file 21 | void AsyncLogging::append(const char *logline, int len) 22 | { 23 | std::lock_guard lg(mutex_); 24 | // Buffer has enough space to write 25 | if (currentBuffer_->avail() > static_cast(len)) 26 | { 27 | currentBuffer_->append(logline, len); 28 | } 29 | else 30 | { 31 | buffers_.push_back(std::move(currentBuffer_)); 32 | 33 | if (nextBuffer_) 34 | { 35 | currentBuffer_ = std::move(nextBuffer_); 36 | } 37 | else 38 | { 39 | currentBuffer_.reset(new LargeBuffer); 40 | } 41 | currentBuffer_->append(logline, len); 42 | // Wake up backend thread to write to disk 43 | cond_.notify_one(); 44 | } 45 | } 46 | 47 | void AsyncLogging::threadFunc() 48 | { 49 | // output interface for writing to disk 50 | LogFile output(basename_, rollSize_); 51 | BufferPtr newbuffer1(new LargeBuffer); // Create new buffer to replace currentbuffer_ 52 | BufferPtr newbuffer2(new LargeBuffer); // Create new buffer2 to replace newBuffer_, to prevent frontend from being unable to write when backend buffers are full 53 | newbuffer1->bzero(); 54 | newbuffer2->bzero(); 55 | // Buffer array set to 16, used to swap with frontend buffer array 56 | BufferVector buffersToWrite; 57 | buffersToWrite.reserve(16); 58 | while (running_) 59 | { 60 | { 61 | // Mutex protection ensures other frontend threads cannot write to frontend buffer 62 | std::unique_lock lg(mutex_); 63 | if (buffers_.empty()) 64 | { 65 | cond_.wait_for(lg, std::chrono::seconds(3)); 66 | } 67 | buffers_.push_back(std::move(currentBuffer_)); 68 | currentBuffer_ = std::move(newbuffer1); 69 | if (!nextBuffer_) 70 | { 71 | nextBuffer_ = std::move(newbuffer2); 72 | } 73 | buffersToWrite.swap(buffers_); 74 | } 75 | // Take data from write buffer and write to disk through LogFile interface 76 | for (auto &buffer : buffersToWrite) 77 | { 78 | output.append(buffer->data(), buffer->length()); 79 | } 80 | 81 | if (buffersToWrite.size() > 2) 82 | { 83 | buffersToWrite.resize(2); 84 | } 85 | 86 | if (!newbuffer1) 87 | { 88 | newbuffer1 = std::move(buffersToWrite.back()); 89 | buffersToWrite.pop_back(); 90 | newbuffer1->reset(); 91 | } 92 | if (!newbuffer2) 93 | { 94 | newbuffer2 = std::move(buffersToWrite.back()); 95 | buffersToWrite.pop_back(); 96 | newbuffer2->reset(); 97 | } 98 | buffersToWrite.clear(); // Clear backend buffer queue 99 | output.flush(); // Clear file buffer 100 | } 101 | output.flush(); // Ensure everything is flushed 102 | } -------------------------------------------------------------------------------- /log/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Get all source files in current directory 2 | file(GLOB LOG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/*cc) 3 | 4 | # Create static or shared library 5 | add_library(log_lib SHARED ${LOG_FILE}) -------------------------------------------------------------------------------- /log/CurrentThread.cc: -------------------------------------------------------------------------------- 1 | #include "CurrentThread.h" 2 | 3 | namespace CurrentThread 4 | { 5 | thread_local int t_cachedTid = 0; // Define thread local variable in source file 6 | void cacheTid() 7 | { 8 | if (t_cachedTid == 0) 9 | { 10 | t_cachedTid = static_cast(::syscall(SYS_gettid)); // Ensure syscall and SYS_gettid are defined 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /log/FileUtil.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FileUtil.h" 3 | 4 | FileUtil::FileUtil(std::string &file_name) : file_(::fopen(file_name.c_str(), "ae")), 5 | writtenBytes_(0) 6 | { 7 | // Set file_ buffer to local buffer to reduce IO operations 8 | ::setbuffer(file_, buffer_, sizeof(buffer_)); 9 | } 10 | FileUtil::~FileUtil() 11 | { 12 | if (file_) 13 | { 14 | ::fclose(file_); 15 | } 16 | } 17 | // Write data to file 18 | void FileUtil::append(const char *data, size_t len) 19 | { 20 | size_t writen = 0; 21 | while (writen != len) 22 | { 23 | size_t remain = len - writen; 24 | size_t n = write(data + writen, remain); 25 | if (n != remain) 26 | { 27 | // Error checking 28 | int err = ferror(file_); 29 | if (err) 30 | { 31 | fprintf(stderr, "AppendFile::append() failed %s\n", strerror(err)); 32 | clearerr(file_); // Clear file pointer error flag 33 | break; 34 | } 35 | } 36 | writen += n; 37 | } 38 | writtenBytes_ += writen; 39 | } 40 | 41 | void FileUtil::flush() 42 | { 43 | ::fflush(file_); 44 | } 45 | // Actually write data to file 46 | size_t FileUtil::write(const char *data, size_t len) 47 | { 48 | // Using non-thread-safe fwrite() for performance reasons 49 | return ::fwrite_unlocked(data, 1, len, file_); 50 | } -------------------------------------------------------------------------------- /log/LogFile.cc: -------------------------------------------------------------------------------- 1 | #include "LogFile.h" 2 | LogFile::LogFile(const std::string &basename, 3 | off_t rollsize, 4 | int flushInterval, 5 | int checkEveryN ) : basename_(basename), 6 | rollsize_(rollsize), 7 | flushInterval_(flushInterval), 8 | checkEveryN_(checkEveryN), 9 | startOfPeriod_(0), 10 | lastRoll_(0), 11 | lastFlush_(0) 12 | { 13 | // When restarting, there might not be a log file, so when constructing the logFile object, directly call rollfile() to create a new log file 14 | rollFile(); 15 | } 16 | LogFile::~LogFile() = default; 17 | void LogFile::append(const char *data, int len) 18 | { 19 | std::lock_guard lg(mutex_); 20 | appendInlock(data, len); 21 | } 22 | void LogFile::flush() 23 | { 24 | file_->flush(); 25 | } 26 | // Roll log file 27 | bool LogFile::rollFile() 28 | { 29 | time_t now = 0; 30 | std::string filename = getLogFileName(basename_, &now); 31 | time_t start = now / kRollPerSeconds_ * kRollPerSeconds_; 32 | if (now > lastRoll_) 33 | { 34 | lastFlush_ = now; 35 | lastRoll_ = now; 36 | startOfPeriod_ = start; 37 | // Make file_ point to a file named filename, equivalent to creating a new file, but each rollfile() call creates a new file object to write data to the log file 38 | file_.reset(new FileUtil(filename)); 39 | return true; 40 | } 41 | return false; 42 | } 43 | // Log format: basename+now+".log" 44 | std::string LogFile::getLogFileName(const std::string &basename, time_t *now) 45 | { 46 | std::string filename; 47 | filename.reserve(basename.size() + 64); 48 | filename = basename; 49 | 50 | char timebuf[32]; 51 | struct tm tm; 52 | *now = time(NULL); // Get current time 53 | localtime_r(now, &tm); 54 | strftime(timebuf, sizeof(timebuf), ".%Y%m%d-%H%M%S", &tm); 55 | 56 | filename += timebuf; 57 | filename += ".log"; 58 | return filename; 59 | } 60 | void LogFile::appendInlock(const char *data, int len) 61 | { 62 | file_->append(data, len); 63 | 64 | time_t now = time(NULL); // Current time 65 | ++count_; 66 | 67 | // 1. Check if log rolling is needed 68 | if (file_->writtenBytes() > rollsize_) 69 | { 70 | rollFile(); 71 | } 72 | else if (count_ >= checkEveryN_) // After reaching write count threshold, perform check 73 | { 74 | count_ = 0; 75 | 76 | // Roll log based on time period 77 | time_t thisPeriod = now / kRollPerSeconds_ * kRollPerSeconds_; 78 | if (thisPeriod != startOfPeriod_) 79 | { 80 | rollFile(); 81 | } 82 | } 83 | 84 | // 2. Check if log needs to be flushed (independent flush logic) 85 | if (now - lastFlush_ > flushInterval_) 86 | { 87 | lastFlush_ = now; 88 | file_->flush(); 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /log/LogStream.cc: -------------------------------------------------------------------------------- 1 | #include "LogStream.h" 2 | #include 3 | 4 | static const char digits[] = "9876543210123456789"; 5 | 6 | template 7 | void LogStream::formatInteger(T num) 8 | { 9 | if (buffer_.avail() >= kMaxNumberSize) 10 | { 11 | char *start = buffer_.current(); 12 | char *cur = start; 13 | static const char *zero = digits + 9; 14 | bool negative = (num < 0); // Check if num is negative 15 | do 16 | { 17 | int remainder = static_cast(num % 10); 18 | (*cur++) = zero[remainder]; 19 | num /= 10; 20 | } while (num != 0); 21 | if (negative) 22 | { 23 | *cur++ = '-'; 24 | } 25 | *cur = '\0'; 26 | std::reverse(start, cur); 27 | int length = static_cast(cur - start); 28 | buffer_.add(length); 29 | } 30 | } 31 | // Overload the output stream operator << to write boolean values into the buffer 32 | LogStream &LogStream::operator<<(bool express) { 33 | buffer_.append(express ? "true" : "false", express ? 4 : 5); 34 | return *this; 35 | } 36 | 37 | // Overload the output stream operator << to write short integers into the buffer 38 | LogStream &LogStream::operator<<(short number) { 39 | formatInteger(number); 40 | return *this; 41 | } 42 | 43 | // Overload the output stream operator << to write unsigned short integers into the buffer 44 | LogStream &LogStream::operator<<(unsigned short number) { 45 | formatInteger(number); 46 | return *this; 47 | } 48 | 49 | // Overload the output stream operator << to write integers into the buffer 50 | LogStream &LogStream::operator<<(int number) { 51 | formatInteger(number); 52 | return *this; 53 | } 54 | 55 | // Overload the output stream operator << to write unsigned integers into the buffer 56 | LogStream &LogStream::operator<<(unsigned int number) { 57 | formatInteger(number); 58 | return *this; 59 | } 60 | 61 | // Overload the output stream operator << to write long integers into the buffer 62 | LogStream &LogStream::operator<<(long number) { 63 | formatInteger(number); 64 | return *this; 65 | } 66 | 67 | // Overload the output stream operator << to write unsigned long integers into the buffer 68 | LogStream &LogStream::operator<<(unsigned long number) { 69 | formatInteger(number); 70 | return *this; 71 | } 72 | 73 | // Overload the output stream operator << to write long long integers into the buffer 74 | LogStream &LogStream::operator<<(long long number) { 75 | formatInteger(number); 76 | return *this; 77 | } 78 | 79 | // Overload the output stream operator << to write unsigned long long integers into the buffer 80 | LogStream &LogStream::operator<<(unsigned long long number) { 81 | formatInteger(number); 82 | return *this; 83 | } 84 | 85 | // Overload the output stream operator << to write floating point numbers into the buffer 86 | LogStream &LogStream::operator<<(float number) { 87 | *this<(number); 88 | return *this; 89 | } 90 | 91 | // Overload the output stream operator << to write double precision floating point numbers into the buffer 92 | LogStream &LogStream::operator<<(double number) { 93 | char buffer[32]; 94 | snprintf(buffer, sizeof(buffer), "%.12g", number); 95 | buffer_.append(buffer, strlen(buffer)); 96 | return *this; 97 | } 98 | 99 | // Overload the output stream operator << to write characters into the buffer 100 | LogStream &LogStream::operator<<(char str) { 101 | buffer_.append(&str, 1); 102 | return *this; 103 | } 104 | 105 | // Overload the output stream operator << to write C-style character strings into the buffer 106 | LogStream &LogStream::operator<<(const char *str) { 107 | buffer_.append(str, strlen(str)); 108 | return *this; 109 | } 110 | 111 | // Overload the output stream operator << to write unsigned character pointers into the buffer 112 | LogStream &LogStream::operator<<(const unsigned char *str) { 113 | buffer_.append(reinterpret_cast(str), strlen(reinterpret_cast(str))); 114 | return *this; 115 | } 116 | 117 | // Overload the output stream operator << to write std::string objects into the buffer 118 | LogStream &LogStream::operator<<(const std::string &str) { 119 | buffer_.append(str.c_str(), str.size()); 120 | return *this; 121 | } 122 | 123 | LogStream& LogStream::operator<<(const GeneralTemplate& g) 124 | { 125 | buffer_.append(g.data_, g.len_); 126 | return *this; 127 | } -------------------------------------------------------------------------------- /memory/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Get all source files in current directory 2 | file(GLOB MEMORY_FILE ${CMAKE_CURRENT_SOURCE_DIR}/*cc) 3 | 4 | # Create static or shared library 5 | add_library(memory_lib SHARED ${MEMORY_FILE}) -------------------------------------------------------------------------------- /memory/memoryPool.cc: -------------------------------------------------------------------------------- 1 | #include "memoryPool.h" 2 | 3 | namespace memoryPool 4 | { 5 | MemoryPool::MemoryPool(size_t BlockSize) 6 | : BlockSize_ (BlockSize) 7 | {} 8 | 9 | MemoryPool::~MemoryPool() 10 | { 11 | // Delete consecutive blocks 12 | Slot* cur = firstBlock_; 13 | while (cur) 14 | { 15 | Slot* next = cur->next; 16 | // Equivalent to free(reinterpret_cast(firstBlock_)); 17 | // Convert to void pointer, because void type doesn't need to call destructor, only free space 18 | operator delete(reinterpret_cast(cur)); 19 | cur = next; 20 | } 21 | } 22 | 23 | void MemoryPool::init(size_t size) 24 | { 25 | assert(size > 0); 26 | SlotSize_ = size; 27 | firstBlock_ = nullptr; 28 | curSlot_ = nullptr; 29 | freeList_ = nullptr; 30 | lastSlot_ = nullptr; 31 | } 32 | 33 | void* MemoryPool::allocate() 34 | { 35 | // Prioritize using memory slots from the free list 36 | if (freeList_ != nullptr) 37 | { 38 | { 39 | std::lock_guard lock(mutexForFreeList_); 40 | if (freeList_ != nullptr) 41 | { 42 | Slot* temp = freeList_; 43 | freeList_ = freeList_->next; 44 | return temp; 45 | } 46 | } 47 | } 48 | 49 | Slot* temp; 50 | { 51 | std::lock_guard lock(mutexForBlock_); 52 | if (curSlot_ >= lastSlot_) 53 | { 54 | // Current memory block has no available slots, allocate a new memory block 55 | allocateNewBlock(); 56 | } 57 | 58 | temp = curSlot_; 59 | // Cannot directly do curSlot_ += SlotSize_ here because curSlot_ is of type Slot*, so need to divide by SlotSize_ and add 1 60 | curSlot_ += SlotSize_ / sizeof(Slot); 61 | } 62 | 63 | return temp; 64 | } 65 | 66 | void MemoryPool::deallocate(void* ptr) 67 | { 68 | if (ptr) 69 | { 70 | // Recycle memory, insert memory into free list through head insertion 71 | std::lock_guard lock(mutexForFreeList_); 72 | reinterpret_cast(ptr)->next = freeList_; 73 | freeList_ = reinterpret_cast(ptr); 74 | } 75 | } 76 | 77 | void MemoryPool::allocateNewBlock() 78 | { 79 | //std::cout << "Apply for a memory block, SlotSize: " << SlotSize_ << std::endl; 80 | // Insert new memory block using head insertion 81 | void* newBlock = operator new(BlockSize_); 82 | reinterpret_cast(newBlock)->next = firstBlock_; 83 | firstBlock_ = reinterpret_cast(newBlock); 84 | 85 | char* body = reinterpret_cast(newBlock) + sizeof(Slot*); 86 | size_t paddingSize = padPointer(body, SlotSize_); // Calculate padding size needed for alignment 87 | curSlot_ = reinterpret_cast(body + paddingSize); 88 | 89 | // If exceeding this mark position, it means the memory block has no available slots, need to request a new memory block from the system 90 | lastSlot_ = reinterpret_cast(reinterpret_cast(newBlock) + BlockSize_ - SlotSize_ + 1); 91 | 92 | freeList_ = nullptr; 93 | } 94 | 95 | // Align pointer to multiple of slot size 96 | size_t MemoryPool::padPointer(char* p, size_t align) 97 | { 98 | // align is the slot size 99 | return (align - reinterpret_cast(p)) % align; 100 | } 101 | 102 | void HashBucket::initMemoryPool() 103 | { 104 | for (int i = 0; i < MEMORY_POOL_NUM; i++) 105 | { 106 | getMemoryPool(i).init((i + 1) * SLOT_BASE_SIZE); 107 | } 108 | } 109 | 110 | // Singleton pattern 111 | MemoryPool& HashBucket::getMemoryPool(int index) 112 | { 113 | static MemoryPool memoryPool[MEMORY_POOL_NUM]; 114 | return memoryPool[index]; 115 | } 116 | 117 | } // namespace memoryPool 118 | -------------------------------------------------------------------------------- /src/Acceptor.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int createNonblocking() 10 | { 11 | int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); 12 | if (sockfd < 0) 13 | { 14 | LOG_FATAL << "listen socket create err " << errno; 15 | } 16 | return sockfd; 17 | } 18 | 19 | Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport) 20 | : loop_(loop) 21 | , acceptSocket_(createNonblocking()) 22 | , acceptChannel_(loop, acceptSocket_.fd()) 23 | , listenning_(false) 24 | { 25 | acceptSocket_.setReuseAddr(true); 26 | acceptSocket_.setReusePort(true); 27 | acceptSocket_.bindAddress(listenAddr); 28 | // TcpServer::start() => Acceptor.listen() If there is a new user connection, execute a callback (accept => connfd => package into Channel => wake up subloop) 29 | // baseloop detects an event => acceptChannel_(listenfd) => execute this callback function 30 | acceptChannel_.setReadCallback( 31 | std::bind(&Acceptor::handleRead, this)); 32 | } 33 | 34 | Acceptor::~Acceptor() 35 | { 36 | acceptChannel_.disableAll(); // Remove interested events from Poller 37 | acceptChannel_.remove(); // Call EventLoop->removeChannel => Poller->removeChannel to remove the corresponding part from Poller's ChannelMap 38 | } 39 | 40 | void Acceptor::listen() 41 | { 42 | listenning_ = true; 43 | acceptSocket_.listen(); // listen 44 | acceptChannel_.enableReading(); // Register acceptChannel_ to Poller !Important 45 | } 46 | 47 | // listenfd has an event, meaning there is a new user connection 48 | void Acceptor::handleRead() 49 | { 50 | InetAddress peerAddr; 51 | int connfd = acceptSocket_.accept(&peerAddr); 52 | if (connfd >= 0) 53 | { 54 | if (NewConnectionCallback_) 55 | { 56 | NewConnectionCallback_(connfd, peerAddr); // Poll to find subLoop, wake up and distribute the current new client's Channel 57 | } 58 | else 59 | { 60 | ::close(connfd); 61 | } 62 | } 63 | else 64 | { 65 | LOG_ERROR<<"accept Err"; 66 | if (errno == EMFILE) 67 | { 68 | LOG_ERROR<<"sockfd reached limit"; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/Buffer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | /** 8 | * Read data from fd, Poller works in LT mode 9 | * Buffer has a size limit! But when reading data from fd, we don't know the final size of the TCP data 10 | * 11 | * @description: The method to read from socket to buffer uses readv to read into buffer_ first, 12 | * If buffer_ space is not enough, it will read into a 65536-byte space on the stack, then append 13 | * to buffer_. This approach avoids the overhead of system calls while not affecting data reception. 14 | **/ 15 | ssize_t Buffer::readFd(int fd, int *saveErrno) 16 | { 17 | // Extra stack space, used when reading from socket and buffer_ is temporarily insufficient, 18 | // temporarily store data until buffer_ is reallocated with enough space, then swap data to buffer_. 19 | char extrabuf[65536] = {0}; // Stack memory space 65536/1024 = 64KB 20 | 21 | /* 22 | struct iovec { 23 | ptr_t iov_base; // The buffer pointed to by iov_base stores the data received by readv or the data to be sent by writev 24 | size_t iov_len; // In different situations, iov_len determines the maximum length to receive or the actual length to write 25 | }; 26 | */ 27 | 28 | // Use iovec to allocate two consecutive buffers 29 | struct iovec vec[2]; 30 | const size_t writable = writableBytes(); // This is the remaining writable space in the buffer 31 | 32 | // The first buffer, points to writable space 33 | vec[0].iov_base = begin() + writerIndex_; 34 | vec[0].iov_len = writable; 35 | // The second buffer, points to stack space 36 | vec[1].iov_base = extrabuf; 37 | vec[1].iov_len = sizeof extrabuf; 38 | 39 | // when there is enough space in this buffer, don't read into extrabuf. 40 | // when extrabuf is used, we read 128k-1 bytes at most. 41 | // The reason for saying at most 128k-1 bytes is: if writable is 64k-1, then two buffers are needed, the first is 64k-1, the second is 64k, so at most 128k-1 42 | // If the first buffer >= 64k, then only one buffer is used and the stack space extrabuf[65536] is not used 43 | const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; 44 | const ssize_t n = ::readv(fd, vec, iovcnt); 45 | 46 | if (n < 0) 47 | { 48 | *saveErrno = errno; 49 | } 50 | else if (n <= writable) // Buffer is enough 51 | { 52 | writerIndex_ += n; 53 | } 54 | else // extrabuf is also written 55 | { 56 | writerIndex_ = buffer_.size(); 57 | append(extrabuf, n - writable); // writerIndex_ begins to write from buffer_.size() 58 | } 59 | return n; 60 | } 61 | 62 | // inputBuffer_.readFd reads data from the peer into inputBuffer_, moving the writerIndex_ pointer 63 | // outputBuffer_.writeFd writes data to outputBuffer_, starting from readerIndex_, can write readableBytes() bytes 64 | /** 65 | * @description: The method to write data from buffer to fd uses writev 66 | * @param {int} fd - Socket file descriptor 67 | * @param {int} *saveErrno - Error number 68 | * @return {ssize_t} - Number of bytes written 69 | */ 70 | ssize_t Buffer::writeFd(int fd, int *saveErrno) 71 | { 72 | ssize_t n = ::write(fd, peek(), readableBytes()); 73 | if (n < 0) 74 | { 75 | *saveErrno = errno; 76 | } 77 | return n; 78 | } -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Get all source files in current directory 2 | file(GLOB SRC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/*cc) 3 | list(REMOVE_ITEM SRC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/main.cc) 4 | 5 | # Create shared library 6 | add_library(src_lib SHARED ${SRC_FILE}) 7 | 8 | # Create executable 9 | add_executable(main main.cc) 10 | 11 | # Link necessary libraries 12 | target_link_libraries(main src_lib memory_lib log_lib ${LIBS}) 13 | 14 | -------------------------------------------------------------------------------- /src/Channel.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | const int Channel::kNoneEvent = 0; // No event 8 | const int Channel::kReadEvent = EPOLLIN | EPOLLPRI; // Read event 9 | const int Channel::kWriteEvent = EPOLLOUT; // Write event 10 | 11 | // EventLoop: ChannelList Poller 12 | Channel::Channel(EventLoop *loop, int fd) 13 | : loop_(loop) 14 | , fd_(fd) 15 | , events_(0) 16 | , revents_(0) 17 | , index_(-1) 18 | , tied_(false) 19 | { 20 | } 21 | 22 | Channel::~Channel() 23 | { 24 | } 25 | 26 | // When is the channel's tie method called? TcpConnection => channel 27 | /* 28 | * TcpConnection registers the callback functions corresponding to Channel, and the passed-in callback functions are all member methods of TcpConnection. 29 | * Therefore, it can be said that: Channel's end must be later than TcpConnection object! 30 | * Here, tie is used to solve the lifetime issue between TcpConnection and Channel, ensuring that the Channel object can be destroyed before TcpConnection is destroyed. 31 | */ 32 | void Channel::tie(const std::shared_ptr &obj) 33 | { 34 | tie_ = obj; 35 | tied_ = true; 36 | } 37 | //update and remove => EpollPoller updates the channel's state in poller 38 | /** 39 | * When the events of the channel represented by fd change, update is responsible for updating the corresponding events of fd in poller 40 | **/ 41 | void Channel::update() 42 | { 43 | // Through the eventloop to which the channel belongs, call the corresponding method of poller to register the events of fd 44 | loop_->updateChannel(this); 45 | } 46 | 47 | // Remove the current channel from the eventloop to which it belongs 48 | void Channel::remove() 49 | { 50 | loop_->removeChannel(this); 51 | } 52 | 53 | void Channel::handleEvent(Timestamp receiveTime) 54 | { 55 | if (tied_) 56 | { 57 | std::shared_ptr guard = tie_.lock(); 58 | if (guard) 59 | { 60 | handleEventWithGuard(receiveTime); 61 | } 62 | // If the upgrade fails, do nothing and indicate that the Channel's TcpConnection object no longer exists 63 | } 64 | else 65 | { 66 | handleEventWithGuard(receiveTime); 67 | } 68 | } 69 | 70 | void Channel::handleEventWithGuard(Timestamp receiveTime) 71 | { 72 | LOG_INFO<<"channel handleEvent revents:"< 2 | 3 | namespace CurrentThread 4 | { 5 | thread_local int t_cachedTid = 0; // Define thread local variable in source file 6 | void cacheTid() 7 | { 8 | if (t_cachedTid == 0) 9 | { 10 | t_cachedTid = static_cast(::syscall(SYS_gettid)); // Ensure syscall and SYS_gettid are defined 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/DefaultPoller.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | Poller *Poller::newDefaultPoller(EventLoop *loop) 7 | { 8 | if (::getenv("MUDUO_USE_POLL")) 9 | { 10 | return nullptr; // Generate an instance of poll 11 | } 12 | else 13 | { 14 | return new EPollPoller(loop); // Generate an instance of epoll 15 | } 16 | } -------------------------------------------------------------------------------- /src/EPollPoller.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | const int kNew = -1; // A channel has not yet been added to Poller // The member index_ of the channel is initialized to -1 10 | const int kAdded = 1; // A channel has already been added to Poller 11 | const int kDeleted = 2; // A channel has been deleted from Poller 12 | 13 | EPollPoller::EPollPoller(EventLoop *loop) 14 | : Poller(loop) 15 | , epollfd_(::epoll_create1(EPOLL_CLOEXEC)) 16 | , events_(kInitEventListSize) // vector(16) 17 | { 18 | if (epollfd_ < 0) 19 | { 20 | LOG_FATAL<<"epoll_create error:%d \n"<(events_.size()), timeoutMs); 35 | int saveErrno = errno; 36 | Timestamp now(Timestamp::now()); 37 | 38 | if (numEvents > 0) 39 | { 40 | LOG_INFO<<"events happend"< EventLoop updateChannel removeChannel => Poller updateChannel removeChannel 63 | void EPollPoller::updateChannel(Channel *channel) 64 | { 65 | const int index = channel->index(); 66 | LOG_INFO<<"func =>"<<"fd"<fd()<<"events="<events()<<"index="<fd(); 73 | channels_[fd] = channel; 74 | } 75 | else // index == kDeleted 76 | { 77 | } 78 | channel->set_index(kAdded); 79 | update(EPOLL_CTL_ADD, channel); 80 | } 81 | else // The channel has already been registered in Poller 82 | { 83 | int fd = channel->fd(); 84 | if (channel->isNoneEvent()) 85 | { 86 | update(EPOLL_CTL_DEL, channel); 87 | channel->set_index(kDeleted); 88 | } 89 | else 90 | { 91 | update(EPOLL_CTL_MOD, channel); 92 | } 93 | } 94 | } 95 | 96 | // Delete the channel from Poller 97 | void EPollPoller::removeChannel(Channel *channel) 98 | { 99 | int fd = channel->fd(); 100 | channels_.erase(fd); 101 | 102 | LOG_INFO<<"removeChannel fd="<index(); 105 | if (index == kAdded) 106 | { 107 | update(EPOLL_CTL_DEL, channel); 108 | } 109 | channel->set_index(kNew); 110 | } 111 | 112 | // Fill in active connections 113 | void EPollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const 114 | { 115 | for (int i = 0; i < numEvents; ++i) 116 | { 117 | Channel *channel = static_cast(events_[i].data.ptr); 118 | channel->set_revents(events_[i].events); 119 | activeChannels->push_back(channel); // EventLoop now has the list of all channels that have events returned by its Poller 120 | } 121 | } 122 | 123 | // Update channel channel, which is actually calling epoll_ctl add/mod/del 124 | void EPollPoller::update(int operation, Channel *channel) 125 | { 126 | epoll_event event; 127 | ::memset(&event, 0, sizeof(event)); 128 | 129 | int fd = channel->fd(); 130 | 131 | event.events = channel->events(); 132 | event.data.fd = fd; 133 | event.data.ptr = channel; 134 | 135 | if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) 136 | { 137 | if (operation == EPOLL_CTL_DEL) 138 | { 139 | LOG_ERROR<<"epoll_ctl del error:"< 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Prevent a thread from creating multiple EventLoop instances 13 | thread_local EventLoop *t_loopInThisThread = nullptr; 14 | 15 | // Define the default timeout for the Poller IO multiplexing interface 16 | const int kPollTimeMs = 10000; // 10000 milliseconds = 10 seconds 17 | /* After creating a thread, it is uncertain whether the main thread or the child thread will run first. 18 | * The advantage of using an eventfd to pass data between threads is that multiple threads can synchronize without locking. 19 | * The minimum kernel version supported by eventfd is Linux 2.6.27. In versions 2.6.26 and earlier, eventfd can also be used, but flags must be set to 0. 20 | * Function prototype: 21 | * #include 22 | * int eventfd(unsigned int initval, int flags); 23 | * Parameter description: 24 | * initval, initial value of the counter. 25 | * flags, EFD_NONBLOCK, set socket to non-blocking. 26 | * EFD_CLOEXEC, when fork is executed, the descriptor in the parent process will be automatically closed, and the descriptor in the child process will be retained. 27 | * Scenario: 28 | * eventfd can be used for communication between threads in the same process. 29 | * eventfd can also be used for communication between processes with the same ancestry. 30 | * If eventfd is used for communication between unrelated processes, the eventfd needs to be placed in shared memory shared by several processes (not tested). 31 | */ 32 | // Create wakeupfd to notify and wake up subReactor to handle new incoming channels 33 | int createEventfd() 34 | { 35 | int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); 36 | if (evtfd < 0) 37 | { 38 | LOG_FATAL<<"eventfd error:"<setReadCallback( 63 | std::bind(&EventLoop::handleRead, this)); // Set the event type for wakeupfd and the callback operation after the event occurs 64 | 65 | wakeupChannel_->enableReading(); // Each EventLoop will listen to the EPOLL read event of wakeupChannel_ 66 | } 67 | EventLoop::~EventLoop() 68 | { 69 | wakeupChannel_->disableAll(); // Remove all interested events from the Channel 70 | wakeupChannel_->remove(); // Remove the Channel from the EventLoop 71 | ::close(wakeupFd_); 72 | t_loopInThisThread = nullptr; 73 | } 74 | 75 | // Start the event loop 76 | void EventLoop::loop() 77 | { 78 | looping_ = true; 79 | quit_ = false; 80 | 81 | LOG_INFO<<"EventLoop start looping"; 82 | 83 | while (!quit_) 84 | { 85 | activeChannels_.clear(); 86 | pollRetureTime_ = poller_->poll(kPollTimeMs, &activeChannels_); 87 | for (Channel *channel : activeChannels_) 88 | { 89 | // Poller listens for which channels have events, then reports to EventLoop, notifying the channel to handle the corresponding event 90 | channel->handleEvent(pollRetureTime_); 91 | } 92 | /** 93 | * Execute the callback operations that need to be processed in the current EventLoop event loop. For the case where the number of threads >= 2, the main work of the IO thread mainloop (mainReactor): 94 | * accept receives connections => packages the connfd returned by accept as a Channel => TcpServer::newConnection assigns the TcpConnection object to subloop for processing through polling 95 | * 96 | * mainloop calls queueInLoop to add the callback to subloop (this callback needs to be executed by subloop, but subloop is still blocked at poller_->poll). queueInLoop wakes up subloop through wakeup 97 | **/ 98 | doPendingFunctors(); 99 | } 100 | LOG_INFO<<"EventLoopstop looping"; 101 | looping_ = false; 102 | } 103 | 104 | /** 105 | * Exit the event loop 106 | * 1. If quit is called successfully in its own thread, it means that the current thread has finished executing the poller_->poll in the loop() function and exited 107 | * 2. If quit is called to exit the EventLoop not in the thread to which the current EventLoop belongs, it is necessary to wake up the epoll_wait of the thread to which the EventLoop belongs 108 | * 109 | * For example, when calling mainloop(IO)'s quit in a subloop(worker), it is necessary to wake up mainloop(IO)'s poller_->poll to let it finish executing the loop() function 110 | * 111 | * !!! Note: Normally, mainloop is responsible for handling connection requests and writing callbacks into subloop. Thread-safe queues can be implemented through the producer-consumer model 112 | * !!! But muduo uses the wakeup() mechanism, and the wakeupFd_ created by eventfd notify allows communication between mainloop and subloop 113 | **/ 114 | void EventLoop::quit() 115 | { 116 | quit_ = true; 117 | 118 | if (!isInLoopThread()) 119 | { 120 | wakeup(); 121 | } 122 | } 123 | 124 | // Execute cb in the current loop 125 | void EventLoop::runInLoop(Functor cb) 126 | { 127 | if (isInLoopThread()) // Execute the callback in the current EventLoop 128 | { 129 | cb(); 130 | } 131 | else // If cb is executed in a non-current EventLoop thread, it is necessary to wake up the thread where the EventLoop is located to execute cb 132 | { 133 | queueInLoop(cb); 134 | } 135 | } 136 | 137 | // Put cb into the queue and wake up the thread where the loop is located to execute cb 138 | void EventLoop::queueInLoop(Functor cb) 139 | { 140 | { 141 | std::unique_lock lock(mutex_); 142 | pendingFunctors_.emplace_back(cb); 143 | } 144 | 145 | /** 146 | * || callingPendingFunctors means that the current loop is executing callbacks, but new callbacks are added to the loop's pendingFunctors_. It is necessary to wake up the corresponding loop thread that needs to execute the above callback operation through a wakeup write event. 147 | * This ensures that the next poller_->poll() in loop() will not block (blocking would delay the execution of the newly added callback), and then continue to execute the callbacks in pendingFunctors_. 148 | **/ 149 | if (!isInLoopThread() || callingPendingFunctors_) 150 | { 151 | wakeup(); // Wake up the thread where the loop is located 152 | } 153 | } 154 | 155 | void EventLoop::handleRead() 156 | { 157 | uint64_t one = 1; 158 | ssize_t n = read(wakeupFd_, &one, sizeof(one)); 159 | if (n != sizeof(one)) 160 | { 161 | LOG_ERROR<<"EventLoop::handleRead() reads"< Poller methods 177 | void EventLoop::updateChannel(Channel *channel) 178 | { 179 | poller_->updateChannel(channel); 180 | } 181 | 182 | void EventLoop::removeChannel(Channel *channel) 183 | { 184 | poller_->removeChannel(channel); 185 | } 186 | 187 | bool EventLoop::hasChannel(Channel *channel) 188 | { 189 | return poller_->hasChannel(channel); 190 | } 191 | 192 | void EventLoop::doPendingFunctors() 193 | { 194 | std::vector functors; 195 | callingPendingFunctors_ = true; 196 | 197 | { 198 | std::unique_lock lock(mutex_); 199 | functors.swap(pendingFunctors_); // Swapping reduces the scope of the lock's critical section, improves efficiency, and avoids deadlock. If functor() is executed in the critical section and functor() calls queueInLoop(), it will cause a deadlock 200 | } 201 | 202 | for (const Functor &functor : functors) 203 | { 204 | functor(); // Execute the callback operation that the current loop needs to execute 205 | } 206 | 207 | callingPendingFunctors_ = false; 208 | } 209 | -------------------------------------------------------------------------------- /src/EventLoopThread.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | EventLoopThread::EventLoopThread(const ThreadInitCallback &cb, 5 | const std::string &name) 6 | : loop_(nullptr) 7 | , exiting_(false) 8 | , thread_(std::bind(&EventLoopThread::threadFunc, this), name) 9 | , mutex_() 10 | , cond_() 11 | , callback_(cb) 12 | { 13 | } 14 | 15 | EventLoopThread::~EventLoopThread() 16 | { 17 | exiting_ = true; 18 | if (loop_ != nullptr) 19 | { 20 | loop_->quit(); 21 | thread_.join(); 22 | } 23 | } 24 | 25 | EventLoop *EventLoopThread::startLoop() 26 | { 27 | thread_.start(); // Start the underlying thread by calling start() on the Thread object thread_ 28 | 29 | EventLoop *loop = nullptr; 30 | { 31 | std::unique_lock lock(mutex_); 32 | cond_.wait(lock, [this](){return loop_ != nullptr;}); 33 | loop = loop_; 34 | } 35 | return loop; 36 | } 37 | 38 | // The following method runs in a separate new thread 39 | void EventLoopThread::threadFunc() 40 | { 41 | EventLoop loop; // Create an independent EventLoop object, which corresponds one-to-one with the above thread (one loop per thread) 42 | 43 | if (callback_) 44 | { 45 | callback_(&loop); 46 | } 47 | 48 | { 49 | std::unique_lock lock(mutex_); 50 | loop_ = &loop; 51 | cond_.notify_one(); 52 | } 53 | loop.loop(); // Execute EventLoop's loop(), which starts the underlying Poller's poll() 54 | std::unique_lock lock(mutex_); 55 | loop_ = nullptr; 56 | } -------------------------------------------------------------------------------- /src/EventLoopThreadPool.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | EventLoopThreadPool::EventLoopThreadPool(EventLoop *baseLoop, const std::string &nameArg) 7 | : baseLoop_(baseLoop), name_(nameArg), started_(false), numThreads_(0), next_(0), hash_(3) 8 | { 9 | } 10 | 11 | EventLoopThreadPool::~EventLoopThreadPool() 12 | { 13 | // Don't delete loop, it's stack variable 14 | } 15 | 16 | void EventLoopThreadPool::start(const ThreadInitCallback &cb) 17 | { 18 | started_ = true; 19 | 20 | for (int i = 0; i < numThreads_; ++i) 21 | { 22 | char buf[name_.size() + 32]; 23 | snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i); 24 | EventLoopThread *t = new EventLoopThread(cb, buf); 25 | threads_.push_back(std::unique_ptr(t)); 26 | loops_.push_back(t->startLoop()); // Create thread at the bottom, bind a new EventLoop, and return the address of the loop 27 | hash_.addNode(buf); // Add the thread to the consistent hash. 28 | } 29 | 30 | if (numThreads_ == 0 && cb) // Only one thread (baseLoop) runs for the entire server 31 | { 32 | cb(baseLoop_); 33 | } 34 | } 35 | 36 | // If working in multithreading, baseLoop_(mainLoop) will assign Channels to subLoops in a polling manner by default 37 | EventLoop *EventLoopThreadPool::getNextLoop(const std::string &key) 38 | { 39 | size_t index = hash_.getNode(key); // Get index 40 | if (index >= loops_.size()) 41 | { 42 | // Handle errors, such as returning baseLoop or throwing an exception 43 | LOG_ERROR<<"EventLoopThreadPool::getNextLoop ERROR"; 44 | return baseLoop_; // Or return nullptr 45 | } 46 | return loops_[index]; // Access loops_ using the index 47 | } 48 | 49 | 50 | std::vector EventLoopThreadPool::getAllLoops() 51 | { 52 | if (loops_.empty()) 53 | { 54 | return std::vector(1, baseLoop_); 55 | } 56 | else 57 | { 58 | return loops_; 59 | } 60 | } -------------------------------------------------------------------------------- /src/InetAddress.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | InetAddress::InetAddress(uint16_t port, std::string ip) 7 | { 8 | ::memset(&addr_, 0, sizeof(addr_)); 9 | addr_.sin_family = AF_INET; 10 | addr_.sin_port = ::htons(port); // Convert local byte order to network byte order 11 | addr_.sin_addr.s_addr = ::inet_addr(ip.c_str()); 12 | } 13 | 14 | std::string InetAddress::toIp() const 15 | { 16 | // addr_ 17 | char buf[64] = {0}; 18 | ::inet_ntop(AF_INET, &addr_.sin_addr, buf, sizeof buf); 19 | return buf; 20 | } 21 | 22 | std::string InetAddress::toIpPort() const 23 | { 24 | // ip:port 25 | char buf[64] = {0}; 26 | ::inet_ntop(AF_INET, &addr_.sin_addr, buf, sizeof buf); 27 | size_t end = ::strlen(buf); 28 | uint16_t port = ::ntohs(addr_.sin_port); 29 | sprintf(buf+end, ":%u", port); 30 | return buf; 31 | } 32 | 33 | uint16_t InetAddress::toPort() const 34 | { 35 | return ::ntohs(addr_.sin_port); 36 | } 37 | 38 | #if 0 39 | #include 40 | int main() 41 | { 42 | InetAddress addr(8080); 43 | std::cout << addr.toIpPort() << std::endl; 44 | } 45 | #endif -------------------------------------------------------------------------------- /src/Logger.cc: -------------------------------------------------------------------------------- 1 | #include "Logger.h" 2 | #include "CurrentThread.h" 3 | 4 | namespace ThreadInfo 5 | { 6 | thread_local char t_errnobuf[512]; // Error message buffer independent for each thread 7 | thread_local char t_timer[64]; // Time formatting buffer independent for each thread 8 | thread_local time_t t_lastSecond; // Each thread records the last formatted time 9 | 10 | } 11 | const char *getErrnoMsg(int savedErrno) 12 | { 13 | return strerror_r(savedErrno, ThreadInfo::t_errnobuf, sizeof(ThreadInfo::t_errnobuf)); 14 | } 15 | // Return the level name based on Level 16 | const char *getLevelName[Logger::LogLevel::LEVEL_COUNT]{ 17 | "TRACE ", 18 | "DEBUG ", 19 | "INFO ", 20 | "WARN ", 21 | "ERROR ", 22 | "FATAL ", 23 | }; 24 | /** 25 | * Default log output function 26 | * Writes log content to standard output stream (stdout) 27 | * @param data Log data to output 28 | * @param len Length of log data 29 | */ 30 | static void defaultOutput(const char *data, int len) 31 | { 32 | fwrite(data, len, sizeof(char), stdout); 33 | } 34 | 35 | /** 36 | * Default flush function 37 | * Flushes the standard output stream buffer to ensure logs are output promptly 38 | * Called when an error occurs or logs need to be seen immediately 39 | */ 40 | static void defaultFlush() 41 | { 42 | fflush(stdout); 43 | } 44 | Logger::OutputFunc g_output = defaultOutput; 45 | Logger::FlushFunc g_flush = defaultFlush; 46 | 47 | Logger::Impl::Impl(Logger::LogLevel level, int savedErrno, const char *filename, int line) 48 | : time_(Timestamp::now()), 49 | stream_(), 50 | level_(level), 51 | line_(line), 52 | basename_(filename) 53 | { 54 | // Format the current time string according to the timezone, also the beginning of a log message 55 | formatTime(); 56 | // Write log level 57 | stream_ << GeneralTemplate(getLevelName[level], 6); 58 | if (savedErrno != 0) 59 | { 60 | stream_ << getErrnoMsg(savedErrno) << " (errno=" << savedErrno << ") "; 61 | } 62 | } 63 | // Format the current time string according to the timezone, also the beginning of a log message 64 | void Logger::Impl::formatTime() 65 | { 66 | Timestamp now = Timestamp::now(); 67 | // Calculate seconds 68 | time_t seconds = static_cast(now.microSecondsSinceEpoch() / Timestamp::kMicroSecondsPerSecond); 69 | int microseconds = static_cast(now.microSecondsSinceEpoch() % Timestamp::kMicroSecondsPerSecond); 70 | // Calculate remaining microseconds 71 | struct tm *tm_timer = localtime(&seconds); 72 | // Write to the time buffer stored by this thread 73 | snprintf(ThreadInfo::t_timer, sizeof(ThreadInfo::t_timer), "%4d/%02d/%02d %02d:%02d:%02d", 74 | tm_timer->tm_year + 1900, 75 | tm_timer->tm_mon + 1, 76 | tm_timer->tm_mday, 77 | tm_timer->tm_hour, 78 | tm_timer->tm_min, 79 | tm_timer->tm_sec); 80 | // Update the last time call 81 | ThreadInfo::t_lastSecond = seconds; 82 | 83 | // muduo uses Fmt to format integers, here we write directly to buf 84 | char buf[32] = {0}; 85 | snprintf(buf, sizeof(buf), "%06d ", microseconds); 86 | 87 | 88 | stream_ << GeneralTemplate(ThreadInfo::t_timer, 17) << GeneralTemplate(buf, 7); 89 | } 90 | void Logger::Impl::finish() 91 | { 92 | stream_ << " - " << GeneralTemplate(basename_.data_, basename_.size_) 93 | << ':' << line_ << '\n'; 94 | } 95 | Logger::Logger(const char *filename, int line, LogLevel level) : impl_(level, 0, filename, line) 96 | { 97 | } 98 | Logger::~Logger() 99 | { 100 | impl_.finish(); 101 | const LogStream::Buffer &buffer = stream().buffer(); 102 | // Output (default terminal output) 103 | g_output(buffer.data(), buffer.length()); 104 | // Terminate program in FATAL case 105 | if (impl_.level_ == FATAL) 106 | { 107 | g_flush(); 108 | abort(); 109 | } 110 | } 111 | 112 | void Logger::setOutput(OutputFunc out) 113 | { 114 | g_output = out; 115 | } 116 | 117 | void Logger::setFlush(FlushFunc flush) 118 | { 119 | g_flush = flush; 120 | } -------------------------------------------------------------------------------- /src/Poller.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | Poller::Poller(EventLoop *loop) 5 | : ownerLoop_(loop) 6 | { 7 | } 8 | 9 | bool Poller::hasChannel(Channel *channel) const 10 | { 11 | auto it = channels_.find(channel->fd()); 12 | return it != channels_.end() && it->second == channel; 13 | } -------------------------------------------------------------------------------- /src/Socket.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | Socket::~Socket() 13 | { 14 | ::close(sockfd_); 15 | } 16 | 17 | void Socket::bindAddress(const InetAddress &localaddr) 18 | { 19 | if (0 != ::bind(sockfd_, (sockaddr *)localaddr.getSockAddr(), sizeof(sockaddr_in))) 20 | { 21 | LOG_FATAL<<"bind sockfd:"<= 0) 47 | { 48 | peeraddr->setSockAddr(addr); 49 | } 50 | return connfd; 51 | } 52 | 53 | void Socket::shutdownWrite() 54 | { 55 | if (::shutdown(sockfd_, SHUT_WR) < 0) 56 | { 57 | LOG_ERROR<<"shutdownWrite error"; 58 | } 59 | } 60 | 61 | void Socket::setTcpNoDelay(bool on) 62 | { 63 | // TCP_NODELAY is used to disable the Nagle algorithm. 64 | // The Nagle algorithm is used to reduce the number of small packets transmitted over the network. 65 | // Setting TCP_NODELAY to 1 disables this algorithm, allowing small packets to be sent immediately. 66 | int optval = on ? 1 : 0; 67 | ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); 68 | } 69 | 70 | void Socket::setReuseAddr(bool on) 71 | { 72 | // SO_REUSEADDR allows a socket to forcibly bind to a port already used by another socket. 73 | // This is very useful for server applications that need to restart and bind to the same port. 74 | int optval = on ? 1 : 0; 75 | ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); 76 | } 77 | 78 | void Socket::setReusePort(bool on) 79 | { 80 | // SO_REUSEPORT allows multiple sockets on the same host to bind to the same port number. 81 | // This is very useful for load balancing incoming connections across multiple threads or processes. 82 | int optval = on ? 1 : 0; 83 | ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); 84 | } 85 | 86 | void Socket::setKeepAlive(bool on) 87 | { 88 | // SO_KEEPALIVE enables periodic transmission of messages on connected sockets. 89 | // If the other end does not respond, the connection is considered broken and closed. 90 | // This is very useful for detecting failed peers in the network. 91 | int optval = on ? 1 : 0; 92 | ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); 93 | } -------------------------------------------------------------------------------- /src/TcpConnection.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include // for open 10 | #include // for close 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | static EventLoop *CheckLoopNotNull(EventLoop *loop) 19 | { 20 | if (loop == nullptr) 21 | { 22 | LOG_FATAL<<" mainLoop is null!"; 23 | } 24 | return loop; 25 | } 26 | 27 | TcpConnection::TcpConnection(EventLoop *loop, 28 | const std::string &nameArg, 29 | int sockfd, 30 | const InetAddress &localAddr, 31 | const InetAddress &peerAddr) 32 | : loop_(CheckLoopNotNull(loop)) 33 | , name_(nameArg) 34 | , state_(kConnecting) 35 | , reading_(true) 36 | , socket_(new Socket(sockfd)) 37 | , channel_(new Channel(loop, sockfd)) 38 | , localAddr_(localAddr) 39 | , peerAddr_(peerAddr) 40 | , highWaterMark_(64 * 1024 * 1024) // 64M 41 | { 42 | // Below, set the corresponding callback functions for the channel. The poller notifies the channel that an interested event has occurred, and the channel will call the corresponding callback function. 43 | channel_->setReadCallback( 44 | std::bind(&TcpConnection::handleRead, this, std::placeholders::_1)); 45 | channel_->setWriteCallback( 46 | std::bind(&TcpConnection::handleWrite, this)); 47 | channel_->setCloseCallback( 48 | std::bind(&TcpConnection::handleClose, this)); 49 | channel_->setErrorCallback( 50 | std::bind(&TcpConnection::handleError, this)); 51 | 52 | LOG_INFO<<"TcpConnection::ctor:["<isWriting() && outputBuffer_.readableBytes() == 0) 93 | { 94 | nwrote = ::write(channel_->fd(), data, len); 95 | if (nwrote >= 0) 96 | { 97 | remaining = len - nwrote; 98 | if (remaining == 0 && writeCompleteCallback_) 99 | { 100 | // Since all data has been sent here, there is no need to set the epollout event for the channel. 101 | loop_->queueInLoop( 102 | std::bind(writeCompleteCallback_, shared_from_this())); 103 | } 104 | } 105 | else // nwrote < 0 106 | { 107 | nwrote = 0; 108 | if (errno != EWOULDBLOCK) // EWOULDBLOCK indicates a normal return when there is no data in non-blocking mode, equivalent to EAGAIN. 109 | { 110 | LOG_ERROR<<"TcpConnection::sendInLoop"; 111 | if (errno == EPIPE || errno == ECONNRESET) // SIGPIPE RESET 112 | { 113 | faultError = true; 114 | } 115 | } 116 | } 117 | } 118 | /** 119 | * Indicates that the current write did not send all the data. The remaining data needs to be saved in the buffer. 120 | * Then register the EPOLLOUT event for the channel. When the poller finds that the TCP send buffer has space, it will notify 121 | * the corresponding sock->channel and call the channel's registered writeCallback_ callback method. 122 | * The channel's writeCallback_ is actually the handleWrite callback set by TcpConnection, 123 | * which sends all the content of the send buffer outputBuffer_. 124 | **/ 125 | if (!faultError && remaining > 0) 126 | { 127 | // The current length of data remaining to be sent in the send buffer. 128 | size_t oldLen = outputBuffer_.readableBytes(); 129 | if (oldLen + remaining >= highWaterMark_ && oldLen < highWaterMark_ && highWaterMarkCallback_) 130 | { 131 | loop_->queueInLoop( 132 | std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining)); 133 | } 134 | outputBuffer_.append((char *)data + nwrote, remaining); 135 | if (!channel_->isWriting()) 136 | { 137 | channel_->enableWriting(); // Here, the channel's write event must be registered, otherwise the poller will not notify the channel of epollout. 138 | } 139 | } 140 | } 141 | 142 | void TcpConnection::shutdown() 143 | { 144 | if (state_ == kConnected) 145 | { 146 | setState(kDisconnecting); 147 | loop_->runInLoop( 148 | std::bind(&TcpConnection::shutdownInLoop, this)); 149 | } 150 | } 151 | 152 | void TcpConnection::shutdownInLoop() 153 | { 154 | if (!channel_->isWriting()) // Indicates that all data in the current outputBuffer_ has been sent outside 155 | { 156 | socket_->shutdownWrite(); 157 | } 158 | } 159 | 160 | // Connection established 161 | void TcpConnection::connectEstablished() 162 | { 163 | setState(kConnected); 164 | channel_->tie(shared_from_this()); 165 | channel_->enableReading(); // Register the channel's EPOLLIN read event with the poller 166 | 167 | // New connection established, execute callback 168 | connectionCallback_(shared_from_this()); 169 | } 170 | // Connection destroyed 171 | void TcpConnection::connectDestroyed() 172 | { 173 | if (state_ == kConnected) 174 | { 175 | setState(kDisconnected); 176 | channel_->disableAll(); // Remove all interested events of the channel from the poller 177 | connectionCallback_(shared_from_this()); 178 | } 179 | channel_->remove(); // Remove the channel from the poller 180 | } 181 | 182 | // Reading is relative to the server. When the client on the other side has data arriving, the server detects EPOLLIN and triggers the callback on this fd. handleRead reads the data sent by the other side. 183 | void TcpConnection::handleRead(Timestamp receiveTime) 184 | { 185 | int savedErrno = 0; 186 | ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); 187 | if (n > 0) // Data has arrived 188 | { 189 | // A readable event has occurred for an established connection user, call the user-provided callback operation onMessage. shared_from_this gets a smart pointer to TcpConnection. 190 | messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); 191 | } 192 | else if (n == 0) // Client disconnected 193 | { 194 | handleClose(); 195 | } 196 | else // An error occurred 197 | { 198 | errno = savedErrno; 199 | LOG_ERROR<<"TcpConnection::handleRead"; 200 | handleError(); 201 | } 202 | } 203 | 204 | void TcpConnection::handleWrite() 205 | { 206 | if (channel_->isWriting()) 207 | { 208 | int savedErrno = 0; 209 | ssize_t n = outputBuffer_.writeFd(channel_->fd(), &savedErrno); 210 | if (n > 0) 211 | { 212 | outputBuffer_.retrieve(n);//Retrieve data from the buffer and move the readindex pointer 213 | if (outputBuffer_.readableBytes() == 0) 214 | { 215 | channel_->disableWriting(); 216 | if (writeCompleteCallback_) 217 | { 218 | // TcpConnection object is in its subloop, adding a callback to pendingFunctors_ 219 | loop_->queueInLoop( 220 | std::bind(writeCompleteCallback_, shared_from_this())); 221 | } 222 | if (state_ == kDisconnecting) 223 | { 224 | shutdownInLoop(); // Remove TcpConnection from its current loop 225 | } 226 | } 227 | } 228 | else 229 | { 230 | LOG_ERROR<<"TcpConnection::handleWrite"; 231 | } 232 | } 233 | else 234 | { 235 | LOG_ERROR<<"TcpConnection fd="<fd()<<"is down, no more writing"; 236 | } 237 | } 238 | 239 | void TcpConnection::handleClose() 240 | { 241 | LOG_INFO<<"TcpConnection::handleClose fd="<fd()<<"state="<<(int)state_; 242 | setState(kDisconnected); 243 | channel_->disableAll(); 244 | 245 | TcpConnectionPtr connPtr(shared_from_this()); 246 | connectionCallback_(connPtr); // Connection callback 247 | closeCallback_(connPtr); // Execute the close connection callback, which is the TcpServer::removeConnection callback method. // must be the last line 248 | } 249 | 250 | void TcpConnection::handleError() 251 | { 252 | int optval; 253 | socklen_t optlen = sizeof optval; 254 | int err = 0; 255 | if (::getsockopt(channel_->fd(), SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) 256 | { 257 | err = errno; 258 | } 259 | else 260 | { 261 | err = optval; 262 | } 263 | LOG_ERROR<<"TcpConnection::handleError name:"<isInLoopThread()) { // Determine whether the current thread is the loop thread 270 | sendFileInLoop(fileDescriptor, offset, count); 271 | }else{ // If not, wake up the thread running this TcpConnection to execute the Loop loop 272 | loop_->runInLoop( 273 | std::bind(&TcpConnection::sendFileInLoop, shared_from_this(), fileDescriptor, offset, count)); 274 | } 275 | } else { 276 | LOG_ERROR<<"TcpConnection::sendFile - not connected"; 277 | } 278 | } 279 | 280 | // How many bytes were sent 281 | void TcpConnection::sendFileInLoop(int fileDescriptor, off_t offset, size_t count) { 282 | ssize_t bytesSent = 0; 283 | size_t remaining = count; // How much data is left to send 284 | bool faultError = false; // Error flag 285 | 286 | if (state_ == kDisconnecting) { // Indicates that the connection is already disconnected, so no data needs to be sent. 287 | LOG_ERROR<<"disconnected, give up writing"; 288 | return; 289 | } 290 | 291 | // Indicates that the Channel is writing data for the first time or the outputBuffer buffer has no data 292 | if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) { 293 | bytesSent = sendfile(socket_->fd(), fileDescriptor, &offset, remaining); 294 | if (bytesSent >= 0) { 295 | remaining -= bytesSent; 296 | if (remaining == 0 && writeCompleteCallback_) { 297 | // remaining being 0 means the data is exactly all sent, so there is no need to set the write event listener for it. 298 | loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this())); 299 | } 300 | } else { // If it is a non-blocking no data return error, this is a normal phenomenon equivalent to EAGAIN, otherwise it is an abnormal situation 301 | if (errno != EWOULDBLOCK) { 302 | LOG_ERROR<<"TcpConnection::sendFileInLoop"; 303 | } 304 | if (errno == EPIPE || errno == ECONNRESET) { 305 | faultError = true; 306 | } 307 | } 308 | } 309 | // Handle remaining data 310 | if (!faultError && remaining > 0) { 311 | // Continue sending remaining data 312 | loop_->queueInLoop( 313 | std::bind(&TcpConnection::sendFileInLoop, shared_from_this(), fileDescriptor, offset, remaining)); 314 | } 315 | } -------------------------------------------------------------------------------- /src/TcpServer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static EventLoop *CheckLoopNotNull(EventLoop *loop) 9 | { 10 | if (loop == nullptr) 11 | { 12 | LOG_FATAL<<"main Loop is NULL!"; 13 | } 14 | return loop; 15 | } 16 | 17 | TcpServer::TcpServer(EventLoop *loop, 18 | const InetAddress &listenAddr, 19 | const std::string &nameArg, 20 | Option option) 21 | : loop_(CheckLoopNotNull(loop)) 22 | , ipPort_(listenAddr.toIpPort()) 23 | , name_(nameArg) 24 | , acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)) 25 | , threadPool_(new EventLoopThreadPool(loop, name_)) 26 | , connectionCallback_() 27 | , messageCallback_() 28 | , nextConnId_(1) 29 | , started_(0) 30 | { 31 | // When a new user connects, the acceptChannel_ bound in the Acceptor class will have a read event, executing handleRead() and calling TcpServer::newConnection callback 32 | acceptor_->setNewConnectionCallback( 33 | std::bind(&TcpServer::newConnection, this, std::placeholders::_1, std::placeholders::_2)); 34 | } 35 | 36 | TcpServer::~TcpServer() 37 | { 38 | for(auto &item : connections_) 39 | { 40 | TcpConnectionPtr conn(item.second); 41 | item.second.reset(); // Reset the original smart pointer, let the stack TcpConnectionPtr conn point to the object, when conn goes out of scope, the object pointed by the smart pointer can be released 42 | // Destroy connection 43 | conn->getLoop()->runInLoop( 44 | std::bind(&TcpConnection::connectDestroyed, conn)); 45 | } 46 | } 47 | 48 | // Set the number of subloops at the bottom 49 | void TcpServer::setThreadNum(int numThreads) 50 | { 51 | int numThreads_=numThreads; 52 | threadPool_->setThreadNum(numThreads_); 53 | } 54 | 55 | // Start server listening 56 | void TcpServer::start() 57 | { 58 | if (started_.fetch_add(1) == 0) // Prevent a TcpServer object from being started multiple times 59 | { 60 | threadPool_->start(threadInitCallback_); // Start the underlying loop thread pool 61 | loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); 62 | } 63 | } 64 | 65 | // When a new user connects, the acceptor will execute this callback operation, responsible for distributing the connection requests received by mainLoop (acceptChannel_ will have read events) to subLoop through polling 66 | void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr) 67 | { 68 | // Polling algorithm to select a subLoop to manage the channel corresponding to connfd 69 | EventLoop *ioLoop = threadPool_->getNextLoop(peerAddr.toIp()); 70 | char buf[64] = {0}; 71 | snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_); 72 | ++nextConnId_; // Not set as atomic because it only executes in mainloop, no thread safety issues 73 | std::string connName = name_ + buf; 74 | 75 | LOG_INFO<<"TcpServer::newConnection ["< TcpConnection, while the Channel is bound to the four handlers set by TcpConnection: handleRead, handleWrite... These callbacks are used in the handleXXX functions 94 | conn->setConnectionCallback(connectionCallback_); 95 | conn->setMessageCallback(messageCallback_); 96 | conn->setWriteCompleteCallback(writeCompleteCallback_); 97 | 98 | // Set the callback for how to close the connection 99 | conn->setCloseCallback( 100 | std::bind(&TcpServer::removeConnection, this, std::placeholders::_1)); 101 | 102 | ioLoop->runInLoop( 103 | std::bind(&TcpConnection::connectEstablished, conn)); 104 | } 105 | 106 | void TcpServer::removeConnection(const TcpConnectionPtr &conn) 107 | { 108 | loop_->runInLoop( 109 | std::bind(&TcpServer::removeConnectionInLoop, this, conn)); 110 | } 111 | 112 | void TcpServer::removeConnectionInLoop(const TcpConnectionPtr &conn) 113 | { 114 | LOG_INFO<<"TcpServer::removeConnectionInLoop ["<< 115 | name_.c_str()<<"] - connection %s"<name().c_str(); 116 | 117 | connections_.erase(conn->name()); 118 | EventLoop *ioLoop = conn->getLoop(); 119 | ioLoop->queueInLoop( 120 | std::bind(&TcpConnection::connectDestroyed, conn)); 121 | } -------------------------------------------------------------------------------- /src/Thread.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | std::atomic_int Thread::numCreated_(0); 7 | 8 | Thread::Thread(ThreadFunc func, const std::string &name) 9 | : started_(false) 10 | , joined_(false) 11 | , tid_(0) 12 | , func_(std::move(func)) 13 | , name_(name) 14 | { 15 | setDefaultName(); 16 | } 17 | 18 | Thread::~Thread() 19 | { 20 | if (started_ && !joined_) 21 | { 22 | thread_->detach(); // Thread class provides method to set detached thread, thread will be automatically destroyed after running (non-blocking) 23 | } 24 | } 25 | 26 | void Thread::start() // A Thread object records the detailed information of a new thread 27 | { 28 | started_ = true; 29 | sem_t sem; 30 | sem_init(&sem, false, 0); // false means not setting inter-process sharing 31 | // Start thread 32 | thread_ = std::shared_ptr(new std::thread([&]() { 33 | tid_ = CurrentThread::tid(); // Get thread's tid value 34 | sem_post(&sem); 35 | func_(); // Start a new thread specifically to execute this thread function 36 | })); 37 | 38 | // Must wait here to get the tid value of the newly created thread above 39 | sem_wait(&sem); 40 | } 41 | 42 | // Difference between join() and detach() in C++ std::thread: https://blog.nowcoder.net/n/8fcd9bb6e2e94d9596cf0a45c8e5858a 43 | void Thread::join() 44 | { 45 | joined_ = true; 46 | thread_->join(); 47 | } 48 | 49 | void Thread::setDefaultName() 50 | { 51 | int num = ++numCreated_; 52 | if (name_.empty()) 53 | { 54 | char buf[32] = {0}; 55 | snprintf(buf, sizeof buf, "Thread%d", num); 56 | name_ = buf; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Timer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void Timer::restart(Timestamp now) 4 | { 5 | if (repeat_) 6 | { 7 | // If it's a repeating timer event, continue adding timer events to get the new event expiration time 8 | expiration_ = addTime(now, interval_); 9 | } 10 | else 11 | { 12 | expiration_ = Timestamp(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/TimerQueue.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | int createTimerfd() 12 | { 13 | /** 14 | * CLOCK_MONOTONIC: absolute time 15 | * TFD_NONBLOCK: non-blocking 16 | */ 17 | int timerfd = ::timerfd_create(CLOCK_MONOTONIC, 18 | TFD_NONBLOCK | TFD_CLOEXEC); 19 | if (timerfd < 0) 20 | { 21 | LOG_ERROR << "Failed in timerfd_create"; 22 | } 23 | return timerfd; 24 | } 25 | 26 | TimerQueue::TimerQueue(EventLoop* loop) 27 | : loop_(loop), 28 | timerfd_(createTimerfd()), 29 | timerfdChannel_(loop_, timerfd_), 30 | timers_() 31 | { 32 | timerfdChannel_.setReadCallback( 33 | std::bind(&TimerQueue::handleRead, this)); 34 | timerfdChannel_.enableReading(); 35 | } 36 | 37 | TimerQueue::~TimerQueue() 38 | { 39 | timerfdChannel_.disableAll(); 40 | timerfdChannel_.remove(); 41 | ::close(timerfd_); 42 | // Delete all timers 43 | for (const Entry& timer : timers_) 44 | { 45 | delete timer.second; 46 | } 47 | } 48 | 49 | void TimerQueue::addTimer(TimerCallback cb, 50 | Timestamp when, 51 | double interval) 52 | { 53 | Timer* timer = new Timer(std::move(cb), when, interval); 54 | loop_->runInLoop( 55 | std::bind(&TimerQueue::addTimerInLoop, this, timer)); 56 | } 57 | 58 | void TimerQueue::addTimerInLoop(Timer* timer) 59 | { 60 | // Whether the earliest timer trigger time has been replaced 61 | bool eraliestChanged = insert(timer); 62 | 63 | // We need to reset the timerfd_ trigger time 64 | if (eraliestChanged) 65 | { 66 | resetTimerfd(timerfd_, timer->expiration()); 67 | } 68 | } 69 | 70 | // Reset timerfd 71 | void TimerQueue::resetTimerfd(int timerfd_, Timestamp expiration) 72 | { 73 | struct itimerspec newValue; 74 | struct itimerspec oldValue; 75 | memset(&newValue, '\0', sizeof(newValue)); 76 | memset(&oldValue, '\0', sizeof(oldValue)); 77 | 78 | // Timeout time - current time 79 | int64_t microSecondDif = expiration.microSecondsSinceEpoch() - Timestamp::now().microSecondsSinceEpoch(); 80 | if (microSecondDif < 100) 81 | { 82 | microSecondDif = 100; 83 | } 84 | 85 | struct timespec ts; 86 | ts.tv_sec = static_cast( 87 | microSecondDif / Timestamp::kMicroSecondsPerSecond); 88 | ts.tv_nsec = static_cast( 89 | (microSecondDif % Timestamp::kMicroSecondsPerSecond) * 1000); 90 | newValue.it_value = ts; 91 | // This function will wake up the event loop 92 | if (::timerfd_settime(timerfd_, 0, &newValue, &oldValue)) 93 | { 94 | LOG_ERROR << "timerfd_settime faield()"; 95 | } 96 | } 97 | 98 | void ReadTimerFd(int timerfd) 99 | { 100 | uint64_t read_byte; 101 | ssize_t readn = ::read(timerfd, &read_byte, sizeof(read_byte)); 102 | 103 | if (readn != sizeof(read_byte)) { 104 | LOG_ERROR << "TimerQueue::ReadTimerFd read_size < 0"; 105 | } 106 | } 107 | 108 | // Return deleted timer nodes (std::vector expired) 109 | std::vector TimerQueue::getExpired(Timestamp now) 110 | { 111 | std::vector expired; 112 | Entry sentry(now, reinterpret_cast(UINTPTR_MAX)); 113 | TimerList::iterator end = timers_.lower_bound(sentry); 114 | std::copy(timers_.begin(), end, back_inserter(expired)); 115 | timers_.erase(timers_.begin(), end); 116 | 117 | return expired; 118 | } 119 | 120 | void TimerQueue::handleRead() 121 | { 122 | Timestamp now = Timestamp::now(); 123 | ReadTimerFd(timerfd_); 124 | 125 | std::vector expired = getExpired(now); 126 | 127 | // Iterate through expired timers and call their callbacks 128 | callingExpiredTimers_ = true; 129 | for (const Entry& it : expired) 130 | { 131 | it.second->run(); 132 | } 133 | callingExpiredTimers_ = false; 134 | 135 | // Reset these timers 136 | reset(expired, now); 137 | } 138 | 139 | void TimerQueue::reset(const std::vector& expired, Timestamp now) 140 | { 141 | Timestamp nextExpire; 142 | for (const Entry& it : expired) 143 | { 144 | // If it's a repeating task, continue execution 145 | if (it.second->repeat()) 146 | { 147 | auto timer = it.second; 148 | timer->restart(Timestamp::now()); 149 | insert(timer); 150 | } 151 | else 152 | { 153 | delete it.second; 154 | } 155 | 156 | // If timers were reinserted, need to continue resetting timerfd 157 | if (!timers_.empty()) 158 | { 159 | resetTimerfd(timerfd_, (timers_.begin()->second)->expiration()); 160 | } 161 | } 162 | } 163 | 164 | bool TimerQueue::insert(Timer* timer) 165 | { 166 | bool earliestChanged = false; 167 | Timestamp when = timer->expiration(); 168 | TimerList::iterator it = timers_.begin(); 169 | if (it == timers_.end() || when < it->first) 170 | { 171 | // The earliest timer has been replaced 172 | earliestChanged = true; 173 | } 174 | 175 | // Insert this new timer into the timer management red-black tree 176 | timers_.insert(Entry(when, timer)); 177 | 178 | return earliestChanged; 179 | } -------------------------------------------------------------------------------- /src/Timestamp.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Get current timestamp 4 | Timestamp Timestamp::now() 5 | { 6 | struct timeval tv; 7 | // Get microseconds and seconds 8 | // On x86-64 platform, gettimeofday() is no longer a system call, won't enter kernel, multiple calls won't have performance impact 9 | gettimeofday(&tv, NULL); 10 | int64_t seconds = tv.tv_sec; 11 | // Convert to microseconds 12 | return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec); 13 | } 14 | std::string Timestamp::toString()const 15 | { 16 | char buf[128] = {0}; 17 | tm *tm_time = localtime(µSecondsSinceEpoch_); 18 | snprintf(buf, 128, "%4d/%02d/%02d %02d:%02d:%02d", 19 | tm_time->tm_year + 1900, 20 | tm_time->tm_mon + 1, 21 | tm_time->tm_mday, 22 | tm_time->tm_hour, 23 | tm_time->tm_min, 24 | tm_time->tm_sec); 25 | return buf; 26 | } 27 | // 2022/08/26 16:29:10 28 | // 20220826 16:29:10.773804 29 | std::string Timestamp::toFormattedString(bool showMicroseconds) const 30 | { 31 | char buf[64] = {0}; 32 | time_t seconds = static_cast(microSecondsSinceEpoch_ / kMicroSecondsPerSecond); 33 | // Use localtime function to format seconds into calendar time 34 | tm *tm_time = localtime(&seconds); 35 | if (showMicroseconds) 36 | { 37 | int microseconds = static_cast(microSecondsSinceEpoch_ % kMicroSecondsPerSecond); 38 | snprintf(buf, sizeof(buf), "%4d/%02d/%02d %02d:%02d:%02d.%06d", 39 | tm_time->tm_year + 1900, 40 | tm_time->tm_mon + 1, 41 | tm_time->tm_mday, 42 | tm_time->tm_hour, 43 | tm_time->tm_min, 44 | tm_time->tm_sec, 45 | microseconds); 46 | } 47 | else 48 | { 49 | snprintf(buf, sizeof(buf), "%4d/%02d/%02d %02d:%02d:%02d", 50 | tm_time->tm_year + 1900, 51 | tm_time->tm_mon + 1, 52 | tm_time->tm_mday, 53 | tm_time->tm_hour, 54 | tm_time->tm_min, 55 | tm_time->tm_sec); 56 | } 57 | return buf; 58 | } 59 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "AsyncLogging.h" 8 | #include "LFU.h" 9 | #include "memoryPool.h" 10 | // Log file roll size is 1MB (1*1024*1024 bytes) 11 | static const off_t kRollSize = 1*1024*1024; 12 | class EchoServer 13 | { 14 | public: 15 | EchoServer(EventLoop *loop, const InetAddress &addr, const std::string &name) 16 | : server_(loop, addr, name) 17 | , loop_(loop) 18 | { 19 | // Register callback functions 20 | server_.setConnectionCallback( 21 | std::bind(&EchoServer::onConnection, this, std::placeholders::_1)); 22 | 23 | server_.setMessageCallback( 24 | std::bind(&EchoServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); 25 | 26 | // Set an appropriate number of subloop threads 27 | server_.setThreadNum(3); 28 | } 29 | void start() 30 | { 31 | server_.start(); 32 | } 33 | 34 | private: 35 | // Callback function for connection establishment or disconnection 36 | void onConnection(const TcpConnectionPtr &conn) 37 | { 38 | if (conn->connected()) 39 | { 40 | LOG_INFO<<"Connection UP :"<peerAddress().toIpPort().c_str(); 41 | } 42 | else 43 | { 44 | LOG_INFO<<"Connection DOWN :"<peerAddress().toIpPort().c_str(); 45 | } 46 | } 47 | 48 | // Read/write event callback 49 | void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time) 50 | { 51 | std::string msg = buf->retrieveAllAsString(); 52 | conn->send(msg); 53 | // conn->shutdown(); // Close the write end, underlying responds with EPOLLHUP => triggers closeCallback_ 54 | } 55 | TcpServer server_; 56 | EventLoop *loop_; 57 | 58 | }; 59 | AsyncLogging* g_asyncLog = NULL; 60 | AsyncLogging * getAsyncLog(){ 61 | return g_asyncLog; 62 | } 63 | void asyncLog(const char* msg, int len) 64 | { 65 | AsyncLogging* logging = getAsyncLog(); 66 | if (logging) 67 | { 68 | logging->append(msg, len); 69 | } 70 | } 71 | int main(int argc,char *argv[]) { 72 | // Step 1: Start logging, double-buffered asynchronous disk write. 73 | // Create a folder 74 | const std::string LogDir="logs"; 75 | mkdir(LogDir.c_str(),0755); 76 | // Use std::stringstream to construct the log file path 77 | std::ostringstream LogfilePath; 78 | LogfilePath << LogDir << "/" << ::basename(argv[0]); // Complete log file path 79 | AsyncLogging log(LogfilePath.str(), kRollSize); 80 | g_asyncLog = &log; 81 | Logger::setOutput(asyncLog); // Set output callback for Logger, reconfigure output location 82 | log.start(); // Start the log backend thread 83 | // Step 2: Start memory pool and LFU cache 84 | // Initialize memory pool 85 | memoryPool::HashBucket::initMemoryPool(); 86 | 87 | // Initialize cache 88 | const int CAPACITY = 5; 89 | RonaldCache::RLfuCache lfu(CAPACITY); 90 | // Step 3: Start the underlying network module 91 | EventLoop loop; 92 | InetAddress addr(8080); 93 | EchoServer server(&loop, addr, "EchoServer"); 94 | server.start(); 95 | // Main loop starts event loop, epoll_wait blocks and waits for ready events (main loop only registers the listening socket fd, so it only handles new connection events) 96 | std::cout << "================================================Start Web Server================================================" << std::endl; 97 | loop.loop(); 98 | std::cout << "================================================Stop Web Server=================================================" << std::endl; 99 | // Stop logging 100 | log.stop(); 101 | } --------------------------------------------------------------------------------