From 63810f186fbfab1e7cbddde1f673641cb828a7f0 Mon Sep 17 00:00:00 2001 From: LostbBlizzard <106630000+LostbBlizzard@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:47:23 -0800 Subject: [PATCH 1/7] fixed errors when using wasm emscripten. --- src/src/SocketId.h | 2 +- src/src/core/Receiver.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/src/SocketId.h b/src/src/SocketId.h index 41e7be4f..fe8a458f 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketId.h @@ -16,7 +16,7 @@ #include #include #include -#elif defined(__linux__) || defined(__APPLE__) +#elif defined(__unix__) || defined(__APPLE__) #include #include #include //memset diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 7be1e71f..7cffbd1c 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -9,7 +9,9 @@ #include #include "../SocketAddress.h" - +#ifndef _WIN32 +#include +#endif namespace MinimalSocket { std::unique_ptr> ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { From f0a63913d30127bd03e8d644165de0b65c27c2b2 Mon Sep 17 00:00:00 2001 From: Foo Date: Thu, 8 Feb 2024 18:39:15 +0100 Subject: [PATCH 2/7] GNU LICENSE added --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4842d817 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 2a44de712196d59d5d4ea8d14380af253c7bf100 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 19 Feb 2024 22:05:23 +0000 Subject: [PATCH 3/7] avoid custom target command in VisualStudio in favour of a Popen C++ wrapper --- samples/CMakeLists.txt | 3 + samples/Launcher.cpp | 106 +++++++++++++++++++++++++++++++++ samples/cmake/MakeSample.cmake | 14 +++++ samples/utils/Monitor.py | 13 +++- 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 samples/Launcher.cpp diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 811e8637..daa81338 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -7,5 +7,8 @@ include(cmake/MakeSample.cmake) add_subdirectory(utils) +set(LauncherSource ${CMAKE_CURRENT_SOURCE_DIR}/Launcher.cpp) +option(BUILD_MinimalCppSocket_AVOID_CUSTOM_TARGET "" OFF) + add_subdirectory(tcp) add_subdirectory(udp) diff --git a/samples/Launcher.cpp b/samples/Launcher.cpp new file mode 100644 index 00000000..7d1167ae --- /dev/null +++ b/samples/Launcher.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include +#if _WIN64 || _WIN32 +#include +#endif + +class Popen { +public: + Popen(const std::string &line) { + fp = +#if _WIN64 || _WIN32 + _popen(line.c_str(), "r") +#elif __linux__ + popen(line.c_str(), "r") +#endif + ; + if (fp == NULL) { + throw std::runtime_error{"Unable to start the child process"}; + } + } + + struct CompletedProcess { + int returnCode; + std::string out; + }; + CompletedProcess communicate() { + if (!done) { + while (poll()) { + } + done = true; + } + return CompletedProcess{returnCode_, out_.str()}; + } + +protected: + bool poll() { + std::string temp; + temp.resize(250); + if (fgets(temp.data(), static_cast(temp.size()), fp) == nullptr) { +#if _WIN64 || _WIN32 + feof(fp); +#endif + + returnCode_ = +#if _WIN64 || _WIN32 + _pclose(fp) +#elif __linux__ + pclose(fp) +#endif + ; + + fp = nullptr; + return false; + } + std::size_t last_pos = temp.find('\n'); + if (last_pos != std::string::npos) { + temp.resize(last_pos + 1); + } + out_ << temp; + return true; + } + +private: + bool done = false; + FILE *fp = nullptr; + std::string buffer_; + int returnCode_ = -1; + std::stringstream out_; +}; + +#include +#include + +const std::string cmd{CMD}; +std::vector getCommands() { + std::vector res; + std::size_t pos = 0; + while (pos < cmd.size()) { + auto next = cmd.find('|', pos); + if (next == std::string::npos) { + res.emplace_back(cmd.data() + pos); + break; + } + res.emplace_back(cmd.data() + pos, next - pos); + pos = next + 1; + } + return res; +} + +int main() { + for (const auto &proc : getCommands()) { + Popen process(std::string{proc.data(), proc.size()}); + auto &&[retCode, out] = process.communicate(); + std::cout << out; + if (retCode != 0) { + throw std::runtime_error{"Child process didn't complete well"}; + } + } + + return EXIT_SUCCESS; +} diff --git a/samples/cmake/MakeSample.cmake b/samples/cmake/MakeSample.cmake index 344ba9fd..b57e3bd4 100644 --- a/samples/cmake/MakeSample.cmake +++ b/samples/cmake/MakeSample.cmake @@ -20,6 +20,17 @@ function(MakeSample INPUT PREFIX) set(BIN_LOCATION ${BIN_LOCATION}/$) endif() +if (CMAKE_GENERATOR MATCHES "Visual Studio" OR BUILD_MinimalCppSocket_AVOID_CUSTOM_TARGET) + # add_custom_target is not supported by visual studio + add_executable(${TARGET_NAME} ${LauncherSource}) + target_compile_definitions(${TARGET_NAME} PUBLIC + -D CMD="${Python3_EXECUTABLE} ${SCRIPT_LOCATION} --cmd ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT}|${Python3_EXECUTABLE} ${SCRIPT_LOCATION} --location ${BIN_LOCATION} --cmd ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT} --dest ${CMAKE_CURRENT_BINARY_DIR}/report.html --sleep 0.5" + ) +else() + add_custom_target(${TARGET_NAME}-preamble ALL + COMMAND "${Python3_EXECUTABLE}" ${SCRIPT_LOCATION} + "--cmd" "${CMAKE_CURRENT_SOURCE_DIR}/${INPUT}" + ) add_custom_target(${TARGET_NAME} ALL COMMAND "${Python3_EXECUTABLE}" ${SCRIPT_LOCATION} "--location" "${BIN_LOCATION}" @@ -27,4 +38,7 @@ function(MakeSample INPUT PREFIX) "--dest" "${CMAKE_CURRENT_BINARY_DIR}/report.html" "--sleep" "0.5" ) + add_dependencies(${TARGET_NAME} ${TARGET_NAME}-preamble) +endif() + endfunction() diff --git a/samples/utils/Monitor.py b/samples/utils/Monitor.py index 1b5a8f37..ce1ff26f 100644 --- a/samples/utils/Monitor.py +++ b/samples/utils/Monitor.py @@ -15,12 +15,11 @@ def stripEndl(line): class ProcessHandler: def __init__(self, cmd_line, location, sleep_initial=None): if not sleep_initial == None: - print('sleeping {} [s]'.format(sleep_initial)) + # print('sleeping {} [s]'.format(sleep_initial)) time.sleep(float(sleep_initial)) self.cmd = cmd_line.strip().split() if not location == None: self.cmd[0] = os.path.join(location, self.cmd[0]) - print('running `{}`'.format( self.to_string(False) )) self.thread = threading.Thread(target=self.run_) self.thread.start() self.stdout = '' @@ -128,6 +127,7 @@ def test(): def monitor(options): try: Monitor.make(options.cmd, options.dest, options.location, options.sleep) + print() print('open in a browser {} to see the results sent by the run processes'.format(options.dest)) except: sys.exit(1) @@ -145,9 +145,16 @@ def main(): (options, args) = parser.parse_args() if options.test: - test() + test() elif options.cmd and options.dest: monitor(options) + elif options.cmd: + # just show the commands + print() + with open(options.cmd, 'r') as stream: + print(''.join(['Running {}'.format(line) for line in stream.readlines()])) + print('\n\nwaiting for all the spawned processes to complete ...\n\n') + if __name__ == '__main__': main() From af443a99f9d11f798e1cae492a62b987a20967b0 Mon Sep 17 00:00:00 2001 From: Foo Date: Tue, 20 Feb 2024 22:44:09 +0100 Subject: [PATCH 4/7] cosmetic changes to Monitor.py --- samples/utils/Monitor.py | 68 ++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/samples/utils/Monitor.py b/samples/utils/Monitor.py index ce1ff26f..8436364d 100644 --- a/samples/utils/Monitor.py +++ b/samples/utils/Monitor.py @@ -7,17 +7,18 @@ from optparse import OptionParser import time -def stripEndl(line): - if len(line) > 0 and line[-1] == '\n': - return line[:-1] - return line +def onlyOnceTrue(iterable): + firstTime = True + for element in iterable: + yield firstTime, element + firstTime = False class ProcessHandler: def __init__(self, cmd_line, location, sleep_initial=None): if not sleep_initial == None: # print('sleeping {} [s]'.format(sleep_initial)) time.sleep(float(sleep_initial)) - self.cmd = cmd_line.strip().split() + self.cmd = cmd_line.split() if not location == None: self.cmd[0] = os.path.join(location, self.cmd[0]) self.thread = threading.Thread(target=self.run_) @@ -25,12 +26,8 @@ def __init__(self, cmd_line, location, sleep_initial=None): self.stdout = '' self.stderr = '' - def to_string(self, prune): - if prune: - temp = list(self.cmd) - temp[0] = os.path.basename(temp[0]) - return ' '.join(temp) - return ' '.join(self.cmd) + def __str__(self): + return ' '.join([os.path.basename(piece) if isFirst else piece for isFirst, piece in onlyOnceTrue(self.cmd)]) def run_(self): hndlr = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) @@ -46,13 +43,8 @@ def get(self): return self.stdout, self.stderr def fromFile(source, location=None, sleep_in_between=None): - processes = [] with open(source, 'r') as stream: - first = True - for line in stream: - processes.append(ProcessHandler(line.strip(), location, None if first else sleep_in_between)) - first = False - return processes + return [ProcessHandler(line.strip(), location, None if firstTime else sleep_in_between) for firstTime, line in onlyOnceTrue(stream.readlines())] class Monitor: LAYOUT_TEMPLATE=""" @@ -82,36 +74,29 @@ class Monitor: """ SHELL_TEMPLATE = """ -
-
$CMD
+
+
{CMD}

-$LINES +{LINES}
""" def makeShell(process, w): - content = Monitor.SHELL_TEMPLATE - content = content.replace('$WIDTH', str(w)) - content = content.replace('$CMD', process.to_string(True)) - shell_content = '' - out, err = process.get() - for line in StringIO(out).readlines(): - shell_content += '
{}
\n'.format( stripEndl(line) ) - for line in StringIO(err).readlines(): - shell_content += '
{}
\n'.format( stripEndl(line) ) - content = content.replace('$LINES', shell_content) - return content + out, err = process.get() + shell_content = '\n'.join(['
{}
'.format( line.strip() ) for line in StringIO(out).readlines()]) + shell_content += '\n'.join(['
{}
'.format( line.strip() ) for line in StringIO(err).readlines()]) + return Monitor.SHELL_TEMPLATE.format(**{ + 'WIDTH': w, + 'CMD': str(process), + 'LINES': shell_content + }) def make(cmd_source, destination, location=None, sleep_in_between=None): procs = ProcessHandler.fromFile(cmd_source, location, sleep_in_between) - monitor_page = Monitor.LAYOUT_TEMPLATE w = int(round(100 / len(procs), 0)) - shells = '' - for proc in procs: - shells += '{}\n'.format( Monitor.makeShell(proc, w) ) - monitor_page = monitor_page.replace('$SHELLS', shells) + shells = '\n'.join([Monitor.makeShell(proc, w) for proc in procs]) with open(destination, 'w') as stream: - stream.write(monitor_page) + stream.write(Monitor.LAYOUT_TEMPLATE.replace('$SHELLS', shells)) def test(): tmpdirname = tempfile.TemporaryDirectory() @@ -125,12 +110,8 @@ def test(): print('open in a browser {}'.format(output)) def monitor(options): - try: - Monitor.make(options.cmd, options.dest, options.location, options.sleep) - print() - print('open in a browser {} to see the results sent by the run processes'.format(options.dest)) - except: - sys.exit(1) + Monitor.make(options.cmd, options.dest, options.location, options.sleep) + print('\nopen in a browser {} to see the results sent by the run processes'.format(options.dest)) def main(): parser = OptionParser() @@ -155,6 +136,5 @@ def main(): print(''.join(['Running {}'.format(line) for line in stream.readlines()])) print('\n\nwaiting for all the spawned processes to complete ...\n\n') - if __name__ == '__main__': main() From 5af23508cdc67116ac50b496b9b0ed70c57f05c8 Mon Sep 17 00:00:00 2001 From: Foo Date: Sat, 24 Feb 2024 17:22:42 +0100 Subject: [PATCH 5/7] Refactoring + doc improved --- .gitignore | 1 + README.md | 48 ++++---- samples/README.cpp | 17 +-- samples/tcp/TcpServer.cpp | 1 + src/header/MinimalSocket/Error.h | 38 +++--- src/header/MinimalSocket/NonCopiable.h | 19 +++ src/header/MinimalSocket/core/Address.h | 15 ++- src/header/MinimalSocket/core/Definitions.h | 14 +-- src/header/MinimalSocket/core/Receiver.h | 16 ++- src/header/MinimalSocket/core/Sender.h | 24 ++-- src/header/MinimalSocket/core/Socket.h | 23 ++-- src/header/MinimalSocket/core/SocketContext.h | 38 ++++-- src/header/MinimalSocket/tcp/TcpClient.h | 4 +- src/header/MinimalSocket/tcp/TcpServer.h | 24 ++-- src/header/MinimalSocket/udp/UdpSocket.h | 28 ++--- src/src/Error.cpp | 13 ++- src/src/SocketAddress.cpp | 20 ++-- src/src/SocketAddress.h | 6 +- src/src/SocketFunctions.cpp | 8 +- src/src/SocketFunctions.h | 10 +- src/src/{SocketId.cpp => SocketHandler.cpp} | 15 +-- src/src/{SocketId.h => SocketHandler.h} | 24 ++-- src/src/Utils.cpp | 51 --------- src/src/Utils.h | 61 +++++++--- src/src/core/Address.cpp | 7 +- src/src/core/Definitions.cpp | 12 +- src/src/core/Receiver.cpp | 108 ++++++++++-------- src/src/core/Sender.cpp | 92 ++++++--------- src/src/core/Socket.cpp | 38 +++--- src/src/core/SocketContext.cpp | 26 ----- src/src/tcp/TcpClient.cpp | 8 +- src/src/tcp/TcpServer.cpp | 19 ++- src/src/udp/UdpSocket.cpp | 42 +++---- tests/SlicedOps.cpp | 9 +- 34 files changed, 436 insertions(+), 443 deletions(-) create mode 100644 src/header/MinimalSocket/NonCopiable.h rename src/src/{SocketId.cpp => SocketHandler.cpp} (87%) rename src/src/{SocketId.h => SocketHandler.h} (72%) delete mode 100644 src/src/Utils.cpp diff --git a/.gitignore b/.gitignore index ffb44a60..1e59ac21 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build .vscode *.log test.html +TODO diff --git a/README.md b/README.md index 42d62e14..b07cad53 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ ## INTRO -**MinimalSocket** gives you a modern **C++** interface to create, connect and handle **tcp** and **udp** sockets, in a -completely platform independent way. The supported systems are: **Windows**, any **Linux** distro and **MacOS**. +**MinimalSocket** gives you a modern **C++** library to set up and create **tcp** and **udp** socket connections, in a +completely platform agnostic way. The supported platforms are: **Windows**, any **Linux** distro and **MacOS**. -Check [Features](#features) to see details about the various features of **MinimalSocket**. You can refer to [Usage](#usage) and [Samples](#samples) to see how to use **MinimalSocket**. +Check [Features](#features) to see details about the various features of **MinimalSocket**. Read [Usage](#usage) and [Samples](#samples) to see how easy is to use **MinimalSocket**. This is a **CMake** project, check [CMake support](#cmake-support) to see how this library can be integrated. @@ -23,18 +23,18 @@ Remember to leave a **star** in case you have found this library useful. Haven't left a **star** already? Do it now ;)! -**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both low level buffer of chars or high level string. Indeed, this is actually the only capability you need for a socket, as more complex messages can be encoded and decoded using among the others approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). +**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both low level buffer of chars or high level string. Indeed, this is actually the only capability you need for a socket, as more complex messages can be serialized to a string or internalized from a string using, among the others, approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). -This are the most notable properties of **MinimalSocket**: -- A modern **C++** interface allows you to set up and build connections in terms of objects. Sockets are not opened as soon as the wrapping object is created, but you after calling a proper method, allowing you to decouple socket creation from socket opening. Sockets are automatically closed (and all relevant information cleaned after destroying the wrapping object). -- You don't need to access low level functions from system modules: let **MinimalSocket** do it for you. Actually, all the system specific modules, functions, linkages are kept completely private. -- **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**), refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported -- Many sockets operations are by default blocking. However, **MinimalSocket** allows you also to opt for non-blocking versions off such operations, specifying a **timeout** to use, after which the operation terminates in any case. In particular, the operations allowing for such possibility are: - - non blocking receive (send are always intrinsically non blocking) +This are the most notable characteristics of **MinimalSocket**: +- A modern **C++** object oriented API allowing you to set up and build socket connections. Typically, socket handlers are represented by the classes part of this library. Any time an object is created, the related socket is closed in order to defer the opening at the convenient moment. This allows you to decouple the moments when sockets are created from those where they are actually connected. Any connection is automatically closed when the handler object is destroyed (and all relevant information cleaned up after destroying the wrapping object). +- Prevent you from handling low level socket programming, abstracting from the particular platform hosting your application(s): let **MinimalSocket** do all the work for you. Morevoer, all the platform specific modules, functions, linkages are not exposed. +- **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**) addresses, refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported +- Many sockets operations are by default blocking. However, **MinimalSocket** allows you also to use specify **timeout**(s) to use, after which the operation terminates in any case giving the control back to the caller. In particular, the operations allowing for such possibility are: + - receive (send are always intrinsically non blocking) - acceptance of a new client from the tcp server side -- **MinimalSocket** is tested to be **thread safe**. Morevoer, you can also send while receiving in different dedicated threads. This allows you to easily create your own asynchronous sockets, building upon the classes offered by this library. -- **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or sconnected during its lifetime. -- Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets specification version. +- **MinimalSocket** is tested to be **thread safe**. However, notice that you can send while receiving for a certain socket, but from different threads. This allows you to easily create your own asynchronous sockets, building on top of the classes offered by this library. +- **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or disconnected during its lifetime. +- Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets version if you need. ## USAGE @@ -48,7 +48,7 @@ To create a **tcp** server you just need to build a **tcp::TcpServer** object: ```cpp #include -MinimalSocket::Port port = 15768; // port the server needs to bind +MinimalSocket::Port port = 15768; // the port to bind MinimalSocket::tcp::TcpServer tcp_server(port, MinimalSocket::AddressFamily::IP_V4); ``` @@ -61,9 +61,9 @@ bool success = tcp_server.open(); and now you are ready to accept new clients: ```cpp -// accepts next client asking connection +// accepts the next client that will ask the connection MinimalSocket::tcp::TcpConnection accepted_connection = - tcp_server.acceptNewClient(); // blocing till a client actually asks the + tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection ``` @@ -92,8 +92,10 @@ MinimalSocket::tcp::TcpClient tcp_client( open it: ```cpp -// open the client: asks connection to server -bool success = tcp_client.open(); + // Open the server. Here, the client will ask the connection to specified + // server. After that, the client will be actually connected. + bool success = + tcp_client.open(); // blocking till the connection is actually established ``` you can now receive and send information with the remote server by simply doing this: @@ -120,7 +122,7 @@ MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, open it: ```cpp -// open the client: reserve port for this cocket +// Open the server. This will bind the specified port. bool success = udp_socket.open(); ``` @@ -167,7 +169,7 @@ udp_connected_socket.send("a message to send"); Haven't left a **star** already? Do it now ;)! -Instructions about the **tcp** samples are contained [here](./samples/tcp/README.md), while [here](./samples/udp/README.md) the **udp** samples are explained. +Instructions about **tcp** samples can be found [here](./samples/tcp/README.md), while **udp** samples are [here](./samples/udp/README.md) discussed. ATTENTION!!! The Samples execution might be blocked the first time by your firewall: set up properly your firewall or run the samples with the [administrator privileges](https://www.techopedia.com/definition/4961/administrative-privileges#:~:text=Administrative%20privileges%20are%20the%20ability,as%20a%20database%20management%20system.) @@ -175,7 +177,7 @@ ATTENTION!!! The Samples execution might be blocked the first time by your firew Haven't left a **star** already? Do it now ;)! -To consume this library you can rely on [CMake](https://cmake.org). +In order to consume this library you can rely on [CMake](https://cmake.org). More precisely, You can fetch this package and link to the **MinimalSocket** library: ```cmake include(FetchContent) @@ -195,5 +197,5 @@ target_link_libraries(${TARGET_NAME} ) ``` -All the system specific modules are internally inlcluded and don't exposed to the outside. -Moreover, under **Windows**, **wsock32** and **ws2_32** are privately linked and you don't need to link them again when integrating **MinimalSocket**. +All the system specific modules are internally inlcluded and are not exposed. +Moreover, under **Windows**, **wsock32** and **ws2_32** are privately linked and you don't need to link them again when consuming **MinimalSocket**. diff --git a/samples/README.cpp b/samples/README.cpp index 8b234360..16288cc3 100644 --- a/samples/README.cpp +++ b/samples/README.cpp @@ -1,16 +1,17 @@ // tcp server #include int main() { - MinimalSocket::Port port = 15768; // port the server needs to bind + MinimalSocket::Port port = 15768; // the port to bind MinimalSocket::tcp::TcpServer tcp_server(port, MinimalSocket::AddressFamily::IP_V4); - // open the server: binds the port and start to listen on the port + // Open the server. This will bind the port and the server will start to + // listen for connection requests. bool success = tcp_server.open(); - // accepts next client asking connection + // accepts the next client that will ask the connection MinimalSocket::tcp::TcpConnection accepted_connection = - tcp_server.acceptNewClient(); // blocing till a client actually asks the + tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection // receive a message @@ -30,8 +31,10 @@ int main() { MinimalSocket::tcp::TcpClient tcp_client( MinimalSocket::Address{server_address, server_port}); - // open the client: asks connection to server - bool success = tcp_client.open(); + // Open the server. Here, the client will ask the connection to specified + // server. After that, the client will be actually connected. + bool success = + tcp_client.open(); // blocking till the connection is actually established // send a message tcp_client.send("a message to send"); @@ -49,7 +52,7 @@ int main() { MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, MinimalSocket::AddressFamily::IP_V6); - // open the client: reserve port for this cocket + // Open the server. This will bind the specified port. bool success = udp_socket.open(); // send a message to another udp diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index 419c1d55..89a11133 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -16,6 +16,7 @@ #include #include +#include #include using namespace std; diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index 733c84a8..0a568db4 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -16,46 +16,46 @@ class Error : public std::runtime_error { public: Error(const std::string &what) : std::runtime_error(what){}; - template Error(Args... args) : Error(merge(args...)) {} + template + Error(const Args &...args) : Error(merge(args...)) {} protected: - template static std::string merge(Args... args) { + template static std::string merge(const Args &...args) { std::stringstream stream; - merge(stream, args...); + (merge_(stream, args), ...); return stream.str(); }; - template - static void merge(std::stringstream &stream, const T ¤t, - Args... remaining) { - stream << current; - merge(stream, remaining...); - }; - - template - static void merge(std::stringstream &stream, const T &back) { - stream << back; + template + static void merge_(std::stringstream &stream, const T &arg) { + stream << arg; }; }; -class ErrorCodeAware { +class ErrorCodeHolder { public: - int getErrorCode() const { return error_code; } + ErrorCodeHolder(); -protected: - ErrorCodeAware(); + int getErrorCode() const { return errorCode; } private: - int error_code; + int errorCode; }; -class SocketError : public ErrorCodeAware, public Error { + +class SocketError : public ErrorCodeHolder, public Error { public: /** * @brief last error code raised by the socket API is automatically retrieved + * and appended to error message */ SocketError(const std::string &what); template SocketError(const Args &...args) : SocketError{merge(args...)} {}; }; + +class TimeOutError : public Error { +public: + TimeOutError() : Error("Timeout"){}; +}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/NonCopiable.h b/src/header/MinimalSocket/NonCopiable.h new file mode 100644 index 00000000..4d683e02 --- /dev/null +++ b/src/header/MinimalSocket/NonCopiable.h @@ -0,0 +1,19 @@ +/** + * Author: Andrea Casalino + * Created: 01.28.2020 + * + * report any bug to andrecasa91@gmail.com. + **/ + +#pragma once + +namespace MinimalSocket { +class NonCopiable { +public: + NonCopiable(const NonCopiable &) = delete; + NonCopiable &operator=(const NonCopiable &) = delete; + +protected: + NonCopiable() = default; +}; +} // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index e6ca3aca..310951fb 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -34,22 +34,25 @@ class Address { * In case of invalid host, the object is built but left empty (i.e. *this == * nullptr would be true) */ - Address(const std::string &hostIp, const Port &port); + Address(const std::string &hostIp, Port port); /** - * @brief A representation of a local host address is created. + * @brief Local host address is asumed. */ - Address(const Port &port, const AddressFamily &family = AddressFamily::IP_V4); + Address(Port port, AddressFamily family = AddressFamily::IP_V4); const std::string &getHost() const { return this->host; }; - const Port &getPort() const { return this->port; }; - const AddressFamily &getFamily() const { return this->family; }; + Port getPort() const { return this->port; }; + AddressFamily getFamily() const { return this->family; }; bool operator==(const Address &o) const; Address(const Address &) = default; Address &operator=(const Address &) = default; + Address(Address &&) = default; + Address &operator=(Address &&) = default; + private: Address() = default; @@ -59,7 +62,7 @@ class Address { }; /** - * @return host:port into a string. + * @return "host:port" into a string. */ std::string to_string(const Address &subject); diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h index e4ad3e37..708b2b4f 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -11,26 +11,26 @@ #include namespace MinimalSocket { -struct Buffer { +struct BufferView { char *buffer; - const std::size_t buffer_size; + std::size_t buffer_size; }; /** * @brief sets all values inside the passed buffer to 0 */ -void clear(const Buffer &subject); +void clear(BufferView &subject); /** * @param subject the string buffer to convert * @return a buffer pointing to the first element of the subject, and a lenght * equal to the current size of subject */ -Buffer makeStringBuffer(std::string &subject); +BufferView makeBufferView(std::string &subject); -struct ConstBuffer { +struct BufferViewConst { const char *buffer; - const std::size_t buffer_size; + std::size_t buffer_size; }; /** @@ -38,7 +38,7 @@ struct ConstBuffer { * @return an immutable buffer pointing to the first element of the subject, and * a lenght equal to the current size of subject */ -ConstBuffer makeStringConstBuffer(const std::string &subject); +BufferViewConst makeBufferViewConst(const std::string &subject); enum class SocketType { UDP, TCP }; diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index caeeaf61..b6fffef9 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -15,10 +15,16 @@ namespace MinimalSocket { class ReceiverBase : public virtual Socket { protected: - std::unique_ptr> - lazyUpdateReceiveTimeout(const Timeout &timeout); + template + void lazyUpdateAndUseTimeout(const Timeout &to, Pred what) { + std::scoped_lock lock{receive_mtx}; + updateTimeout_(to); + what(receive_timeout); + } private: + void updateTimeout_(const Timeout &timeout); + std::mutex receive_mtx; Timeout receive_timeout = NULL_TIMEOUT; }; @@ -40,7 +46,7 @@ class Receiver : public ReceiverBase { * message. It can be also lower then buffer size, as less bytes might be * received. */ - std::size_t receive(const Buffer &message, + std::size_t receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); /** @@ -61,7 +67,7 @@ class Receiver : public ReceiverBase { /** * @brief Typically associated to a non connected socket, whose remote peer that - * sends bytes is known and may change over the time. + * sends bytes is not fixed. * Attention!! Even when calling from different threads some simultaneously * receive, they will be satisfited one at a time, as an internal mutex must be * locked before starting to receive. @@ -81,7 +87,7 @@ class ReceiverUnkownSender : public ReceiverBase { * also lower then buffer size, as less bytes might be received. * In case no bytes were received within the timeout, a nullopt is returned. */ - std::optional receive(const Buffer &message, + std::optional receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); struct ReceiveStringResult { diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 125adb06..161a07c8 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -10,8 +10,6 @@ #include #include -#include -#include #include #include @@ -29,7 +27,7 @@ class Sender : public virtual Socket { * @param message the buffer storing the bytes to send * @return true in case all the bytes were successfully sent */ - bool send(const ConstBuffer &message); + bool send(const BufferViewConst &message); /** * @param message the buffer storing the bytes to send as a string @@ -43,7 +41,7 @@ class Sender : public virtual Socket { /** * @brief Typically associated to a non connected socket, whose remote peer that - * sends bytes is known and may change over the time. + * sends bytes is not fixed. * Attention!! It is thread safe to simultaneously send messages from different * threads to many different recipients. * However, be aware that in case 2 or more threads are sending a message to the @@ -57,7 +55,7 @@ class SenderTo : public virtual Socket { * @return true in case all the bytes were successfully sent to the specified * recipient */ - bool sendTo(const ConstBuffer &message, const Address &recipient); + bool sendTo(const BufferViewConst &message, const Address &recipient); /** * @param message the buffer storing the bytes to send as a string @@ -68,20 +66,20 @@ class SenderTo : public virtual Socket { bool sendTo(const std::string &message, const Address &recipient); private: - std::future reserveAddress(const Address &to_reserve); - void freeAddress(const Address &to_reserve); + std::mutex &getRecipientMtx(const Address &recipient); std::mutex recipients_register_mtx; - struct AddressHasher { - std::hash string_hasher; - std::size_t operator()(const Address &subject) const { - return string_hasher(to_string(subject)); + return getHasher()(to_string(subject)); + } + + static std::hash &getHasher() { + static std::hash res = std::hash{}; + return res; } }; - using WaitingToSendQueue = std::list>; - std::unordered_map + std::unordered_map, AddressHasher> recipients_register; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index d96aaada..9bf52c11 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -15,7 +15,6 @@ #include #include #include -#include namespace MinimalSocket { #ifdef _WIN32 @@ -48,7 +47,7 @@ class WSAManager { }; #endif -class SocketIdWrapper; +class SocketHandler; /** * @brief The base onject of any kind of socket. @@ -61,7 +60,7 @@ class Socket { Socket &operator=(const Socket &) = delete; /** - * @return the socket id associated to this object. + * @return the socket descriptor associated to this object. * * This might be: * @@ -80,23 +79,25 @@ class Socket { * number. Beware that you should really know what you are doing when using * this number. */ - int accessSocketID() const; + int getSocketDescriptor() const; protected: Socket(); - static void transfer(Socket &receiver, Socket &giver); + void steal(Socket &giver); + void transfer(Socket &recipient) { recipient.steal(*this); } - const SocketIdWrapper &getIDWrapper() const; - SocketIdWrapper &getIDWrapper(); - void resetIDWrapper(); + const SocketHandler &getHandler() const; + SocketHandler &getHandler(); + void resetHandler(); private: - std::unique_ptr socket_id_wrapper; + std::unique_ptr socket_id_wrapper; }; class Openable : public virtual Socket { public: + virtual ~Openable() = default; bool wasOpened() const { return opened; } /** @@ -114,8 +115,8 @@ class Openable : public virtual Socket { protected: Openable() = default; - static void transfer(Openable &receiver, - Openable &giver); // Socket::transfer(...) is also called + void steal(Openable &giver); // Socket::steal(...) is also called + void transfer(Openable &recipient) { recipient.steal(*this); } virtual void open_() = 0; diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index f931a6b8..d6614f35 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -15,15 +15,19 @@ namespace MinimalSocket { class RemoteAddressAware { public: + RemoteAddressAware(const RemoteAddressAware &o) + : remote_address{o.remote_address} {} + RemoteAddressAware &operator=(const RemoteAddressAware &o) { + remote_address = o.remote_address; + return *this; + } + /** * @return the address of the peer that can exchange messages with this * socket. */ Address getRemoteAddress() const; - RemoteAddressAware(const RemoteAddressAware &); - RemoteAddressAware &operator=(const RemoteAddressAware &); - protected: /** * @throw in case the passed address is invalid (i.e. address == nullptr is @@ -38,15 +42,21 @@ class RemoteAddressAware { class PortToBindAware { public: + PortToBindAware(const PortToBindAware &o) + : port_to_bind{o.port_to_bind.load()}, must_be_free_port{ + o.must_be_free_port.load()} {} + PortToBindAware &operator=(const PortToBindAware &o) { + port_to_bind = o.port_to_bind.load(); + must_be_free_port = o.must_be_free_port.load(); + return *this; + } + /** * @return the port that will be reserved, in case the socket was not already * opened, or the port actually reserved when the socket was opened. */ Port getPortToBind() const { return port_to_bind; } - PortToBindAware(const PortToBindAware &); - PortToBindAware &operator=(const PortToBindAware &); - /** * @brief Used to enforce the fact that this port should be not previously * binded by anyone else when opening the socket. Beware that the default @@ -57,9 +67,9 @@ class PortToBindAware { bool shallBeFreePort() const { return must_be_free_port; } protected: - PortToBindAware(const Port &port) : port_to_bind(port){}; + PortToBindAware(Port port) : port_to_bind(port){}; - void setPort(const Port &port) { port_to_bind = port; }; + void setPort(Port port) { port_to_bind = port; }; private: std::atomic port_to_bind; @@ -68,17 +78,21 @@ class PortToBindAware { class RemoteAddressFamilyAware { public: + RemoteAddressFamilyAware(const RemoteAddressFamilyAware &o) + : remote_address_family{o.remote_address_family.load()} {} + RemoteAddressFamilyAware &operator=(const RemoteAddressFamilyAware &o) { + remote_address_family = o.remote_address_family.load(); + return *this; + } + /** * @return the address family of the peer that can exchange messages with this * socket. */ AddressFamily getRemoteAddressFamily() const { return remote_address_family; } - RemoteAddressFamilyAware(const RemoteAddressFamilyAware &); - RemoteAddressFamilyAware &operator=(const RemoteAddressFamilyAware &); - protected: - RemoteAddressFamilyAware(const AddressFamily &family) + RemoteAddressFamilyAware(AddressFamily family) : remote_address_family(family){}; private: diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index e39d1e54..4e35d618 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -7,12 +7,14 @@ #pragma once +#include #include #include #include namespace MinimalSocket::tcp { -class TcpClient : public Openable, +class TcpClient : public NonCopiable, + public Openable, public Sender, public Receiver, public RemoteAddressAware { diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 99adc837..62d0da1c 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -17,11 +18,12 @@ namespace MinimalSocket::tcp { class TcpServer; /** - * @brief An already accepted connection to a client. - * An istance of this object can be built by before creating a TcpServer, open - * it and call acceptNewClient(). + * @brief Handler of an already established connection with a client, on the + * server side. + * An istance of this object is created calling TcpServer::acceptNewClient(). */ -class TcpConnection : public Sender, +class TcpConnection : public NonCopiable, + public Sender, public Receiver, public RemoteAddressAware { friend class TcpServer; @@ -34,9 +36,9 @@ class TcpConnection : public Sender, TcpConnection(const Address &remote_address); }; -class TcpServer : public PortToBindAware, +class TcpServer : public NonCopiable, + public PortToBindAware, public RemoteAddressFamilyAware, - public virtual Socket, public Openable { public: TcpServer(TcpServer &&o); @@ -52,8 +54,8 @@ class TcpServer : public PortToBindAware, * @param accepted_client_family family of the client that will ask the * connection to this server */ - TcpServer(const Port port_to_bind = ANY_PORT, - const AddressFamily &accepted_client_family = AddressFamily::IP_V4); + TcpServer(Port port_to_bind = ANY_PORT, + AddressFamily accepted_client_family = AddressFamily::IP_V4); /** * @brief Wait till accepting the connection from a new client. This is a @@ -83,9 +85,9 @@ class TcpServer : public PortToBindAware, void open_() override; private: - std::atomic client_queue_size = - 50; // maximum number of clients put in the queue wiating for connection - // to be accepted + // maximum number of clients waiting for the connection to be + // accepted + std::atomic client_queue_size = 50; std::mutex accept_mtx; }; diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index eb785175..2d3bd86e 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -28,7 +29,8 @@ class UdpConnected; * At the same time, this udp can send messages to any other non connected udp * sockets. */ -class UdpBinded : public SenderTo, +class UdpBinded : public NonCopiable, + public SenderTo, public ReceiverUnkownSender, public PortToBindAware, public RemoteAddressFamilyAware, @@ -44,9 +46,8 @@ class UdpBinded : public SenderTo, * @param port_to_bind the port to reserve by this udp * @param accepted_connection_family the kind of udp that can reach this one */ - UdpBinded( - const Port port_to_bind = ANY_PORT, - const AddressFamily &accepted_connection_family = AddressFamily::IP_V4); + UdpBinded(Port port_to_bind = ANY_PORT, + AddressFamily accepted_connection_family = AddressFamily::IP_V4); /** * @brief Connects the udo socket to the specified remote address. @@ -93,7 +94,8 @@ class UdpBinded : public SenderTo, * incoming from udp sockets different from the remote address are filtered out. * At the same time, the remote address might also not exists at all. */ -class UdpConnected : public Sender, +class UdpConnected : public NonCopiable, + public Sender, public Receiver, public PortToBindAware, public RemoteAddressAware, @@ -109,7 +111,7 @@ class UdpConnected : public Sender, * @param remote_address remote address of the peer * @param port the port to reserve by this udp */ - UdpConnected(const Address &remote_address, const Port &port = ANY_PORT); + UdpConnected(const Address &remote_address, Port port = ANY_PORT); /** * @brief disconnect the underlying socket, generating an unbinded udp that @@ -132,17 +134,17 @@ class UdpConnected : public Sender, * @param initial_message the message sent from the remote peer to detect its * address */ -UdpConnected -makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family, - std::string *initial_message = nullptr); +UdpConnected makeUdpConnectedToUnknown(Port port, + AddressFamily accepted_connection_family, + std::string *initial_message = nullptr); /** * @brief non blocking version of makeUdpConnectedToUnknown(const Port &, const * AddressFamily &, std::string *). In case no remote peer sends at least 1 byte * within the timeout, a nullopt is returned. */ -std::optional makeUdpConnectedToUnknown( - const Port &port, const AddressFamily &accepted_connection_family, - const Timeout &timeout, std::string *initial_message = nullptr); +std::optional +makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, + const Timeout &timeout, + std::string *initial_message = nullptr); } // namespace MinimalSocket::udp diff --git a/src/src/Error.cpp b/src/src/Error.cpp index 2143b12a..eb2a4264 100644 --- a/src/src/Error.cpp +++ b/src/src/Error.cpp @@ -7,18 +7,23 @@ #include -#include "SocketId.h" +#include "../src/SocketHandler.h" namespace MinimalSocket { -ErrorCodeAware::ErrorCodeAware() { - error_code = +namespace { +int getLastErrorCode() { + int res = #ifdef _WIN32 WSAGetLastError(); #else static_cast(errno); #endif + return res; } +} // namespace + +ErrorCodeHolder::ErrorCodeHolder() : errorCode{getLastErrorCode()} {} SocketError::SocketError(const std::string &what) - : ErrorCodeAware(), Error(what, " , error code: ", getErrorCode()) {} + : ErrorCodeHolder{}, Error(what, " , error code: ", getErrorCode()) {} } // namespace MinimalSocket diff --git a/src/src/SocketAddress.cpp b/src/src/SocketAddress.cpp index 29f42ade..bb3f6cb7 100644 --- a/src/src/SocketAddress.cpp +++ b/src/src/SocketAddress.cpp @@ -17,7 +17,7 @@ namespace MinimalSocket { std::optional toSocketAddressIpv4(const std::string &host, - const Port &port) { + Port port) { #ifdef _WIN32 WSALazyInitializer::lazyInit(); #endif @@ -33,14 +33,14 @@ std::optional toSocketAddressIpv4(const std::string &host, #ifdef _WIN32 in_addr ia; if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { - ::memcpy(&result->sin_addr, &ia, sizeof(in_addr)); - return result; + ::memcpy(&result->sin_addr, &ia, sizeof(in_addr)); + return result; } #else in_addr ia; if (1 == ::inet_pton(AF_INET, host.c_str(), &ia)) { - result->sin_addr.s_addr = ia.s_addr; - return result; + result->sin_addr.s_addr = ia.s_addr; + return result; } #endif @@ -60,14 +60,14 @@ std::optional toSocketAddressIpv4(const std::string &host, return std::nullopt; } - const auto* ipv4 = reinterpret_cast(res->ai_addr); + const auto *ipv4 = reinterpret_cast(res->ai_addr); result->sin_addr.s_addr = ipv4->sin_addr.s_addr; ::freeaddrinfo(res); return result; } std::optional toSocketAddressIpv6(const std::string &host, - const Port &port) { + Port port) { #ifdef _WIN32 WSALazyInitializer::lazyInit(); #endif @@ -84,8 +84,8 @@ std::optional toSocketAddressIpv6(const std::string &host, #ifdef _WIN32 in6_addr ia; if (1 == ::inet_pton(AF_INET6, host.c_str(), &ia)) { - ::memcpy(&result->sin6_addr, &ia, sizeof(in6_addr)); - return result; + ::memcpy(&result->sin6_addr, &ia, sizeof(in6_addr)); + return result; } #else in6_addr ia; @@ -111,7 +111,7 @@ std::optional toSocketAddressIpv6(const std::string &host, return std::nullopt; } - const auto* ipv6 = reinterpret_cast(res->ai_addr); + const auto *ipv6 = reinterpret_cast(res->ai_addr); result->sin6_addr = ipv6->sin6_addr; ::freeaddrinfo(res); return result; diff --git a/src/src/SocketAddress.h b/src/src/SocketAddress.h index 240f112d..417a3a5d 100644 --- a/src/src/SocketAddress.h +++ b/src/src/SocketAddress.h @@ -7,7 +7,7 @@ #pragma once -#include "SocketId.h" +#include "SocketHandler.h" namespace MinimalSocket { /** @@ -51,14 +51,14 @@ static constexpr std::size_t MAX_POSSIBLE_ADDRESS_SIZE = * of the address */ std::optional toSocketAddressIpv4(const std::string &host, - const Port &port); + Port port); /** * @brief checks the address syntax and in case * it's valid as an ipv6, creates the socket API representation * of the address */ std::optional toSocketAddressIpv6(const std::string &host, - const Port &port); + Port port); std::optional toPort(const SocketAddress &address); diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index fe5a5cb8..f51d6bdd 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -20,8 +20,8 @@ namespace { #endif } // namespace -Port bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port, const bool must_be_free_port) { +Port bind(SocketID socket_id, AddressFamily family, Port port, + bool must_be_free_port) { if (!must_be_free_port) { int reusePortOptVal = 1; ::setsockopt(socket_id, SOL_SOCKET, REBIND_OPTION, @@ -94,14 +94,14 @@ Port bind(const SocketID &socket_id, const AddressFamily &family, return binded_port; } -void listen(const SocketID &socket_id, const std::size_t backlog_size) { +void listen(SocketID socket_id, std::size_t backlog_size) { if (::listen(socket_id, static_cast(backlog_size)) == SCK_SOCKET_ERROR) { auto err = SocketError{"Error: listening on reserved port"}; throw err; } } -void connect(const SocketID &socket_id, const Address &remote_address) { +void connect(SocketID socket_id, const Address &remote_address) { visitAddress( remote_address.getFamily(), [&]() { diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index 026b9f4e..eaed8e76 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -7,14 +7,14 @@ #pragma once -#include "SocketId.h" +#include "SocketHandler.h" namespace MinimalSocket { // return port actually binded (as you could pass to the function also AnyPort) -Port bind(const SocketID &socket_id, const AddressFamily &family, - const Port &port, const bool must_be_free_port); +Port bind(SocketID socket_id, AddressFamily family, Port port, + bool must_be_free_port); -void listen(const SocketID &socket_id, const std::size_t backlog_size); +void listen(SocketID socket_id, std::size_t backlog_size); -void connect(const SocketID &socket_id, const Address &remote_address); +void connect(SocketID socket_id, const Address &remote_address); } // namespace MinimalSocket diff --git a/src/src/SocketId.cpp b/src/src/SocketHandler.cpp similarity index 87% rename from src/src/SocketId.cpp rename to src/src/SocketHandler.cpp index 9c6c7641..5e75091f 100644 --- a/src/src/SocketId.cpp +++ b/src/src/SocketHandler.cpp @@ -7,11 +7,12 @@ #include +#include "SocketHandler.h" #include "Utils.h" namespace MinimalSocket { #ifdef _WIN32 -WSALazyInitializer::WSALazyInitializer(const WSAVersion& version) +WSALazyInitializer::WSALazyInitializer(const WSAVersion &version) : configured_version(version) { WSADATA wsa; const BYTE version_major = static_cast(version[0]); @@ -55,8 +56,9 @@ std::unique_ptr WSALazyInitializer::lazy_proxy = nullptr; void WSALazyInitializer::lazyInit() { auto version = WSAManager::getWsaVersion(); std::scoped_lock lock(WSALazyInitializer::lazy_proxy_mtx); - if ((nullptr != WSALazyInitializer::lazy_proxy) && (WSALazyInitializer::lazy_proxy->configured_version == version)) { - return; + if ((nullptr != WSALazyInitializer::lazy_proxy) && + (WSALazyInitializer::lazy_proxy->configured_version == version)) { + return; } try { WSALazyInitializer::lazy_proxy.reset(new WSALazyInitializer{version}); @@ -83,9 +85,9 @@ void close(SocketID &socket_id) { } } // namespace -SocketIdWrapper::~SocketIdWrapper() { MinimalSocket::close(socket_id); } +SocketHandler::~SocketHandler() { MinimalSocket::close(socket_id); } -void SocketIdWrapper::reset(const SocketID &hndl) { +void SocketHandler::reset(SocketID hndl) { if (socket_id != SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); } @@ -102,8 +104,7 @@ int domain_number(const AddressFamily &family) { } } // namespace -void SocketIdWrapper::reset(const SocketType &type, - const AddressFamily &family) { +void SocketHandler::reset(SocketType type, AddressFamily family) { if (socket_id != SCK_INVALID_SOCKET) { MinimalSocket::close(socket_id); } diff --git a/src/src/SocketId.h b/src/src/SocketHandler.h similarity index 72% rename from src/src/SocketId.h rename to src/src/SocketHandler.h index fe8a458f..3f347b23 100644 --- a/src/src/SocketId.h +++ b/src/src/SocketHandler.h @@ -48,33 +48,35 @@ using SocketID = int; * An object storing a socket API handler and containing the minimal * functionalities for interacting with it. */ -class SocketIdWrapper { +class SocketHandler { public: - SocketIdWrapper(const SocketIdWrapper &) = delete; - SocketIdWrapper &operator=(const SocketIdWrapper &) = delete; + SocketHandler(const SocketHandler &) = delete; + SocketHandler &operator=(const SocketHandler &) = delete; + SocketHandler(SocketHandler &&) = delete; + SocketHandler &operator=(SocketHandler &&) = delete; - const SocketID &accessId() const { return socket_id; }; + auto accessId() const { return socket_id; }; /** * @brief an invalid socket id is created */ - SocketIdWrapper() = default; + SocketHandler() = default; /** * @brief close and shutdown the current socket */ - ~SocketIdWrapper(); + ~SocketHandler(); /** - * @brief internally creates a new socket + * @brief regenerates the socket descriptor, i.e. creates a new socket */ - void reset(const SocketType &type, const AddressFamily &family); + void reset(SocketType type, AddressFamily family); /** - * @brief the passed handler should be already created externally - * by the socket api + * @brief the passed handler should be already externally created and setup + * (for blocking or non blocking node). */ - void reset(const SocketID &hndl); + void reset(SocketID hndl); private: SocketID socket_id = SCK_INVALID_SOCKET; diff --git a/src/src/Utils.cpp b/src/src/Utils.cpp deleted file mode 100644 index 399c01b4..00000000 --- a/src/src/Utils.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 01.28.2020 - * - * report any bug to andrecasa91@gmail.com. - **/ - -#include - -#include "Utils.h" - -#include - -namespace MinimalSocket { -void visitAddress(const AddressFamily &family, - const std::function &ipv4_case, - const std::function &ipv6_case) { - switch (family) { - case AddressFamily::IP_V4: - ipv4_case(); - break; - case AddressFamily::IP_V6: - ipv6_case(); - break; - default: - throw Error{"Unrecognized AddressFamily"}; - break; - } -} - -void try_within_timeout(const std::function &action_to_try, - const std::function &action_to_abort, - const Timeout &timeout) { - if (NULL_TIMEOUT == timeout) { - throw Error{"Invalid timeout"}; - } - auto open_task = std::async([&]() { action_to_try(); }); - auto open_task_status = open_task.wait_for(timeout); - if (open_task_status == std::future_status::ready) { - open_task.get(); // will throw if ready because an exception throwned - // before timeout - } else { - try { - action_to_abort(); - open_task.get(); - } catch (...) { - } - throw TimeOutError{}; - } -} -} // namespace MinimalSocket diff --git a/src/src/Utils.h b/src/src/Utils.h index 5befe359..c7d9f37b 100644 --- a/src/src/Utils.h +++ b/src/src/Utils.h @@ -7,31 +7,54 @@ #pragma once +#include +#include #include -#include "SocketId.h" - -#include +#include namespace MinimalSocket { -void visitAddress(const AddressFamily &family, - const std::function &ipv4_case, - const std::function &ipv6_case); - -template void copy_as(U &receiver, const U &giver) { - T &receiver_ref = receiver; - const T &giver_ref = giver; - receiver_ref = giver_ref; +template +void visitAddress(AddressFamily family, Ipv4Pred ipv4_case, + Ipv6Pred ipv6_case) { + switch (family) { + case AddressFamily::IP_V4: + ipv4_case(); + break; + case AddressFamily::IP_V6: + ipv6_case(); + break; + default: + throw Error{"Unrecognized AddressFamily"}; + break; + } } -class TimeOutError : public Error { -public: - TimeOutError() : Error("Timeout"){}; -}; - // rethrow exception if happens // throw timeout excpetion if timeout reached -void try_within_timeout(const std::function &action_to_try, - const std::function &action_to_abort, - const Timeout &timeout); +template +void try_within_timeout(TryAction action_to_try, + RecoverAction action_to_recover, + const Timeout &timeout) { + if (NULL_TIMEOUT == timeout) { + throw Error{"Invalid timeout"}; + } + auto task = std::async(action_to_try); + auto task_status = task.wait_for(timeout); + if (task_status == std::future_status::ready) { + task.get(); // will throw if ready because an exception throwned + // before timeout + } else { + try { + action_to_recover(); + task.get(); + } catch (...) { + } + throw TimeOutError{}; + } +} + +template void copy_as(T &recipient, const T &giver) { + recipient = giver; +} } // namespace MinimalSocket diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index 731df6cf..a850b46d 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -14,9 +14,8 @@ #include namespace MinimalSocket { -Address::Address(const std::string &hostIp, const Port &port) { - this->host = hostIp; - this->port = port; +Address::Address(const std::string &hostIp, Port port) + : host{hostIp}, port{port} { if (std::nullopt != toSocketAddressIpv4(hostIp, port)) { this->family = AddressFamily::IP_V4; @@ -36,7 +35,7 @@ static const std::string LOCALHOST_IPv4 = "127.0.0.1"; static const std::string LOCALHOST_IPv6 = "::1"; } // namespace -Address::Address(const std::uint16_t &port, const AddressFamily &family) { +Address::Address(std::uint16_t port, AddressFamily family) { this->port = port; this->family = family; visitAddress( diff --git a/src/src/core/Definitions.cpp b/src/src/core/Definitions.cpp index b5556011..800e10ca 100644 --- a/src/src/core/Definitions.cpp +++ b/src/src/core/Definitions.cpp @@ -7,18 +7,18 @@ #include -#include "../SocketId.h" +#include "../SocketHandler.h" namespace MinimalSocket { -void clear(const Buffer &subject) { +void clear(BufferView &subject) { ::memset(subject.buffer, 0, subject.buffer_size); } -Buffer makeStringBuffer(std::string &subject) { - return Buffer{subject.data(), subject.size()}; +BufferView makeBufferView(std::string &subject) { + return BufferView{subject.data(), subject.size()}; } -ConstBuffer makeStringConstBuffer(const std::string &subject) { - return ConstBuffer{subject.data(), subject.size()}; +BufferViewConst makeBufferViewConst(const std::string &subject) { + return BufferViewConst{subject.data(), subject.size()}; } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 7cffbd1c..6871c2e8 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -12,19 +12,17 @@ #ifndef _WIN32 #include #endif + namespace MinimalSocket { -std::unique_ptr> -ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { - std::unique_ptr> lock = - std::make_unique>(receive_mtx); +void ReceiverBase::updateTimeout_(const Timeout &timeout) { if (timeout == receive_timeout) { - return lock; + return; } receive_timeout = timeout; // set new timeout #ifdef _WIN32 auto tv = DWORD(this->receive_timeout.count()); - if (setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, + if (setsockopt(getHandler().accessId(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&tv), sizeof(DWORD)) == SOCKET_ERROR) { #else @@ -38,14 +36,12 @@ ReceiverBase::lazyUpdateReceiveTimeout(const Timeout &timeout) { std::chrono::duration_cast(receive_timeout) .count(); } - if (::setsockopt(getIDWrapper().accessId(), SOL_SOCKET, SO_RCVTIMEO, + if (::setsockopt(getHandler().accessId(), SOL_SOCKET, SO_RCVTIMEO, static_cast(&tv), sizeof(struct timeval)) != 0) { #endif - auto err = SocketError{"can't set timeout"}; - throw err; + throw SocketError{"can't set timeout"}; } - return lock; } namespace { @@ -63,61 +59,75 @@ void check_received_bytes(int &recvBytes, const Timeout &timeout) { recvBytes = 0; if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && (timeout != NULL_TIMEOUT)) { - // just out of time: tolerate + // just out of time: tolerable return; } throw error_with_code; } } // namespace -std::size_t Receiver::receive(const Buffer &message, const Timeout &timeout) { - auto lock = lazyUpdateReceiveTimeout(timeout); - clear(message); - int recvBytes = ::recv(getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0); - check_received_bytes(recvBytes, timeout); - if (recvBytes > message.buffer_size) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - return static_cast(recvBytes); +std::size_t Receiver::receive(BufferView message, const Timeout &timeout) { + std::size_t res = 0; + + lazyUpdateAndUseTimeout( + timeout, [&message, &res, this](const Timeout &timeout) { + clear(message); + + int recvBytes = ::recv(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0); + check_received_bytes(recvBytes, timeout); + if (recvBytes > message.buffer_size) { + // if here, the message received is probably corrupted + recvBytes = 0; + } + res = static_cast(recvBytes); + }); + + return res; } std::string Receiver::receive(std::size_t expected_max_bytes, const Timeout &timeout) { std::string buffer; buffer.resize(expected_max_bytes); - auto buffer_temp = makeStringBuffer(buffer); + auto buffer_temp = makeBufferView(buffer); auto recvBytes = receive(buffer_temp, timeout); buffer.resize(recvBytes); return buffer; } std::optional -ReceiverUnkownSender::receive(const Buffer &message, const Timeout &timeout) { - auto lock = lazyUpdateReceiveTimeout(timeout); - clear(message); - - char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; - SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; - - int recvBytes = - ::recvfrom(getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&sender_address[0]), - &sender_address_length); - check_received_bytes(recvBytes, timeout); - if (recvBytes > message.buffer_size) { - // if here, the message received is probably corrupted - return std::nullopt; - } - if (0 == recvBytes) { - // if here, timeout was reached - return std::nullopt; - } - return ReceiveResult{ - toAddress(reinterpret_cast(sender_address)), - static_cast(recvBytes)}; +ReceiverUnkownSender::receive(BufferView message, const Timeout &timeout) { + std::optional res; + + lazyUpdateAndUseTimeout( + timeout, [&message, &res, this](const Timeout &timeout) { + clear(message); + + char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; + + int recvBytes = + ::recvfrom(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&sender_address[0]), + &sender_address_length); + check_received_bytes(recvBytes, timeout); + if (recvBytes > message.buffer_size) { + // if here, the message received is probably corrupted + return; + } + if (0 == recvBytes) { + // if here, timeout was reached + return; + } + + res = ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; + }); + + return res; } std::optional @@ -125,12 +135,12 @@ ReceiverUnkownSender::receive(std::size_t expected_max_bytes, const Timeout &timeout) { std::string buffer; buffer.resize(expected_max_bytes); - auto buffer_temp = makeStringBuffer(buffer); + auto buffer_temp = makeBufferView(buffer); auto result = receive(buffer_temp, timeout); if (!result) { return std::nullopt; } buffer.resize(result->received_bytes); - return ReceiveStringResult{result->sender, std::move(buffer)}; + return ReceiveStringResult{std::move(result->sender), std::move(buffer)}; } } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 77f5945b..80ec5eab 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -12,76 +12,56 @@ #include "../Utils.h" namespace MinimalSocket { -bool Sender::send(const ConstBuffer &message) { +bool Sender::send(const BufferViewConst &message) { std::scoped_lock lock(send_mtx); - int sentBytes = ::send(getIDWrapper().accessId(), message.buffer, + int sentBytes = ::send(getHandler().accessId(), message.buffer, static_cast(message.buffer_size), 0); if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; - auto err = SocketError{"send failed"}; - throw err; + throw SocketError{"send failed"}; } return (sentBytes == static_cast(message.buffer_size)); } bool Sender::send(const std::string &message) { - return send(makeStringConstBuffer(message)); + return send(makeBufferViewConst(message)); } -std::future SenderTo::reserveAddress(const Address &to_reserve) { - std::scoped_lock lock(recipients_register_mtx); - auto it = recipients_register.find(to_reserve); - if (it == recipients_register.end()) { - auto &promises = recipients_register[to_reserve]; - promises.emplace_back(); - auto &promise = promises.back(); - auto result = promise.get_future(); - promise.set_value(); - return result; +std::mutex &SenderTo::getRecipientMtx(const Address &recipient) { + std::scoped_lock lock{recipients_register_mtx}; + auto &res = recipients_register[recipient]; + if (res == nullptr) { + res = std::make_unique(); } - auto &promises = it->second; - promises.emplace_back(); - auto &promise = promises.back(); - return promise.get_future(); + return *res; } -void SenderTo::freeAddress(const Address &to_reserve) { - std::scoped_lock lock(recipients_register_mtx); - auto it = recipients_register.find(to_reserve); - auto &promises = it->second; - if (1 == promises.size()) { - recipients_register.erase(it); - } else { - promises.pop_front(); - promises.front().set_value(); - } -} - -bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { - auto send_allowed = reserveAddress(recipient); - send_allowed.wait(); +bool SenderTo::sendTo(const BufferViewConst &message, + const Address &recipient) { int sentBytes; - visitAddress( - recipient.getFamily(), - [&]() { - auto socketIp4 = - toSocketAddressIpv4(recipient.getHost(), recipient.getPort()); - sentBytes = ::sendto( - getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&socketIp4.value()), - sizeof(SocketAddressIpv4)); - }, - [&]() { - auto socketIp6 = - toSocketAddressIpv6(recipient.getHost(), recipient.getPort()); - sentBytes = ::sendto( - getIDWrapper().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&socketIp6.value()), - sizeof(SocketAddressIpv6)); - }); - freeAddress(recipient); + { + std::scoped_lock lock{getRecipientMtx(recipient)}; + visitAddress( + recipient.getFamily(), + [&]() { + auto socketIp4 = + toSocketAddressIpv4(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto( + getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&socketIp4.value()), + sizeof(SocketAddressIpv4)); + }, + [&]() { + auto socketIp6 = + toSocketAddressIpv6(recipient.getHost(), recipient.getPort()); + sentBytes = ::sendto( + getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&socketIp6.value()), + sizeof(SocketAddressIpv6)); + }); + } if (sentBytes == SCK_SOCKET_ERROR) { sentBytes = 0; auto err = SocketError{"sendto failed"}; @@ -91,6 +71,6 @@ bool SenderTo::sendTo(const ConstBuffer &message, const Address &recipient) { } bool SenderTo::sendTo(const std::string &message, const Address &recipient) { - return sendTo(makeStringConstBuffer(message), recipient); + return sendTo(makeBufferViewConst(message), recipient); } } // namespace MinimalSocket diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index f628e24f..37155b74 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -8,7 +8,7 @@ #include #include -#include "../SocketId.h" +#include "../SocketHandler.h" #include "../Utils.h" namespace MinimalSocket { @@ -29,24 +29,22 @@ WSAVersion WSAManager::getWsaVersion() { Socket::~Socket() = default; -Socket::Socket() { resetIDWrapper(); } +Socket::Socket() { resetHandler(); } -int Socket::accessSocketID() const { - return static_cast(getIDWrapper().accessId()); +int Socket::getSocketDescriptor() const { + return static_cast(getHandler().accessId()); } -void Socket::transfer(Socket &receiver, Socket &giver) { - receiver.socket_id_wrapper = std::move(giver.socket_id_wrapper); - giver.resetIDWrapper(); +void Socket::steal(Socket &giver) { + this->socket_id_wrapper = std::move(giver.socket_id_wrapper); + giver.resetHandler(); } -const SocketIdWrapper &Socket::getIDWrapper() const { - return *socket_id_wrapper; -} -SocketIdWrapper &Socket::getIDWrapper() { return *socket_id_wrapper; } +const SocketHandler &Socket::getHandler() const { return *socket_id_wrapper; } +SocketHandler &Socket::getHandler() { return *socket_id_wrapper; } -void Socket::resetIDWrapper() { - socket_id_wrapper = std::make_unique(); +void Socket::resetHandler() { + socket_id_wrapper = std::make_unique(); } bool Openable::open(const Timeout &timeout) { @@ -60,7 +58,7 @@ bool Openable::open(const Timeout &timeout) { this->open_(); } else { try_within_timeout([this]() { this->open_(); }, - [this]() { this->resetIDWrapper(); }, timeout); + [this]() { this->resetHandler(); }, timeout); } opened = true; } catch (const SocketError &e) { @@ -70,21 +68,21 @@ bool Openable::open(const Timeout &timeout) { } catch (const Error &e) { exception = std::make_unique(e); } catch (...) { - exception = std::make_unique("Not opened for an unkown reason"); + exception = std::make_unique("Not opened for an unknown reason"); } if (nullptr != exception) { - this->resetIDWrapper(); + this->resetHandler(); throw *exception; } return opened; } -void Openable::transfer(Openable &receiver, Openable &giver) { - std::scoped_lock lock(receiver.open_procedure_mtx, giver.open_procedure_mtx); +void Openable::steal(Openable &giver) { + std::scoped_lock lock(this->open_procedure_mtx, giver.open_procedure_mtx); const bool o_value = giver.opened; - receiver.opened = o_value; + this->opened = o_value; giver.opened = false; - Socket::transfer(receiver, giver); + this->Socket::steal(giver); } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp index 49dc30b7..6f016a2a 100644 --- a/src/src/core/SocketContext.cpp +++ b/src/src/core/SocketContext.cpp @@ -14,22 +14,6 @@ Address RemoteAddressAware::getRemoteAddress() const { return remote_address; } -RemoteAddressAware::RemoteAddressAware(const RemoteAddressAware &o) - : remote_address(o.getRemoteAddress()) {} - -RemoteAddressAware &RemoteAddressAware::operator=(const RemoteAddressAware &o) { - this->remote_address = o.getRemoteAddress(); - return *this; -} - -PortToBindAware::PortToBindAware(const PortToBindAware &o) { *this = o; } - -PortToBindAware &PortToBindAware::operator=(const PortToBindAware &o) { - this->port_to_bind = o.getPortToBind(); - this->must_be_free_port = o.shallBeFreePort(); - return *this; -} - RemoteAddressAware::RemoteAddressAware(const Address &address) : remote_address(address) { if (nullptr == getRemoteAddress()) { @@ -37,14 +21,4 @@ RemoteAddressAware::RemoteAddressAware(const Address &address) } } -RemoteAddressFamilyAware::RemoteAddressFamilyAware( - const RemoteAddressFamilyAware &o) { - *this = o; -} - -RemoteAddressFamilyAware & -RemoteAddressFamilyAware::operator=(const RemoteAddressFamilyAware &o) { - this->remote_address_family = o.getRemoteAddressFamily(); - return *this; -} } // namespace MinimalSocket diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index 0ae01de0..740e17df 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -12,11 +12,9 @@ #include "../Utils.h" namespace MinimalSocket::tcp { -TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { - Openable::transfer(*this, o); -} +TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { this->steal(o); } TcpClient &TcpClient::operator=(TcpClient &&o) { - Openable::transfer(*this, o); + this->steal(o); copy_as(*this, o); return *this; } @@ -25,7 +23,7 @@ TcpClient::TcpClient(const Address &server_address) : RemoteAddressAware(server_address) {} void TcpClient::open_() { - auto &socket = getIDWrapper(); + auto &socket = getHandler(); const auto remote_address = getRemoteAddress(); socket.reset(SocketType::TCP, remote_address.getFamily()); MinimalSocket::connect(socket.accessId(), remote_address); diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index 04fdd02c..cb22217e 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -15,22 +15,21 @@ namespace MinimalSocket::tcp { TcpServer::TcpServer(TcpServer &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { - Openable::transfer(*this, o); + this->steal(o); } TcpServer &TcpServer::operator=(TcpServer &&o) { - Openable::transfer(*this, o); + this->steal(o); copy_as(*this, o); copy_as(*this, o); return *this; } -TcpServer::TcpServer(const Port port_to_bind, - const AddressFamily &accepted_client_family) +TcpServer::TcpServer(Port port_to_bind, AddressFamily accepted_client_family) : PortToBindAware(port_to_bind), RemoteAddressFamilyAware(accepted_client_family) {} void TcpServer::open_() { - auto &socket = getIDWrapper(); + auto &socket = getHandler(); const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); socket.reset(SocketType::TCP, family); @@ -67,7 +66,7 @@ TcpServer::acceptNewClient(const Timeout &timeout) { // accept: wait for a client to call connect and hit this server and get a // pointer to this client. accepted_client_socket_id = - ::accept(getIDWrapper().accessId(), + ::accept(getHandler().accessId(), reinterpret_cast(&acceptedClientAddress[0]), &acceptedClientAddress_length); if (accepted_client_socket_id == SCK_INVALID_SOCKET) { @@ -81,7 +80,7 @@ TcpServer::acceptNewClient(const Timeout &timeout) { accept_client(); } else { try_within_timeout([&]() { accept_client(); }, - [this]() { this->resetIDWrapper(); }, timeout); + [this]() { this->resetHandler(); }, timeout); } } catch (const TimeOutError &) { TcpServer reopened = TcpServer{getPortToBind(), getRemoteAddressFamily()}; @@ -97,7 +96,7 @@ TcpServer::acceptNewClient(const Timeout &timeout) { std::optional result; auto &accepted = result.emplace(TcpConnection{accepted_client_parsed_address}); - accepted.getIDWrapper().reset(accepted_client_socket_id); + accepted.getHandler().reset(accepted_client_socket_id); return result; } @@ -105,11 +104,11 @@ TcpConnection::TcpConnection(const Address &remote_address) : RemoteAddressAware(remote_address) {} TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { - Socket::transfer(*this, o); + this->steal(o); } TcpConnection &TcpConnection::operator=(TcpConnection &&o) { copy_as(*this, o); - Socket::transfer(*this, o); + this->steal(o); return *this; } } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index e3512120..91ddfe78 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -12,26 +12,26 @@ #include "../Utils.h" namespace MinimalSocket::udp { -UdpBinded::UdpBinded(const Port port_to_bind, - const AddressFamily &accepted_connection_family) +UdpBinded::UdpBinded(Port port_to_bind, + AddressFamily accepted_connection_family) : PortToBindAware(port_to_bind), RemoteAddressFamilyAware(accepted_connection_family) {} UdpBinded::UdpBinded(UdpBinded &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { - Openable::transfer(*this, o); + this->steal(o); } UdpBinded &UdpBinded::operator=(UdpBinded &&o) { copy_as(*this, o); copy_as(*this, o); - Openable::transfer(*this, o); + this->steal(o); return *this; } void UdpBinded::open_() { - getIDWrapper().reset(SocketType::UDP, getRemoteAddressFamily()); + getHandler().reset(SocketType::UDP, getRemoteAddressFamily()); auto binded_port = - MinimalSocket::bind(getIDWrapper().accessId(), getRemoteAddressFamily(), + MinimalSocket::bind(getHandler().accessId(), getRemoteAddressFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); } @@ -42,9 +42,9 @@ UdpConnected UdpBinded::connect(const Address &remote_address) { } UdpConnected result(remote_address, getPortToBind()); if (wasOpened()) { - MinimalSocket::connect(getIDWrapper().accessId(), remote_address); + MinimalSocket::connect(getHandler().accessId(), remote_address); } - Openable::transfer(result, *this); + this->transfer(result); return std::move(result); } @@ -65,24 +65,24 @@ std::optional UdpBinded::connect(const Timeout &timeout, return connect(maybe_received->sender); } -UdpConnected::UdpConnected(const Address &remote_address, const Port &port) +UdpConnected::UdpConnected(const Address &remote_address, Port port) : PortToBindAware(port), RemoteAddressAware(remote_address) {} UdpConnected::UdpConnected(UdpConnected &&o) : PortToBindAware(o), RemoteAddressAware(o) { - Openable::transfer(*this, o); + this->steal(o); } UdpConnected &UdpConnected::operator=(UdpConnected &&o) { copy_as(*this, o); copy_as(*this, o); - Openable::transfer(*this, o); + this->steal(o); return *this; } void UdpConnected::open_() { - const auto &socket_id = getIDWrapper().accessId(); const auto &remote_address = getRemoteAddress(); - getIDWrapper().reset(SocketType::UDP, remote_address.getFamily()); + getHandler().reset(SocketType::UDP, remote_address.getFamily()); + auto socket_id = getHandler().accessId(); auto binded_port = MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); @@ -90,24 +90,24 @@ void UdpConnected::open_() { } UdpBinded UdpConnected::disconnect() { - resetIDWrapper(); + resetHandler(); UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); result.open(); return std::move(result); } -UdpConnected -makeUdpConnectedToUnknown(const Port &port, - const AddressFamily &accepted_connection_family, - std::string *initial_message) { +UdpConnected makeUdpConnectedToUnknown(Port port, + AddressFamily accepted_connection_family, + std::string *initial_message) { auto result = makeUdpConnectedToUnknown(port, accepted_connection_family, NULL_TIMEOUT, initial_message); return std::move(result.value()); } -std::optional makeUdpConnectedToUnknown( - const Port &port, const AddressFamily &accepted_connection_family, - const Timeout &timeout, std::string *initial_message) { +std::optional +makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, + const Timeout &timeout, + std::string *initial_message) { UdpBinded primal_socket(port, accepted_connection_family); auto success = primal_socket.open(); if (!success) { diff --git a/tests/SlicedOps.cpp b/tests/SlicedOps.cpp index 31232eb6..2ae73b1a 100644 --- a/tests/SlicedOps.cpp +++ b/tests/SlicedOps.cpp @@ -31,7 +31,7 @@ void sliced_send(Sender &subject, const std::string &to_send, while (buffer.remainingBytes() != 0) { std::size_t bytes_to_send = std::min(delta_send, buffer.remainingBytes()); - subject.send(ConstBuffer{buffer.data(), bytes_to_send}); + subject.send(BufferViewConst{buffer.data(), bytes_to_send}); buffer.shift(bytes_to_send); } } @@ -42,7 +42,8 @@ void sliced_send(SenderTo &subject, const std::string &to_send, while (buffer.remainingBytes() != 0) { std::size_t bytes_to_send = std::min(delta_send, buffer.remainingBytes()); - subject.sendTo(ConstBuffer{buffer.data(), bytes_to_send}, to_send_address); + subject.sendTo(BufferViewConst{buffer.data(), bytes_to_send}, + to_send_address); buffer.shift(bytes_to_send); } } @@ -54,7 +55,7 @@ std::string sliced_receive(Receiver &subject, const std::size_t to_receive, std::size_t bytes_to_receive = std::min(delta_receive, buffer.remainingBytes()); auto bytes_received = - subject.receive(Buffer{buffer.data(), bytes_to_receive}); + subject.receive(BufferView{buffer.data(), bytes_to_receive}); buffer.shift(bytes_received); } return buffer.asString(); @@ -68,7 +69,7 @@ std::string sliced_receive(ReceiverUnkownSender &subject, std::size_t bytes_to_receive = std::min(delta_receive, buffer.remainingBytes()); auto maybe_bytes_received = - subject.receive(Buffer{buffer.data(), bytes_to_receive}); + subject.receive(BufferView{buffer.data(), bytes_to_receive}); if (maybe_bytes_received) { buffer.shift(maybe_bytes_received->received_bytes); } From 00237dbbd248f564e6611995d10fd85950375615 Mon Sep 17 00:00:00 2001 From: Foo Date: Sun, 25 Feb 2024 01:26:21 +0100 Subject: [PATCH 6/7] non blocking semantics for receiving soeckts and the tcp acceptor tests updated documentation updated and improved --- README.md | 127 +++++++--- samples/README.cpp | 76 +++++- samples/tcp/CMakeLists.txt | 4 + samples/tcp/README.md | 32 +++ .../tcp/Sample04_server_nn_block_2_clients | 3 + samples/tcp/TcpClient.cpp | 14 +- samples/tcp/TcpRepeater.cpp | 19 +- samples/tcp/TcpServer.cpp | 15 +- samples/tcp/TcpServerNonBlocking.cpp | 94 ++++++++ samples/udp/CMakeLists.txt | 4 + samples/udp/README.md | 25 ++ .../Sample04_2_askers_2_nn_block_responders | 3 + samples/udp/UdpAsker.cpp | 16 +- samples/udp/UdpResponder.cpp | 11 +- samples/udp/UdpResponderNonBlocking.cpp | 71 ++++++ samples/udp/UdpScriptsGenerator.cpp | 79 ------ samples/utils/Args.cpp | 16 -- samples/utils/Args.h | 79 ++++-- samples/utils/Ask.h | 42 +++- samples/utils/Names.cpp | 10 +- samples/utils/Names.h | 20 +- samples/utils/Pollables.cpp | 34 +++ samples/utils/Pollables.h | 29 +++ samples/utils/Respond.h | 43 ++-- src/header/MinimalSocket/Error.h | 6 +- src/header/MinimalSocket/core/Address.h | 26 +- src/header/MinimalSocket/core/Definitions.h | 2 +- src/header/MinimalSocket/core/Receiver.h | 187 ++++++++++----- src/header/MinimalSocket/core/Sender.h | 63 +++-- src/header/MinimalSocket/core/Socket.h | 103 +++++--- src/header/MinimalSocket/core/SocketContext.h | 16 +- src/header/MinimalSocket/tcp/TcpClient.h | 51 ++-- src/header/MinimalSocket/tcp/TcpServer.h | 184 ++++++++++---- src/header/MinimalSocket/udp/UdpSocket.h | 213 +++++++++++------ src/src/SocketFunctions.cpp | 46 +++- src/src/SocketFunctions.h | 6 + src/src/SocketHandler.cpp | 5 + src/src/core/Address.cpp | 20 +- src/src/core/Receiver.cpp | 200 ++++++++++------ src/src/core/Sender.cpp | 16 +- src/src/core/Socket.cpp | 64 +++-- src/src/core/SocketContext.cpp | 10 +- src/src/tcp/TcpClient.cpp | 21 +- src/src/tcp/TcpServer.cpp | 156 +++++++----- src/src/udp/UdpSocket.cpp | 98 +++++--- tests/ConnectionsUtils.cpp | 60 ++++- tests/ConnectionsUtils.h | 47 ++-- tests/ParallelSection.cpp | 14 +- tests/PortFactory.cpp | 10 +- tests/PortFactory.h | 10 +- tests/RollingView.cpp | 56 +++++ tests/RollingView.h | 51 ++++ tests/SlicedOps.cpp | 79 ------ tests/SlicedOps.h | 39 --- tests/TestAddress.cpp | 18 +- tests/TestOpenTimeout.cpp | 2 +- tests/TestRobustness.cpp | 107 +++++---- tests/TestTCP.cpp | 224 +++++++++--------- tests/TestUDP.cpp | 193 +++++++-------- 59 files changed, 2061 insertions(+), 1208 deletions(-) create mode 100644 samples/tcp/Sample04_server_nn_block_2_clients create mode 100644 samples/tcp/TcpServerNonBlocking.cpp create mode 100644 samples/udp/Sample04_2_askers_2_nn_block_responders create mode 100644 samples/udp/UdpResponderNonBlocking.cpp delete mode 100644 samples/udp/UdpScriptsGenerator.cpp create mode 100644 samples/utils/Pollables.cpp create mode 100644 samples/utils/Pollables.h create mode 100644 tests/RollingView.cpp create mode 100644 tests/RollingView.h delete mode 100644 tests/SlicedOps.cpp delete mode 100644 tests/SlicedOps.h diff --git a/README.md b/README.md index b07cad53..344ce7c3 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ ## INTRO **MinimalSocket** gives you a modern **C++** library to set up and create **tcp** and **udp** socket connections, in a -completely platform agnostic way. The supported platforms are: **Windows**, any **Linux** distro and **MacOS**. +completely platform agnostic way. The supported systems are: **Windows**, any **Linux** distro and **MacOS**. -Check [Features](#features) to see details about the various features of **MinimalSocket**. Read [Usage](#usage) and [Samples](#samples) to see how easy is to use **MinimalSocket**. +The [Features](#features) Section details the various features of **MinimalSocket**. Read [Usage](#usage) and [Samples](#samples) to see how easy is to use **MinimalSocket**. This is a **CMake** project, check [CMake support](#cmake-support) to see how this library can be integrated. @@ -23,18 +23,22 @@ Remember to leave a **star** in case you have found this library useful. Haven't left a **star** already? Do it now ;)! -**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both low level buffer of chars or high level string. Indeed, this is actually the only capability you need for a socket, as more complex messages can be serialized to a string or internalized from a string using, among the others, approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). +**MinimalSocket** allows you to build and set up **tcp** and **udp** connections. Messages can be sent and received in terms of both buffer of bytes or strings. Indeed, this is actually the only capability you need for a socket, as more complex messages can be serialized into or internalized from a buffer of bytes using, among the others, approaches like [Google Protocol Buffers](https://developers.google.com/protocol-buffers/docs/cpptutorial) or [NanoPb](https://jpa.kapsi.fi/nanopb/). -This are the most notable characteristics of **MinimalSocket**: -- A modern **C++** object oriented API allowing you to set up and build socket connections. Typically, socket handlers are represented by the classes part of this library. Any time an object is created, the related socket is closed in order to defer the opening at the convenient moment. This allows you to decouple the moments when sockets are created from those where they are actually connected. Any connection is automatically closed when the handler object is destroyed (and all relevant information cleaned up after destroying the wrapping object). -- Prevent you from handling low level socket programming, abstracting from the particular platform hosting your application(s): let **MinimalSocket** do all the work for you. Morevoer, all the platform specific modules, functions, linkages are not exposed. +These are the most notable characteristics of **MinimalSocket**: +- A modern **C++** object oriented API allowing you to set up and build socket connections. Typically, sockets are represented by the classes part of this library. Any time an object is created, the related socket is generated in a closed state in order to defer the opening at the convenient moment. This allows you to decouple the moments when sockets are created from those when the socket should be actually started and used. At the same time, any connection is automatically closed when the handler object is destroyed (and all relevant information cleaned up). +- Prevent you from handling low level socket programming, abstracting from the particular platform hosting your application(s): let **MinimalSocket** do all the work for you. Morevoer, all the system specific modules, functions, linkages (ex. winsock in **Windows**) are not exposed. - **AF_INET** (**ip v4**) and **AF_INET6** (**ip v6**) addresses, refer to [this](https://www.ibm.com/docs/en/i/7.1?topic=characteristics-socket-address-family) link, are both supported -- Many sockets operations are by default blocking. However, **MinimalSocket** allows you also to use specify **timeout**(s) to use, after which the operation terminates in any case giving the control back to the caller. In particular, the operations allowing for such possibility are: - - receive (send are always intrinsically non blocking) - - acceptance of a new client from the tcp server side -- **MinimalSocket** is tested to be **thread safe**. However, notice that you can send while receiving for a certain socket, but from different threads. This allows you to easily create your own asynchronous sockets, building on top of the classes offered by this library. -- **Udp** sockets can be used both as un-connected or connected, check [here](./samples/udp/README.md) for further details. Moreover, the same **udp** socket can be connected or disconnected during its lifetime. -- Under **Windows** systems, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any functionalities. From the outside, you can specify the Windows Sockets version if you need. +- For any socket type, **MinimalSocket** allows you to choose between a blocking and a non blocking version (see also the table at the end of this Section as well as the [Usage](#usage) Section). In essence, non blocking sockets functions return always instantaneously, with some kind of result when succeeding or an empty result when failing. On the contrary, blocking sockets absorb the caller till the function can be actually completed. At the same time, it is also possible to specify some timeout for blocking socket after which the completion of the function is considered failed. +- **MinimalSocket** is tested to be **thread safe**. However, notice that for a ceratin socket you can still send while receiving from different threads. This allows you to easily create your own asynchronous sockets, building on top of the classes offered by this library. +- Under **Windows**, [**WSAStartup**](https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup) is automatically called before using any relevant functionalities. If you need, it is also possible to specify the Windows Sockets to use. + +This table summarizes the differences between the blocking and the non blocking behaviours of the socket that can be created using **MinimalSocket**: +| | Blocking Behaviour, caller is blocked till completion or timeout is reached (if one was specified) | Non Blocking Behaviour, functions return immediately | +| --- | --- | --- | +| accepting of a new client (tcp only) | caller thread is absorbed till a new client actually asks to connect or timeout is reached (if any was specified) | if a connection request from a client was already queued before calling the accept function, a new connection handler is returned, otherwise a nullopt is returned. | +| receive a new message (tcp and udp) | caller thread is absorbed till a new a message is sent to the socket or timeout is reached (if any was specified) | if a message was sent and already queued in the socket buffer before calling the receive function, that message is returned, otherwise an empty message is returned. | +| send a new message (tcp and udp) | In case the buffer of the socket is not full and can entirely host the message to send, the message is actaully written in the buffer and the function returns almost instantaneously. On the contrary case, caller thread is absorbed until space is done in the buffer (as messages are consumed from the other side) and after that the message is actually written and function completes. | If there is enough space in the buffer of the socket, the message is written and the function returns. In the contrary case, the function returns immediately without actually send the message (the send can be retried later) | ## USAGE @@ -44,13 +48,13 @@ Haven't left a **star** already? Do it now ;)! #### SERVER -To create a **tcp** server you just need to build a **tcp::TcpServer** object: +To create a classic blocking **tcp** server you just need to build a **tcp::TcpServer** object: ```cpp #include MinimalSocket::Port port = 15768; // the port to bind -MinimalSocket::tcp::TcpServer tcp_server(port, - MinimalSocket::AddressFamily::IP_V4); +MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); ``` open it: @@ -62,12 +66,12 @@ bool success = tcp_server.open(); and now you are ready to accept new clients: ```cpp // accepts the next client that will ask the connection -MinimalSocket::tcp::TcpConnection accepted_connection = +MinimalSocket::tcp::TcpConnectionBlocking accepted_connection = tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection ``` -you can now receive and send information with the accepted client by simply doing this: +you can now exhange messages with the accepted client by simply doing this: ```cpp // receive a message std::size_t message_max_size = 1000; @@ -78,6 +82,30 @@ std::string accepted_connection.send("a message to send"); ``` +If you instead need a non blocking server, you can create it in a similar way: +```cpp +MinimalSocket::Port port = 15768; // the port to bind +MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); +tcp_server.open(); +``` + +This server version will be non blocking, meaning that the accept function will return immediately: +```cpp +// check if a client asked for the connection. If no, the function immediately +// returns a nullopt. On the contrary, the returned optional contains the +// handler to the connected client +std::optional + maybe_accepted_connection = tcp_server.acceptNewClient(); +``` + +Notice that even though the server per se is non blocking, the eventually accepted client handler is blocking. +You can turn it to a non blocking socket too, by transferring the socket to a non blocking handler: +```cpp +MinimalSocket::tcp::TcpConnectionNonBlocking accepted_connection_nn_block = + maybe_accepted_connection->turnToNonBlocking(); +``` + #### CLIENT To create a **tcp** client you just need to build a **tcp::TcpClient** object: @@ -86,16 +114,16 @@ To create a **tcp** client you just need to build a **tcp::TcpClient** object: MinimalSocket::Port server_port = 15768; std::string server_address = "192.168.125.85"; -MinimalSocket::tcp::TcpClient tcp_client( +MinimalSocket::tcp::TcpClient tcp_client( MinimalSocket::Address{server_address, server_port}); ``` open it: ```cpp - // Open the server. Here, the client will ask the connection to specified - // server. After that, the client will be actually connected. - bool success = - tcp_client.open(); // blocking till the connection is actually established +// Open the server. Here, the client will ask the connection to specified +// server. After that, the client will be actually connected. +bool success = + tcp_client.open(); // blocking till the connection is actually established ``` you can now receive and send information with the remote server by simply doing this: @@ -109,14 +137,28 @@ std::string = tcp_client.receive(message_max_size); ``` +If you instead need a non blocking client you can create it ans use it in a similar way: +```cpp +MinimalSocket::Port server_port = 15768; +std::string server_address = "192.168.125.85"; +MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::Address{server_address, server_port}); +tcp_client.open(); + +std::size_t message_max_size = 1000; +// non blocking receive: returns immediately with an empty message in case no +// new data were available, or with a non empty message in the contrary case +std::string received_message = tcp_client.receive(message_max_size); +``` + ### UDP -To create a normal **udp** socket you just need to build a **udp::UdpBinded** object: +To create a normal **udp** socket you just need to build a **udp::Udp** object: ```cpp #include MinimalSocket::Port this_socket_port = 15768; -MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, +MinimalSocket::udp::Udp udp_socket(this_socket_port, MinimalSocket::AddressFamily::IP_V6); ``` @@ -126,7 +168,7 @@ open it: bool success = udp_socket.open(); ``` -you can now receive and send information with any other opened **udp** socket: +you can now receive and send information with other **udp** sockets: ```cpp // send a message to another udp MinimalSocket::Address other_recipient_udp = @@ -143,15 +185,17 @@ std::string received_message_content // resized to the nunber of bytes = received_message->received_message; ``` -you can also decide to connect an opened **udp** socket to a specific address. This simply means that messages incoming from other peers will be filtered out, as **udp** sockets are not connection oriented: +you can also decide to "connect" an opened **udp** socket to a specific address. Beware that this simply means that messages incoming from other peers will be filtered out, as **udp** sockets are not connection oriented: ```cpp MinimalSocket::Address permanent_sender_udp = MinimalSocket::Address{"192.168.125.85", 15768}; -MinimalSocket::udp::UdpConnected udp_connected_socket = udp_socket.connect( - permanent_sender_udp); // ownership of the underlying socket is transfered - // from udp_socket to udp_connected_socket, meaning - // that you can't use anymore udp_socket (unless - // you re-open it) +MinimalSocket::udp::UdpConnected udp_connected_socket = + udp_socket.connect( + permanent_sender_udp); // ownership of the underlying socket is + // transfered from udp_socket to + // udp_connected_socket, meaning that you can't + // use anymore udp_socket (unless you re-open + // it) ``` Now you can send and receive data without having to specify the recpient/sender: @@ -165,11 +209,30 @@ std::string udp_connected_socket.send("a message to send"); ``` +You can also create and use non blocking **udp** sockets: +```cpp +MinimalSocket::Port this_socket_port = 15768; +MinimalSocket::udp::Udp udp_socket( + this_socket_port, MinimalSocket::AddressFamily::IP_V6); +udp_socket.open(); + +std::size_t message_max_size = 1000; +// non blocking receive: returns immediately with an empty message in case no +// new data were available, or with a non empty message in the contrary case +// +// struct ReceiveStringResult { +// Address sender; +// std::string received_message; +// }; +std::optional received_message = + udp_socket.receive(message_max_size); +``` + ## SAMPLES Haven't left a **star** already? Do it now ;)! -Instructions about **tcp** samples can be found [here](./samples/tcp/README.md), while **udp** samples are [here](./samples/udp/README.md) discussed. +Examples of usage about **tcp** sockets can be found [here](./samples/tcp/README.md), while **udp** samples are [here](./samples/udp/README.md) discussed. ATTENTION!!! The Samples execution might be blocked the first time by your firewall: set up properly your firewall or run the samples with the [administrator privileges](https://www.techopedia.com/definition/4961/administrative-privileges#:~:text=Administrative%20privileges%20are%20the%20ability,as%20a%20database%20management%20system.) diff --git a/samples/README.cpp b/samples/README.cpp index 16288cc3..b2b3d0a3 100644 --- a/samples/README.cpp +++ b/samples/README.cpp @@ -2,15 +2,15 @@ #include int main() { MinimalSocket::Port port = 15768; // the port to bind - MinimalSocket::tcp::TcpServer tcp_server(port, - MinimalSocket::AddressFamily::IP_V4); + MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); // Open the server. This will bind the port and the server will start to // listen for connection requests. bool success = tcp_server.open(); // accepts the next client that will ask the connection - MinimalSocket::tcp::TcpConnection accepted_connection = + MinimalSocket::tcp::TcpConnectionBlocking accepted_connection = tcp_server.acceptNewClient(); // blocking till a client actually asks the // connection @@ -28,7 +28,7 @@ int main() { int main() { MinimalSocket::Port server_port = 15768; std::string server_address = "192.168.125.85"; - MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::tcp::TcpClient tcp_client( MinimalSocket::Address{server_address, server_port}); // Open the server. Here, the client will ask the connection to specified @@ -49,7 +49,7 @@ int main() { #include int main() { MinimalSocket::Port this_socket_port = 15768; - MinimalSocket::udp::UdpBinded udp_socket(this_socket_port, + MinimalSocket::udp::Udp udp_socket(this_socket_port, MinimalSocket::AddressFamily::IP_V6); // Open the server. This will bind the specified port. @@ -71,11 +71,13 @@ int main() { MinimalSocket::Address permanent_sender_udp = MinimalSocket::Address{"192.168.125.85", 15768}; - MinimalSocket::udp::UdpConnected udp_connected_socket = udp_socket.connect( - permanent_sender_udp); // ownership of the underlying socket is transfered - // from udp_socket to udp_connected_socket, meaning - // that you can't use anymore udp_socket (unless - // you re-open it) + MinimalSocket::udp::UdpConnected udp_connected_socket = + udp_socket.connect( + permanent_sender_udp); // ownership of the underlying socket is + // transfered from udp_socket to + // udp_connected_socket, meaning that you can't + // use anymore udp_socket (unless you re-open + // it) // receive a message std::size_t message_max_size = 1000; @@ -85,3 +87,57 @@ int main() { // send a message udp_connected_socket.send("a message to send"); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// tcp server, non blocking +int main() { + MinimalSocket::Port port = 15768; // the port to bind + MinimalSocket::tcp::TcpServer tcp_server( + port, MinimalSocket::AddressFamily::IP_V4); + tcp_server.open(); + + // check if a client asked for the connection. If no, the function immediately + // returns a nullopt. On the contrary, the returned optional contains the + // handler to the connected client + std::optional + maybe_accepted_connection = tcp_server.acceptNewClient(); + + MinimalSocket::tcp::TcpConnectionNonBlocking accepted_connection_nn_block = + maybe_accepted_connection->turnToNonBlocking(); +} + +// tcp client, non blocking +int main() { + MinimalSocket::Port server_port = 15768; + std::string server_address = "192.168.125.85"; + MinimalSocket::tcp::TcpClient tcp_client( + MinimalSocket::Address{server_address, server_port}); + tcp_client.open(); + + std::size_t message_max_size = 1000; + // non blocking receive: returns immediately with an empty message in case no + // new data were available, or with a non empty message in the contrary case + std::string received_message = tcp_client.receive(message_max_size); +} + +// udp socket, non blocking +int main() { + MinimalSocket::Port this_socket_port = 15768; + MinimalSocket::udp::Udp udp_socket( + this_socket_port, MinimalSocket::AddressFamily::IP_V6); + udp_socket.open(); + + std::size_t message_max_size = 1000; + // non blocking receive: returns immediately with an empty message in case no + // new data were available, or with a non empty message in the contrary case + // + // struct ReceiveStringResult { + // Address sender; + // std::string received_message; + // }; + std::optional received_message = + udp_socket.receive(message_max_size); +} diff --git a/samples/tcp/CMakeLists.txt b/samples/tcp/CMakeLists.txt index f62c6531..4f5bb5b1 100644 --- a/samples/tcp/CMakeLists.txt +++ b/samples/tcp/CMakeLists.txt @@ -1,5 +1,6 @@ MakeApp(TcpClient) MakeApp(TcpServer) +MakeApp(TcpServerNonBlocking) MakeApp(TcpRepeater) MakeSample(Sample01_server_client Tcp) @@ -10,3 +11,6 @@ add_dependencies(TcpSample02_server_2_clients TcpClient TcpServer) MakeSample(Sample03_chain_with_2_repeaters Tcp) add_dependencies(TcpSample03_chain_with_2_repeaters TcpClient TcpServer TcpRepeater) + +MakeSample(Sample04_server_nn_block_2_clients Tcp) +add_dependencies(TcpSample04_server_nn_block_2_clients TcpClient TcpServerNonBlocking) diff --git a/samples/tcp/README.md b/samples/tcp/README.md index fdb62112..c49e35d4 100644 --- a/samples/tcp/README.md +++ b/samples/tcp/README.md @@ -78,4 +78,36 @@ The above classes of samples can be described as follows: TcpRepeater2->>TcpClient: forawrd response 1 ``` +- **TcpSample04_server_nn_block_2_clients** is an example of non blocking tcp server. The application uses one single thread to spin multiple connections. More in detail: + - related config file is [Sample04_server_nn_block_2_clients](./Sample04_server_nn_block_2_clients) + - runs **TcpServerNonBlocking**, creating a tcp server that binds and listen to a specified port + - runs **TcpClient**, creating a first tcp client that connections to the previous server, exchanging messages with it. + - runs **TcpClient**, creating a second tcp client that connections to the previous server, exchanging messages with it with a different frequency. + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + TcpServer->>TcpServer: bind a port + TcpClient1->>TcpServer: ask for connection + TcpServer->>TcpClient1: connection done + TcpClient2->>TcpServer: ask for connection + TcpServer->>TcpClient2: connection done + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpClient1->>TcpServer: request 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpServer->>TcpClient1: response 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpClient2->>TcpServer: request 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpServer->>TcpClient2: response 1 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpClient1->>TcpServer: request 2 + TcpServer->>TcpServer: has something arrived from client 1? if so send response + TcpServer->>TcpServer: has something arrived from client 2? if so send response + TcpServer->>TcpClient1: response 2 + **TcpServer** and **TcpClient** can be also used as stand alone processes, in order to check connections locally or on a different host. diff --git a/samples/tcp/Sample04_server_nn_block_2_clients b/samples/tcp/Sample04_server_nn_block_2_clients new file mode 100644 index 00000000..35cd99ad --- /dev/null +++ b/samples/tcp/Sample04_server_nn_block_2_clients @@ -0,0 +1,3 @@ +TcpServerNonBlocking --port 35998 --clients 2 +TcpClient --port 35998 +TcpClient --port 35998 --rate 400 \ No newline at end of file diff --git a/samples/tcp/TcpClient.cpp b/samples/tcp/TcpClient.cpp index f9f5bde6..777fd77e 100644 --- a/samples/tcp/TcpClient.cpp +++ b/samples/tcp/TcpClient.cpp @@ -22,14 +22,13 @@ int main(const int argc, const char **argv) { cout << "----------------------- Client -----------------------" << endl; PARSE_ARGS - const auto server_host = options->getValue("host", "127.0.0.1"); - const auto server_port = - static_cast(options->getIntValue("port")); - const auto rate = - std::chrono::milliseconds{options->getIntValue<250>("rate")}; + const auto server_host = options->getValue("host", "127.0.0.1"); + const auto server_port = options->getValue("port"); + const auto rate = options->getValue( + "rate", std::chrono::milliseconds{250}); const MinimalSocket::Address server_address(server_host, server_port); - MinimalSocket::tcp::TcpClient client(server_address); + MinimalSocket::tcp::TcpClient client(server_address); cout << "Connecting to " << MinimalSocket::to_string(server_address) << endl; if (!client.open()) { @@ -38,7 +37,8 @@ int main(const int argc, const char **argv) { } cout << "Connected" << endl; - MinimalSocket::samples::ask(client, rate, options->getIntValue<5>("cycles")); + MinimalSocket::samples::ask(client, rate, + options->getValue("cycles", 5)); // the connection will be close when destroying the client object return EXIT_SUCCESS; diff --git a/samples/tcp/TcpRepeater.cpp b/samples/tcp/TcpRepeater.cpp index 912ea8e4..85a99e9f 100644 --- a/samples/tcp/TcpRepeater.cpp +++ b/samples/tcp/TcpRepeater.cpp @@ -21,8 +21,8 @@ #include using namespace std; -void repeat(MinimalSocket::tcp::TcpConnection &preceding, - MinimalSocket::tcp::TcpClient &following) { +void repeat(MinimalSocket::tcp::TcpConnectionBlocking &preceding, + MinimalSocket::tcp::TcpClient &following) { while (true) { auto request = preceding.receive(500, std::chrono::seconds{5}); if (request.empty()) { @@ -45,17 +45,17 @@ int main(const int argc, const char **argv) { cout << "----------------------- Repeater -----------------------" << endl; PARSE_ARGS - const auto following_host = options->getValue("host", "127.0.0.1"); + const auto following_host = + options->getValue("host", "127.0.0.1"); const auto following_port = - static_cast(options->getIntValue("next_port")); + options->getValue("next_port"); MinimalSocket::Address following_address(following_host, following_port); - const auto port_to_reserve = - static_cast(options->getIntValue("port")); + const auto port_to_reserve = options->getValue("port"); // reserve port - MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, - following_address.getFamily()); + MinimalSocket::tcp::TcpServer acceptor(port_to_reserve, + following_address.getFamily()); if (!acceptor.open()) { cerr << "Failed to bind and listen to specified port" << endl; return EXIT_FAILURE; @@ -63,7 +63,8 @@ int main(const int argc, const char **argv) { cout << "Listening on port " << port_to_reserve << endl; // ask connection to follower - MinimalSocket::tcp::TcpClient connection_to_following(following_address); + MinimalSocket::tcp::TcpClient connection_to_following( + following_address); cout << "Connecting to next on chain at " << MinimalSocket::to_string(following_address) << endl; if (!connection_to_following.open()) { diff --git a/samples/tcp/TcpServer.cpp b/samples/tcp/TcpServer.cpp index 89a11133..3128360f 100644 --- a/samples/tcp/TcpServer.cpp +++ b/samples/tcp/TcpServer.cpp @@ -20,8 +20,8 @@ #include using namespace std; -std::thread accept_new_client(MinimalSocket::tcp::TcpServer &server) { - MinimalSocket::tcp::TcpConnection accepted_connection = +std::thread accept_new_client(MinimalSocket::tcp::TcpServer &server) { + MinimalSocket::tcp::TcpConnectionBlocking accepted_connection = server.acceptNewClient(); cout << "New client accepted" << endl; return std::thread([connection = std::move(accepted_connection)]() mutable { @@ -33,13 +33,12 @@ int main(const int argc, const char **argv) { cout << "----------------------- Server -----------------------" << endl; PARSE_ARGS - const auto server_port = - static_cast(options->getIntValue("port")); - const auto max_clients = options->getIntValue("clients"); - const auto family = - MinimalSocket::samples::to_family(options->getValue("family", "v4")); + const auto server_port = options->getValue("port"); + const auto max_clients = options->getValue("clients", 0); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); - MinimalSocket::tcp::TcpServer server(server_port, family); + MinimalSocket::tcp::TcpServer server(server_port, family); if (!server.open()) { cerr << "Failed to bind and listen to specified port" << endl; diff --git a/samples/tcp/TcpServerNonBlocking.cpp b/samples/tcp/TcpServerNonBlocking.cpp new file mode 100644 index 00000000..6229866e --- /dev/null +++ b/samples/tcp/TcpServerNonBlocking.cpp @@ -0,0 +1,94 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + +// elements from the MinimalSocket library +#include + +// just a bunch of utilities +#include +#include +#include +#include + +#include +#include +using namespace std; + +int main(const int argc, const char **argv) { + cout << "----------------------- Server -----------------------" << endl; + PARSE_ARGS + + const auto server_port = options->getValue("port"); + const auto max_clients = options->getValue("clients", 0); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); + + MinimalSocket::tcp::TcpServer server(server_port, family); + + if (!server.open()) { + cerr << "Failed to bind and listen to specified port" << endl; + return EXIT_FAILURE; + } + cout << "Listening for new clients on port " << server_port << endl; + + std::size_t connected = 0; + std::list connections; + MinimalSocket::samples::Pollables pollables; + + auto create_pollable_connection = + [&](MinimalSocket::tcp::TcpConnectionNonBlocking &&connection) { + auto &conn = connections.emplace_back( + std::forward( + connection)); + return [conn = &conn]() { + // poll the connection by doing a non blocking receive + try { + auto request = conn->receive(500); + if (request.empty()) { + return MinimalSocket::samples::PollableStatus::NOT_ADVANCED; + } + const auto &response = + MinimalSocket::samples::NamesCircularIterator::NAMES_SURNAMES + .find(request) + ->second; + cout << MinimalSocket::samples::TimeOfDay{} + << " received: " << request << " ; sending: " << response + << endl; + conn->send(response); + } catch (const MinimalSocket::SocketError &) { + // if here the connection was closed + return MinimalSocket::samples::PollableStatus::COMPLETED; + } + return MinimalSocket::samples::PollableStatus::ADVANCED; + }; + }; + + pollables.emplace([&]() { + // poll the acceptor by trying to accept a new client + auto maybe_new_connection = server.acceptNewNonBlockingClient(); + if (maybe_new_connection.has_value()) { + cout << MinimalSocket::samples::TimeOfDay{} + << " connected a new client from " + << MinimalSocket::to_string(maybe_new_connection->getRemoteAddress()) + << endl; + pollables.emplace( + create_pollable_connection(std::move(maybe_new_connection.value()))); + return (max_clients != 0 && ++connected == max_clients) + ? MinimalSocket::samples::PollableStatus::COMPLETED + : MinimalSocket::samples::PollableStatus::ADVANCED; + } + return MinimalSocket::samples::PollableStatus::NOT_ADVANCED; + }); + + pollables.loop(std::chrono::seconds{5}); + + return EXIT_SUCCESS; +} diff --git a/samples/udp/CMakeLists.txt b/samples/udp/CMakeLists.txt index 0b3086f9..83e6de20 100644 --- a/samples/udp/CMakeLists.txt +++ b/samples/udp/CMakeLists.txt @@ -1,5 +1,6 @@ MakeApp(UdpAsker) MakeApp(UdpResponder) +MakeApp(UdpResponderNonBlocking) MakeSample(Sample01_asker_responder Udp) add_dependencies(UdpSample01_asker_responder UdpAsker UdpResponder) @@ -9,3 +10,6 @@ add_dependencies(UdpSample02_asker_connected_responer UdpAsker UdpResponder) MakeSample(Sample03_2_askers_responder Udp) add_dependencies(UdpSample03_2_askers_responder UdpAsker UdpResponder) + +MakeSample(Sample04_2_askers_2_nn_block_responders Udp) +add_dependencies(UdpSample04_2_askers_2_nn_block_responders UdpAsker UdpResponderNonBlocking) diff --git a/samples/udp/README.md b/samples/udp/README.md index 111b8777..637f38f7 100644 --- a/samples/udp/README.md +++ b/samples/udp/README.md @@ -65,5 +65,30 @@ The above classes of samples can be described as follows: UdpResponder->>UdpAsker1: response 2 ``` +- **UdpSample04_2_askers_2_nn_block_responders**: is an example of non blocking udp. The application uses one single thread to spin multiple connections. More in detail: + - related config file is [Sample04_2_askers_2_nn_block_responders](./Sample04_2_askers_2_nn_block_responders) + - runs **UdpResponderNonBlocking**, creating two non blocking udp sockets that binds two distinct specified ports. Then, a single thread is used to spin such sockets, checking, at each iteration and one socket at a time, if something was received and eventually respond + - runs **UdpAsker**, creating a udp socket that binds another port and exchanges messages with one of the two udp spawned in **UdpResponderNonBlocking** + - runs another **UdpAsker**, creating a udp socket that binds another port and exchanges messages with the other udp spawned in **UdpResponderNonBlocking** + - the following sequence diagram summarizes this sample + ```mermaid + sequenceDiagram + UdpResponder->>UdpResponder: bind port_A + UdpResponder->>UdpResponder: bind port_B + UdpAsker->>UdpAsker: bind a port + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpAsker->>UdpResponder: request 1 + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpResponder->>UdpAsker: response 1 + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpAsker->>UdpResponder: request 2 + UdpResponder->>UdpResponder: was a message delivered to the first udp? if so send response + UdpResponder->>UdpResponder: was a message delivered to the second udp? if so send response + UdpResponder->>UdpAsker: response 2 + ``` + **UdpAsker** and **UdpResponder** can be also used as stand alone processes, in order to check connections on local processes or the ones stored in a different host. Check the sources (or the scripts generated by **UdpScriptsGenerator**) for the syntax of the accepted arguments. diff --git a/samples/udp/Sample04_2_askers_2_nn_block_responders b/samples/udp/Sample04_2_askers_2_nn_block_responders new file mode 100644 index 00000000..9beff1d6 --- /dev/null +++ b/samples/udp/Sample04_2_askers_2_nn_block_responders @@ -0,0 +1,3 @@ +UdpResponderNonBlocking --port_A 36995 --port_B 36996 +UdpAsker --port 36995 --port_this 37005 +UdpAsker --port 36996 --port_this 37015 \ No newline at end of file diff --git a/samples/udp/UdpAsker.cpp b/samples/udp/UdpAsker.cpp index 7c79a2e0..db3e5dc6 100644 --- a/samples/udp/UdpAsker.cpp +++ b/samples/udp/UdpAsker.cpp @@ -22,16 +22,14 @@ int main(const int argc, const char **argv) { cout << "----------------------- Udp asker -----------------------" << endl; PARSE_ARGS - const auto remote_host = options->getValue("host", "127.0.0.1"); - const auto remote_port = - static_cast(options->getIntValue("port")); - const auto port_this = - static_cast(options->getIntValue("port_this")); - const auto rate = - std::chrono::milliseconds{options->getIntValue<250>("rate")}; + const auto remote_host = options->getValue("host", "127.0.0.1"); + const auto remote_port = options->getValue("port"); + const auto port_this = options->getValue("port_this"); + const auto rate = options->getValue( + "rate", std::chrono::milliseconds{250}); const MinimalSocket::Address remote_address(remote_host, remote_port); - MinimalSocket::udp::UdpBinded asker(port_this, remote_address.getFamily()); + MinimalSocket::udp::Udp asker(port_this, remote_address.getFamily()); std::this_thread::sleep_for( std::chrono::seconds{1}); // just to be sure the responder has already @@ -43,7 +41,7 @@ int main(const int argc, const char **argv) { cout << "Port successfully reserved" << endl; MinimalSocket::samples::ask(asker, remote_address, rate, - options->getIntValue<5>("cycles")); + options->getValue("cycles", 5)); return EXIT_SUCCESS; } diff --git a/samples/udp/UdpResponder.cpp b/samples/udp/UdpResponder.cpp index 926db3e6..01aceae0 100644 --- a/samples/udp/UdpResponder.cpp +++ b/samples/udp/UdpResponder.cpp @@ -22,13 +22,12 @@ int main(const int argc, const char **argv) { << endl; PARSE_ARGS - const auto port_this = - static_cast(options->getIntValue("port_this")); - const auto family = - MinimalSocket::samples::to_family(options->getValue("family", "v4")); - const bool connect = options->getValue("connect", "no") == "yes"; + const auto port_this = options->getValue("port_this"); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); + const bool connect = options->getValue("connect", false); - MinimalSocket::udp::UdpBinded responder(port_this, family); + MinimalSocket::udp::Udp responder(port_this, family); if (!responder.open()) { cerr << "Failed to reserve specified port" << endl; diff --git a/samples/udp/UdpResponderNonBlocking.cpp b/samples/udp/UdpResponderNonBlocking.cpp new file mode 100644 index 00000000..58a23903 --- /dev/null +++ b/samples/udp/UdpResponderNonBlocking.cpp @@ -0,0 +1,71 @@ +/** + * Author: Andrea Casalino + * Created: 16.05.2019 + * + * report any bug to andrecasa91@gmail.com. + **/ + +/////////////////////////////////////////////////////////////////////////// +// Have a look to README.md // +/////////////////////////////////////////////////////////////////////////// + +// elements from the MinimalSocket library +#include + +// just a bunch of utilities +#include +#include +#include +#include +using namespace std; + +int main(const int argc, const char **argv) { + cout << "----------------------- Udp responder -----------------------" + << endl; + PARSE_ARGS + + const auto port_A = options->getValue("port_A"); + const auto port_B = options->getValue("port_B"); + const auto family = options->getValue( + "family", MinimalSocket::AddressFamily::IP_V4); + + vector> responders; + responders.emplace_back(port_A, family); + responders.emplace_back(port_B, family); + for (auto &socket : responders) { + if (!socket.open()) { + cerr << "Failed to reserve one of the port" << endl; + return EXIT_FAILURE; + } + } + cout << "Ports successfully reserved" << endl; + + MinimalSocket::samples::Pollables pollables; + + auto make_pollable_responder = [](MinimalSocket::udp::Udp &responder) { + return [&responder]() { + auto request = responder.receive(500); + if (!request.has_value()) { + return MinimalSocket::samples::PollableStatus::NOT_ADVANCED; + } + const auto &response = + MinimalSocket::samples::NamesCircularIterator::NAMES_SURNAMES + .find(request->received_message) + ->second; + cout << MinimalSocket::samples::TimeOfDay{} + << " received: " << request->received_message + << " from: " << MinimalSocket::to_string(request->sender) + << " ; sending: " << response << endl; + responder.sendTo(response, request->sender); + return MinimalSocket::samples::PollableStatus::ADVANCED; + }; + }; + + for (auto &socket : responders) { + pollables.emplace(make_pollable_responder(socket)); + } + + pollables.loop(std::chrono::seconds{5}); + + return EXIT_SUCCESS; +} diff --git a/samples/udp/UdpScriptsGenerator.cpp b/samples/udp/UdpScriptsGenerator.cpp deleted file mode 100644 index 09e7e057..00000000 --- a/samples/udp/UdpScriptsGenerator.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Author: Andrea Casalino - * Created: 16.05.2019 - * - * report any bug to andrecasa91@gmail.com. - **/ - -/////////////////////////////////////////////////////////////////////////// -// Have a look to README.md // -/////////////////////////////////////////////////////////////////////////// - -#include - -#include -using namespace std; - -int main() { - { - // 1 responder 1 asker - const std::string sample_name = "udp01_responder_asker"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::size_t port_asker = 36995; - const std::size_t port_responder = port_asker + 10; - - generator.add("UdpResponder", - {{"port_this", std::to_string(port_responder)}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker)}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - { - // 1 connecting responder 1 asker - const std::string sample_name = "udp02_connecting_responder_asker"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::size_t port_asker = 36995; - const std::size_t port_responder = port_asker + 10; - - generator.add( - "UdpResponder", - {{"port_this", std::to_string(port_responder)}, {"connect", "yes"}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker)}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - { - // 1 responder 2 askers - const std::string sample_name = "udp03_responder_2_askers"; - MinimalSocket::samples::ScriptGenerator generator; - - const std::size_t port_responder = 36995; - const std::size_t port_asker_1 = port_responder + 10; - const std::size_t port_asker_2 = port_responder + 20; - - generator.add("UdpResponder", - {{"port_this", std::to_string(port_responder)}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker_1)}}); - - generator.add("UdpAsker", {{"port", std::to_string(port_responder)}, - {"port_this", std::to_string(port_asker_2)}, - {"rate", "800"}}); - - cout << "generating " << sample_name << endl; - generator.generate(sample_name); - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/samples/utils/Args.cpp b/samples/utils/Args.cpp index 827162cb..9db79eaa 100644 --- a/samples/utils/Args.cpp +++ b/samples/utils/Args.cpp @@ -49,22 +49,6 @@ Args::Args(const int argc, const char **argv) { std::cout << std::endl; } -std::string Args::getValue(const std::string &argument_name, - const std::string &default_value) const { - auto args_it = arguments_map.find(argument_name); - return (args_it == arguments_map.end()) ? default_value : args_it->second; -} - -std::string Args::getValue(const std::string &argument_name) const { - auto args_it = arguments_map.find(argument_name); - if (args_it == arguments_map.end()) { - std::stringstream stream; - stream << "--" << argument_name << " was not specififed"; - throw std::runtime_error{stream.str()}; - } - return args_it->second; -} - MinimalSocket::AddressFamily to_family(const std::string &family_as_string) { if (family_as_string == "v4") { return MinimalSocket::AddressFamily::IP_V4; diff --git a/samples/utils/Args.h b/samples/utils/Args.h index 22dc9dec..c5d9f543 100644 --- a/samples/utils/Args.h +++ b/samples/utils/Args.h @@ -9,51 +9,80 @@ #include +#include #include #include #include #include namespace MinimalSocket::samples { -using ArgsMap = std::unordered_map; +template struct Convert {}; -// Group the passed args into an ordered table -class Args { -public: - Args(Args &&) = default; +template <> struct Convert { + static std::string convert(const std::string &val) { return val; } +}; - static std::optional parse(const int argc, const char **argv); +template <> struct Convert { + static bool convert(const std::string &val) { + if (val == "1" || val == "yes" || val == "true") { + return true; + } + if (val == "0" || val == "no" || val == "false") { + return false; + } + throw std::runtime_error{"Unrecognized boolean value"}; + } +}; - // default value is returned in case the argument name is not found among the - // parsed ones - std::string getValue(const std::string &argument_name, - const std::string &default_value) const; +template <> struct Convert { + static int convert(const std::string &val) { return std::atoi(val.c_str()); } +}; - // throw if this option does not exists - std::string getValue(const std::string &argument_name) const; +template <> struct Convert { + static std::chrono::milliseconds convert(const std::string &val) { + return std::chrono::milliseconds{std::atoi(val.c_str())}; + } +}; - template - int getIntValue(const std::string &argument_name) const { - auto temp = getValue(argument_name, ""); - if (temp.empty()) { - return DefaultValue; - } - return std::atoi(temp.c_str()); +template <> struct Convert { + static MinimalSocket::Port convert(const std::string &val) { + return static_cast(std::atoi(val.c_str())); } +}; - int getIntValue(const std::string &argument_name) const { - auto temp = getValue(argument_name); - return std::atoi(temp.c_str()); +MinimalSocket::AddressFamily to_family(const std::string &family_as_string); + +template <> struct Convert { + static MinimalSocket::AddressFamily convert(const std::string &val) { + return to_family(val); + } +}; + +// Group the passed args into an ordered table +class Args { +public: + static std::optional parse(const int argc, const char **argv); + + template + T getValue(const std::string &argument_name, + const std::optional &default_value = std::nullopt) const { + auto it = arguments_map.find(argument_name); + if (it == arguments_map.end()) { + if (default_value == std::nullopt) { + std::string msg = "Unable to find '" + argument_name + "'"; + throw std::runtime_error{msg}; + } + return default_value.value(); + } + return Convert::convert(it->second); } private: Args(const int argc, const char **argv); - ArgsMap arguments_map; + std::unordered_map arguments_map; }; -MinimalSocket::AddressFamily to_family(const std::string &family_as_string); - #define PARSE_ARGS \ auto options = MinimalSocket::samples::Args::parse(argc, argv); \ if (std::nullopt == options) { \ diff --git a/samples/utils/Ask.h b/samples/utils/Ask.h index ef0fcb37..5231646a 100644 --- a/samples/utils/Ask.h +++ b/samples/utils/Ask.h @@ -7,7 +7,8 @@ #pragma once -#include +#include +#include #include #include @@ -17,34 +18,36 @@ #include namespace MinimalSocket::samples { -template -void ask(SocketT &channel, const std::chrono::milliseconds &rate, - std::size_t cycles) { +void ask_connected(ReceiverBlocking &receiver, Sender &sender, + const std::chrono::milliseconds &rate, std::size_t cycles) { NamesCircularIterator iterator; - for (std::size_t k = 0; k < cycles * NamesCircularIterator::size(); ++k) { + for (std::size_t k = 0; + k < cycles * NamesCircularIterator::NAMES_SURNAMES.size(); ++k) { // send name of this person std::cout << TimeOfDay{} << "Sending: " << iterator.current()->first << std::endl; - channel.send(iterator.current()->first); + sender.send(iterator.current()->first); // expect to get back the corresponding surname - auto response = channel.receive(500); + auto response = receiver.receive(500); std::cout << TimeOfDay{} << "Got response: " << response << std::endl; iterator.next(); std::this_thread::sleep_for(rate); } } -void ask(MinimalSocket::udp::UdpBinded &channel, - const MinimalSocket::Address &target, - const std::chrono::milliseconds &rate, std::size_t cycles) { +void ask_disconnected(ReceiverUnkownSenderBlocking &receiver, SenderTo &sender, + const MinimalSocket::Address &target, + const std::chrono::milliseconds &rate, + std::size_t cycles) { NamesCircularIterator iterator; - for (std::size_t k = 0; k < cycles * NamesCircularIterator::size(); ++k) { + for (std::size_t k = 0; + k < cycles * NamesCircularIterator::NAMES_SURNAMES.size(); ++k) { // send name of this person std::cout << TimeOfDay{} << "Sending: " << iterator.current()->first << std::endl; - channel.sendTo(iterator.current()->first, target); + sender.sendTo(iterator.current()->first, target); // expect to get back the corresponding surname - auto response = channel.receive(500); + auto response = receiver.receive(500); std::cout << TimeOfDay{} << "From " << MinimalSocket::to_string(response->sender) << " , got as response: " << response->received_message @@ -53,4 +56,17 @@ void ask(MinimalSocket::udp::UdpBinded &channel, std::this_thread::sleep_for(rate); } } + +template +void ask(SocketT &socket, const std::chrono::milliseconds &rate, + std::size_t cycles) { + ask_connected(socket, socket, rate, cycles); +} + +template +void ask(SocketT &socket, const MinimalSocket::Address &target, + const std::chrono::milliseconds &rate, std::size_t cycles) { + ask_disconnected(socket, socket, target, rate, cycles); +} + } // namespace MinimalSocket::samples diff --git a/samples/utils/Names.cpp b/samples/utils/Names.cpp index 159012db..b3052e36 100644 --- a/samples/utils/Names.cpp +++ b/samples/utils/Names.cpp @@ -8,16 +8,8 @@ #include namespace MinimalSocket::samples { -const Names NamesCircularIterator::NAMES_SURNAMES = - Names{{"Luciano", "Pavarotti"}, - {"Gengis", "Khan"}, - {"Giulio", "Cesare"}, - {"Theodor", "Roosvelt"}, - {"Immanuel", "Kant"}}; - void NamesCircularIterator::next() { - ++current_; - if (current_ == NAMES_SURNAMES.end()) { + if (++current_ == NAMES_SURNAMES.end()) { current_ = NAMES_SURNAMES.begin(); } } diff --git a/samples/utils/Names.h b/samples/utils/Names.h index 6e795733..0a125a76 100644 --- a/samples/utils/Names.h +++ b/samples/utils/Names.h @@ -7,30 +7,28 @@ #pragma once -#include #include #include namespace MinimalSocket::samples { -using Names = std::unordered_map; - -using NamesIterator = Names::const_iterator; class NamesCircularIterator { public: - static const Names NAMES_SURNAMES; + using Names = std::unordered_map; + using NamesIterator = Names::const_iterator; + static inline Names NAMES_SURNAMES = {{"Luciano", "Pavarotti"}, + {"Gengis", "Khan"}, + {"Giulio", "Cesare"}, + {"Theodor", "Roosvelt"}, + {"Immanuel", "Kant"}}; - NamesCircularIterator() : current_(NAMES_SURNAMES.begin()){}; + NamesCircularIterator() = default; const NamesIterator ¤t() { return current_; }; void next(); - static std::size_t size() { - return NamesCircularIterator::NAMES_SURNAMES.size(); - } - private: - NamesIterator current_; + NamesIterator current_ = NAMES_SURNAMES.begin(); }; } // namespace MinimalSocket::samples diff --git a/samples/utils/Pollables.cpp b/samples/utils/Pollables.cpp new file mode 100644 index 00000000..5630e1ec --- /dev/null +++ b/samples/utils/Pollables.cpp @@ -0,0 +1,34 @@ +#include "Pollables.h" + +namespace MinimalSocket::samples { +void Pollables::loop(const std::chrono::seconds &timeout) { + auto last_notable_event = std::chrono::high_resolution_clock::now(); + while (!pollables_.empty()) { + auto it = pollables_.begin(); + while (it != pollables_.end()) { + auto stat = (*it)(); + switch (stat) { + case PollableStatus::NOT_ADVANCED: + ++it; + break; + case PollableStatus::ADVANCED: + last_notable_event = std::chrono::high_resolution_clock::now(); + ++it; + break; + case PollableStatus::COMPLETED: + last_notable_event = std::chrono::high_resolution_clock::now(); + it = pollables_.erase(it); + break; + } + } + + auto elapsed_since_last_notable = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - last_notable_event); + if (timeout < elapsed_since_last_notable) { + break; + } + } +} + +} // namespace MinimalSocket::samples diff --git a/samples/utils/Pollables.h b/samples/utils/Pollables.h new file mode 100644 index 00000000..8ca3745f --- /dev/null +++ b/samples/utils/Pollables.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace MinimalSocket::samples { +enum class PollableStatus { NOT_ADVANCED, ADVANCED, COMPLETED }; + +using Pollable = std::function; + +class Pollables { +public: + Pollables() = default; + + template void emplace(Pred &&pred) { + pollables_.emplace_back(std::forward(pred)); + } + + // At each iteration, it polls one by one all the pollables. + // If there is no progress for a time period higher than the specified + // timeout, the function completes and return to the caller. + void loop(const std::chrono::seconds &timeout); + +private: + std::list pollables_; +}; + +} // namespace MinimalSocket::samples diff --git a/samples/utils/Respond.h b/samples/utils/Respond.h index 0d627861..53a05a2b 100644 --- a/samples/utils/Respond.h +++ b/samples/utils/Respond.h @@ -7,31 +7,38 @@ #pragma once -#include +#include +#include +#include #include namespace MinimalSocket::samples { template void respond(SocketT &channel) { while (true) { - // receive name to search - auto request = channel.receive(500, std::chrono::seconds{5}); - // respond with corresponding surname - if constexpr (std::is_same::value) { - if (!request.has_value()) { - break; + try { + // receive name to search + auto request = channel.receive(500, std::chrono::seconds{5}); + // respond with corresponding surname + if constexpr (std::is_base_of_v) { + if (!request.has_value()) { + break; + } + const auto &response = NamesCircularIterator::NAMES_SURNAMES + .find(request->received_message) + ->second; + channel.sendTo(response, request->sender); + } else { + if (request.empty()) { + break; + } + const auto &response = + NamesCircularIterator::NAMES_SURNAMES.find(request)->second; + channel.send(response); } - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request->received_message) - ->second; - channel.sendTo(response, request->sender); - } else { - if (request.empty()) { - break; - } - const auto &response = - NamesCircularIterator::NAMES_SURNAMES.find(request)->second; - channel.send(response); + } catch (const MinimalSocket::SocketError &) { + // if here the connection was shut down + break; } } } diff --git a/src/header/MinimalSocket/Error.h b/src/header/MinimalSocket/Error.h index 0a568db4..7aa96374 100644 --- a/src/header/MinimalSocket/Error.h +++ b/src/header/MinimalSocket/Error.h @@ -45,8 +45,8 @@ class ErrorCodeHolder { class SocketError : public ErrorCodeHolder, public Error { public: /** - * @brief last error code raised by the socket API is automatically retrieved - * and appended to error message + * @brief The last raised error code is automatically retrieved + * and included in the error message */ SocketError(const std::string &what); @@ -56,6 +56,6 @@ class SocketError : public ErrorCodeHolder, public Error { class TimeOutError : public Error { public: - TimeOutError() : Error("Timeout"){}; + TimeOutError() : Error("Timeout reached"){}; }; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Address.h b/src/header/MinimalSocket/core/Address.h index 310951fb..9c4f7e02 100644 --- a/src/header/MinimalSocket/core/Address.h +++ b/src/header/MinimalSocket/core/Address.h @@ -21,23 +21,20 @@ enum class AddressFamily { IP_V4, IP_V6 }; using Port = std::uint16_t; /** - * @brief Passing this value as Port implies to ask the system to reserve a - * random port. + * @brief Used to indicate a random port that can be assigned by the os. */ static constexpr Port ANY_PORT = 0; class Address { public: /** - * @brief Internally the AddressFamily is deduced according to the - * hostIp content. - * In case of invalid host, the object is built but left empty (i.e. *this == - * nullptr would be true) + * @brief The AddressFamily is deduced on the basis of the hostIp. + * @throw In case of an invalid hostIp */ Address(const std::string &hostIp, Port port); /** - * @brief Local host address is asumed. + * @brief Local host on the specified port is assumed as address. */ Address(Port port, AddressFamily family = AddressFamily::IP_V4); @@ -47,12 +44,6 @@ class Address { bool operator==(const Address &o) const; - Address(const Address &) = default; - Address &operator=(const Address &) = default; - - Address(Address &&) = default; - Address &operator=(Address &&) = default; - private: Address() = default; @@ -62,20 +53,17 @@ class Address { }; /** - * @return "host:port" into a string. + * @return "host:port" */ std::string to_string(const Address &subject); /** * @brief Tries to deduce the family from the host. - * @return nullopt in case the host is invalid, otherwise the deduced family - * value is returned. + * @return nullopt in case the host is invalid, otherwise the family + * conrresponding to the passed address */ std::optional deduceAddressFamily(const std::string &host_address); -bool operator==(std::nullptr_t, const Address &subject); -bool operator==(const Address &subject, std::nullptr_t); - bool isValidHost(const std::string &host_address); } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Definitions.h b/src/header/MinimalSocket/core/Definitions.h index 708b2b4f..68226a66 100644 --- a/src/header/MinimalSocket/core/Definitions.h +++ b/src/header/MinimalSocket/core/Definitions.h @@ -22,7 +22,7 @@ struct BufferView { void clear(BufferView &subject); /** - * @param subject the string buffer to convert + * @param subject the string to convert * @return a buffer pointing to the first element of the subject, and a lenght * equal to the current size of subject */ diff --git a/src/header/MinimalSocket/core/Receiver.h b/src/header/MinimalSocket/core/Receiver.h index b6fffef9..307ca17d 100644 --- a/src/header/MinimalSocket/core/Receiver.h +++ b/src/header/MinimalSocket/core/Receiver.h @@ -13,102 +13,179 @@ #include namespace MinimalSocket { -class ReceiverBase : public virtual Socket { +class ReceiverWithTimeout : public virtual Socket { protected: - template - void lazyUpdateAndUseTimeout(const Timeout &to, Pred what) { - std::scoped_lock lock{receive_mtx}; - updateTimeout_(to); - what(receive_timeout); - } - -private: void updateTimeout_(const Timeout &timeout); - std::mutex receive_mtx; +private: Timeout receive_timeout = NULL_TIMEOUT; }; -/** - * @brief Typically associated to a connected socket, whose remote peer - * exchanging messages is known. - * Attention!! Even when calling from different threads some simultaneously - * receive, they will be satisfited one at a time, as an internal mutex must be - * locked before starting to receive. - */ -class Receiver : public ReceiverBase { +class ReceiverBlocking : public ReceiverWithTimeout { public: /** - * @param message the buffer that will store the received bytes. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking receive. - * @return the number of received bytes actually received and copied into - * message. It can be also lower then buffer size, as less bytes might be - * received. + * @param the buffer that will store the received message. + * @param optional timeout after which the receive is considered failed. A + * NULL_TIMEOUT means actually to begin a blocking receive. + * @return the number of actually received bytes. It can be also lower then + * the message cacapity, as that represents the maxium number of bytes + * expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::size_t receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); /** - * @brief Similar to Receiver::receive(Buffer &, const Timeout &), but - * internally building the recipient buffer which is converted into a string - * before returning it. - * - * @param expected_max_bytes maximum number of bytes to receive - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking receive. + * @brief Similar to Receiver::receive(BufferView &, const Timeout &), but + * returning a string as a buffer containing the received message. + * @param maximum number of bytes to receive + * @param optional timeout after which the receive is considered failed. A + * NULL_TIMEOUT means actually to begin a blocking receive. * @return the received sequence of bytes as a string. The size of the result - * might be less than expected_max_bytes, in case less bytes were actually - * received. + * might be less than expected_max_bytes, as that represents the maxium number + * of bytes expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::string receive(std::size_t expected_max_bytes, const Timeout &timeout = NULL_TIMEOUT); + +private: + std::mutex recv_mtx; }; -/** - * @brief Typically associated to a non connected socket, whose remote peer that - * sends bytes is not fixed. - * Attention!! Even when calling from different threads some simultaneously - * receive, they will be satisfited one at a time, as an internal mutex must be - * locked before starting to receive. - */ -class ReceiverUnkownSender : public ReceiverBase { +class ReceiverNonBlocking : public virtual Socket { public: - struct ReceiveResult { - Address sender; - std::size_t received_bytes; - }; /** - * @param message the buffer that will store the received bytes. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to + * @brief Notice this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, 0 will be + * immediately returned. + * @param the buffer that will store the received bytes. + * @return the number of actually received bytes. It can be also lower then + * the message cacapity, as that represents the maxium number of bytes + * expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::size_t receive(BufferView message); + + /** + * @brief Similar to Receiver::receive(BufferView), but + * returning a string as a buffer containing the received message. + * Notice that this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, an empty + * string will be immediately returned. + * + * @param expected_max_bytes maximum number of bytes to receive + * @return the received sequence of bytes as a string. The size of the result + * might be less than expected_max_bytes, as that represents the maxium number + * of bytes expected to be received. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::string receive(std::size_t expected_max_bytes); + +private: + std::mutex recv_mtx; +}; + +template class Receiver {}; +template <> class Receiver : public ReceiverBlocking {}; +template <> class Receiver : public ReceiverNonBlocking {}; + +struct ReceiveResult { + Address sender; + std::size_t received_bytes; +}; + +struct ReceiveStringResult { + Address sender; + std::string received_message; +}; + +class ReceiverUnkownSenderBlocking : public ReceiverWithTimeout { +public: + /** + * @param the buffer that will store the received bytes. + * @param the timeout to consider. A NULL_TIMEOUT means actually to * begin a blocking receive. * @return the number of received bytes actually received and copied into * message, together with the address of the sender. The received bytes can be * also lower then buffer size, as less bytes might be received. * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::optional receive(BufferView message, const Timeout &timeout = NULL_TIMEOUT); - struct ReceiveStringResult { - Address sender; - std::string received_message; - }; /** - * @brief Similar to ReceiverUnkownSender::receive(Buffer &, const Timeout &), - * but internally building the recipient buffer which is converted into a + * @brief Similar to ReceiverUnkownSender::receive(BufferView &, const Timeout + * &), but internally building the recipient buffer which is converted into a * string before returning it. * - * @param expected_max_bytes maximum number of bytes to receive - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to + * @param maximum number of bytes to receive + * @param the timeout to consider. A NULL_TIMEOUT means actually to * begin a blocking receive. * @return the received sequence of bytes as a string, together with the * address of the sender. The size of the result might be less than * expected_max_bytes, in case less bytes were actually received. * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ std::optional receive(std::size_t expected_max_bytes, const Timeout &timeout = NULL_TIMEOUT); + +private: + std::mutex recv_mtx; }; + +class ReceiverUnkownSenderNonBlocking : public virtual Socket { +public: + /** + * @brief Notice this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, a nullopt + * will be actually returned. + * @param the buffer that will store the received bytes. + * @return the number of received bytes actually received and copied into + * message, together with the address of the sender. The received bytes can be + * also lower then buffer size, as less bytes might be received. + * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::optional receive(BufferView message); + + /** + * @brief Similar to ReceiverUnkownSender::receive(BufferView &, const Timeout + * &), but internally building the recipient buffer which is converted into a + * string before returning it. + * Notice this is a non blocking operation, meaning that in case no new + * bytes were actually received at the time of calling this method, a nullopt + * will be actually returned. + * + * @param maximum number of bytes to receive + * @param the timeout to consider. A NULL_TIMEOUT means actually to + * begin a blocking receive. + * @return the received sequence of bytes as a string, together with the + * address of the sender. The size of the result might be less than + * expected_max_bytes, in case less bytes were actually received. + * In case no bytes were received within the timeout, a nullopt is returned. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. + */ + std::optional receive(std::size_t expected_max_bytes); + +private: + std::mutex recv_mtx; +}; + +template class ReceiverUnkownSender {}; +template <> +class ReceiverUnkownSender : public ReceiverUnkownSenderBlocking {}; +template <> +class ReceiverUnkownSender : public ReceiverUnkownSenderNonBlocking {}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/Sender.h b/src/header/MinimalSocket/core/Sender.h index 161a07c8..3849858e 100644 --- a/src/header/MinimalSocket/core/Sender.h +++ b/src/header/MinimalSocket/core/Sender.h @@ -14,24 +14,31 @@ #include namespace MinimalSocket { -/** - * @brief Typically associated to a connected socket, whose remote peer - * exchanging messages is known. - * Attention!! Even when calling from different threads some simultaneously - * send, they will be satisfited one at a time, as an internal mutex must be - * locked before starting to receive. - */ class Sender : public virtual Socket { public: /** - * @param message the buffer storing the bytes to send - * @return true in case all the bytes were successfully sent + * @param the buffer storing the bytes to send + * @return true in case all the bytes were successfully sent. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool send(const BufferViewConst &message); /** - * @param message the buffer storing the bytes to send as a string - * @return true in case all the bytes were successfully sent + * @param the buffer storing the bytes to send as a string + * @return true in case all the bytes were successfully sent. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool send(const std::string &message); @@ -39,29 +46,35 @@ class Sender : public virtual Socket { std::mutex send_mtx; }; -/** - * @brief Typically associated to a non connected socket, whose remote peer that - * sends bytes is not fixed. - * Attention!! It is thread safe to simultaneously send messages from different - * threads to many different recipients. - * However, be aware that in case 2 or more threads are sending a message to the - * same recipient, sendTo request will be queued and executed one at a time. - */ class SenderTo : public virtual Socket { public: /** - * @param message the buffer storing the bytes to send - * @param recipient the recpient of the message + * @param the buffer storing the bytes to send + * @param the recpient of the message * @return true in case all the bytes were successfully sent to the specified - * recipient + * recipient. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool sendTo(const BufferViewConst &message, const Address &recipient); /** - * @param message the buffer storing the bytes to send as a string - * @param recipient the recpient of the message + * @param the buffer storing the bytes to send as a string + * @param the recpient of the message * @return true in case all the bytes were successfully sent to the specified - * recipient + * recipient. In case the + * socket is non blocking, false is returned when the buffer is full and no + * further bytes can be inserted. In such case, the send fails but does not + * throw. + * On the opposite, a blocking socket will wait (absorbing the calling thread) + * until the buffer has enough space to proceed with the send. + * @throw When the receive is not possible. This can be due to the fact that + * the connection was terminated or the socket was actually transferred. */ bool sendTo(const std::string &message, const Address &recipient); diff --git a/src/header/MinimalSocket/core/Socket.h b/src/header/MinimalSocket/core/Socket.h index 9bf52c11..d790889a 100644 --- a/src/header/MinimalSocket/core/Socket.h +++ b/src/header/MinimalSocket/core/Socket.h @@ -23,18 +23,18 @@ using WSAVersion = std::array; /** * @brief Refer to * https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup - * In windows, any application using sockets, need to initialize the WSA data - * structure. When doing this, the version to use must be specified. This will - * be internally handled by this library, before calling any fucntion that - * requires the WSA data to be already initialized. - * The WSA data will be automatically cleaned up when the process terminates. + * When operating under Windows, any application using sockets need to + * initialize the WSA data structure. When doing this, the version to use must + * be specified. This is internally handled, before using any relevant + * functionality. The WSA data will be automatically cleaned up when the process + * terminates. * - * This object allows you specify the WSA version used to initialize the WSA - * data. When calling setWsaVersion(...) you can change the WSA version and such - * information will be used next time the WSA data will be (automatically and - * internally) initialized. Clearly, in case the WSA data was already - * initialized, as a consequence of creating any kind of sockets or using one of - * the features defined in Address.h, setWsaVersion has actually no effect. + * This object allows you to specify the WSA version to use. When calling + * setWsaVersion(...) you can change the WSA version, as this information will + * be used next time the WSA data will be (automatically and internally) + * initialized. Clearly, in case the WSA data was already initialized, as a + * consequence of creating any kind of sockets or using one of the features + * defined in Address.h, setWsaVersion has actually no effect. */ class WSAManager { public: @@ -50,7 +50,7 @@ class WSAManager { class SocketHandler; /** - * @brief The base onject of any kind of socket. + * @brief The base object for any socket. */ class Socket { public: @@ -60,12 +60,12 @@ class Socket { Socket &operator=(const Socket &) = delete; /** - * @return the socket descriptor associated to this object. + * @return the file descriptor associated to this socket. * - * This might be: + * This could be: * * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket - * in windows is the SOCKET (cast as in integer) data used to identify the + * in Windows is the SOCKET (cast as an integer) used to identify the * underlying socket and returned by ::socket(...) * * https://man7.org/linux/man-pages/man2/socket.2.html @@ -74,13 +74,21 @@ class Socket { * * Normally, all the operations involving the socket should be handled by the * functions provided by this library. Therefore, you shouldn't need to - * access this number However, it might happen that sometimes you want to - * specify additional socket option, and in order to do that you need this - * number. Beware that you should really know what you are doing when using - * this number. + * access this value. However, it might happen that you want to + * specify additional options, and in order to do so you need to access such + * value. Beware that you should really know what you are doing when using + * this value. */ int getSocketDescriptor() const; + /** + * @return true in case the socket is blocking, i.e. all possible operations + * absorb the calling thread till completion. Otherwise false, for a + * socket configured to be non blocking, i.e. operations immediately + * succeed or not. + */ + bool isBlocking() const { return isBlocking_; } + protected: Socket(); @@ -91,37 +99,58 @@ class Socket { SocketHandler &getHandler(); void resetHandler(); + void setNonBlocking() { isBlocking_ = false; } + void setUp(); + private: + bool isBlocking_ = true; std::unique_ptr socket_id_wrapper; }; -class Openable : public virtual Socket { +class OpenableBase : public virtual Socket { public: - virtual ~Openable() = default; + virtual ~OpenableBase() = default; bool wasOpened() const { return opened; } - /** - * @brief Tries to do all the steps required to open this socket. In case - * potentially expected expections are raised, they are interally catched and - asserted. - * On the opposite, unexpected expections are passed to the caller. - * In both cases, the object is inernally closed and left in a state for which - * open may be tried again. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking open. - */ - bool open(const Timeout &timeout = NULL_TIMEOUT); - protected: - Openable() = default; + OpenableBase() = default; - void steal(Openable &giver); // Socket::steal(...) is also called - void transfer(Openable &recipient) { recipient.steal(*this); } + void steal(OpenableBase &giver); // Socket::steal(...) is also called + void transfer(OpenableBase &recipient) { recipient.steal(*this); } virtual void open_() = 0; -private: std::mutex open_procedure_mtx; std::atomic_bool opened = false; }; + +class Openable : public OpenableBase { +public: + /** + * @brief Tries to execute all required steps in order to open this socket. + * Expections (but actually only those extending the Error class) thrown will + * doing so, are interally catched, terminating the opening phase and + * consequently leaving the socket closed. On the opposite, unexpected + * expections are passed to the caller. In both cases, the object is inernally + * closed and left in a state for which open may be tried again. + */ + bool open(); +}; + +class OpenableWithTimeout : public OpenableBase { +public: + /** + * @brief Tries to execute all required steps in order to open this socket. + * Expections (but actually only those extending the Error class) thrown will + * doing so, are interally catched, terminating the opening phase and + * consequently leaving the socket closed. On the opposite, unexpected + * expections are passed to the caller. In both cases, the object is inernally + * closed and left in a state for which open may be tried again. + * @param the timeout to consider. A NULL_TIMEOUT means actually to + * begin a blocking open. On the contrary, in case the open steps are not + * completely within the specified timeout, function returns to the caller and + * leave the socket closed. + */ + bool open(const Timeout &timeout = NULL_TIMEOUT); +}; } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/core/SocketContext.h b/src/header/MinimalSocket/core/SocketContext.h index d6614f35..25b118b3 100644 --- a/src/header/MinimalSocket/core/SocketContext.h +++ b/src/header/MinimalSocket/core/SocketContext.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -29,10 +30,6 @@ class RemoteAddressAware { Address getRemoteAddress() const; protected: - /** - * @throw in case the passed address is invalid (i.e. address == nullptr is - * true). - */ RemoteAddressAware(const Address &address); private: @@ -52,16 +49,14 @@ class PortToBindAware { } /** - * @return the port that will be reserved, in case the socket was not already - * opened, or the port actually reserved when the socket was opened. + * @return the port to reserve. */ Port getPortToBind() const { return port_to_bind; } /** - * @brief Used to enforce the fact that this port should be not previously - * binded by anyone else when opening the socket. Beware that the default - * behaviour is the opposite: you don't call this function the port will be - * possibly re-used. + * @brief Used to specify that the port should be actually free when trying to + * open this socket. Beware that the default behaviour is the opposite: until + * you don't call this function the port will be re-used. */ void mustBeFreePort() { must_be_free_port = true; }; bool shallBeFreePort() const { return must_be_free_port; } @@ -98,4 +93,5 @@ class RemoteAddressFamilyAware { private: std::atomic remote_address_family; }; + } // namespace MinimalSocket diff --git a/src/header/MinimalSocket/tcp/TcpClient.h b/src/header/MinimalSocket/tcp/TcpClient.h index 4e35d618..7a246fa9 100644 --- a/src/header/MinimalSocket/tcp/TcpClient.h +++ b/src/header/MinimalSocket/tcp/TcpClient.h @@ -13,30 +13,47 @@ #include namespace MinimalSocket::tcp { -class TcpClient : public NonCopiable, - public Openable, - public Sender, - public Receiver, - public RemoteAddressAware { -public: - TcpClient(TcpClient &&o); - TcpClient &operator=(TcpClient &&o); +class TcpClientBase : public NonCopiable, + public OpenableWithTimeout, + public Sender, + public RemoteAddressAware { +protected: + TcpClientBase(TcpClientBase &&o); + + void stealBase(TcpClientBase &o); + TcpClientBase(const Address &server_address, bool block_mode); + + void open_() override; +}; + +template +class TcpClient : public TcpClientBase, public Receiver { +public: /** - * @brief The connection to the server is not asked in this c'tor which - * simply initialize this object. Such a connection is tried to be established - * when calling open(...) + * @brief After construction, the socket is left closed. Indeed, the + * connection to the server is actually asked when opening the client. * @param server_address the server to reach when opening this socket */ - TcpClient(const Address &server_address); + TcpClient(const Address &server_address) + : TcpClientBase{server_address, BlockMode} {} -protected: - void open_() override; + TcpClient(TcpClient &&o) + : TcpClientBase{std::forward(o)} {} + TcpClient &operator=(TcpClient &&o) { + this->stealBase(o); + return *this; + } }; /** - * @return a client ready to ask the connection to the same server. - * Beware that a closed socket is returned, which can be later opened. + * @return a client ready to ask the connection to the same server targeted by + * the passed client. Beware that a closed socket is returned, which can be + * later opened. */ -TcpClient clone(const TcpClient &o); +template +TcpClient clone(const TcpClient &o) { + return TcpClient{o.getRemoteAddress()}; +} + } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/tcp/TcpServer.h b/src/header/MinimalSocket/tcp/TcpServer.h index 62d0da1c..57854118 100644 --- a/src/header/MinimalSocket/tcp/TcpServer.h +++ b/src/header/MinimalSocket/tcp/TcpServer.h @@ -16,79 +16,171 @@ #include namespace MinimalSocket::tcp { -class TcpServer; +class TcpServerBase; +class TcpConnectionNonBlocking; + /** - * @brief Handler of an already established connection with a client, on the - * server side. - * An istance of this object is created calling TcpServer::acceptNewClient(). + * @brief Handler of an already established connection. */ -class TcpConnection : public NonCopiable, - public Sender, - public Receiver, - public RemoteAddressAware { - friend class TcpServer; +class TcpConnectionBlocking : public NonCopiable, + public Sender, + public Receiver, + public RemoteAddressAware { + friend class TcpServerBase; public: - TcpConnection(TcpConnection &&o); - TcpConnection &operator=(TcpConnection &&o); + TcpConnectionBlocking(TcpConnectionBlocking &&o); + TcpConnectionBlocking &operator=(TcpConnectionBlocking &&o); + + /** + * @brief Beware that the giver objet is left empty. + */ + TcpConnectionNonBlocking turnToNonBlocking(); private: - TcpConnection(const Address &remote_address); + TcpConnectionBlocking(const Address &remote_address); }; -class TcpServer : public NonCopiable, - public PortToBindAware, - public RemoteAddressFamilyAware, - public Openable { +/** + * @brief Similar to TcpConnectionBlocking, but representing the non blocking + * version. + */ +class TcpConnectionNonBlocking : public NonCopiable, + public Sender, + public Receiver, + public RemoteAddressAware { public: - TcpServer(TcpServer &&o); - TcpServer &operator=(TcpServer &&o); + TcpConnectionNonBlocking(TcpConnectionNonBlocking &&o); + TcpConnectionNonBlocking &operator=(TcpConnectionNonBlocking &&o); + + TcpConnectionNonBlocking(TcpConnectionBlocking &&connection); +}; + +class TcpServerBase : public NonCopiable, + public PortToBindAware, + public RemoteAddressFamilyAware, + public Openable { +protected: + TcpServerBase(TcpServerBase &&o); + + void stealBase(TcpServerBase &o); + TcpServerBase(Port port_to_bind, AddressFamily accepted_client_family, + bool block_mode); + +public: /** - * @brief The port is not reserved in this c'tor which - * simply initialize this object. Such a thing is done when - * when calling open(...) which does: - * bind to the specified port this server - * start listening for clients on the same port - * @param port_to_bind the port that will be reserved when opening this server - * @param accepted_client_family family of the client that will ask the - * connection to this server + * @param the maximum size of the queue of clients waiting to establish a + * connection with this server. + * https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html#:~:text=TCP%20listen()%20Backlog,queue%20of%20partially%20open%20connections. + * (valid also for Windows) + * @throw in case the server was already opened. */ - TcpServer(Port port_to_bind = ANY_PORT, - AddressFamily accepted_client_family = AddressFamily::IP_V4); + void setClientQueueSize(std::size_t queue_size); + +protected: + void open_() override; + + struct AcceptedSocket; + void acceptClient_(AcceptedSocket &recipient); + static TcpConnectionBlocking makeClient(const AcceptedSocket &acceptedSocket); + +private: + std::atomic client_queue_size = 50; +}; + +class AcceptorBlocking : public TcpServerBase { +public: /** * @brief Wait till accepting the connection from a new client. This is a * blocking operation. */ - TcpConnection acceptNewClient(); // blocking + TcpConnectionBlocking acceptNewClient(); /** - * @brief Wait till accepting the connection from a new client. In case such a - * connection is not asked within the specified timeout, a nullopt is - * returned. - * @param timeout the timeout to consider. A NULL_TIMEOUT means actually to - * begin a blocking accept. + * @brief Similar to AcceptorBlocking::acceptNewClient, but returning a non + * blocking socket after the connection is established. + * Notice that in any case, this operation is blocking. */ - std::optional acceptNewClient(const Timeout &timeout); + TcpConnectionNonBlocking acceptNewNonBlockingClient() { + return acceptNewClient().turnToNonBlocking(); + } +protected: + template + AcceptorBlocking(Args &&...args) + : TcpServerBase{std::forward(args)...} {} + +private: + std::mutex accept_mtx; +}; + +class AcceptorNonBlocking : public TcpServerBase { +public: /** - * @param queue_size the backlog size to assume when the server will be - * opened, refer also to - * https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html#:~:text=TCP%20listen()%20Backlog,queue%20of%20partially%20open%20connections. - * (valid also for Windows) - * @throw in case the server was already opened. + * @brief Check if a new client tried to connect and eventually immediately + * return the object handling the connection. This is a non blocking + * operation: in case no client actually asked for the connection, the method + * immeditely returns a nullopt. + * + * Notice that even though this operation is non + * blocking, a blocking socket is returned. */ - void setClientQueueSize(const std::size_t queue_size); + std::optional acceptNewClient(); + + /** + * @brief Similar to AcceptorNonBlocking::acceptNewClient, but returning a non + * blocking socket. + */ + std::optional acceptNewNonBlockingClient() { + auto client = acceptNewClient(); + if (client.has_value()) { + return client->turnToNonBlocking(); + } + return std::nullopt; + } protected: - void open_() override; + template + AcceptorNonBlocking(Args &&...args) + : TcpServerBase{std::forward(args)...} {} private: - // maximum number of clients waiting for the connection to be - // accepted - std::atomic client_queue_size = 50; - std::mutex accept_mtx; }; + +template class Acceptor {}; +template <> class Acceptor : public AcceptorBlocking { +protected: + template + Acceptor(Args &&...args) : AcceptorBlocking{std::forward(args)...} {} +}; +template <> class Acceptor : public AcceptorNonBlocking { +protected: + template + Acceptor(Args &&...args) : AcceptorNonBlocking{std::forward(args)...} {} +}; + +template class TcpServer : public Acceptor { +public: + /** + * @brief After construction, the socket is left closed. Only after calling + * open(...), the port is binded and the server starts to listen for + * connection request on that port. + * @param the port that will be reserved when opening this server + * @param family of the client that will ask the + * connection to this server + */ + TcpServer(Port port_to_bind = 0, + AddressFamily accepted_client_family = AddressFamily::IP_V4) + : Acceptor{port_to_bind, accepted_client_family, BlockMode} {} + + TcpServer(TcpServer &&o) + : Acceptor{std::forward>(o)} {} + TcpServer &operator=(TcpServer &&o) { + this->stealBase(o); + return *this; + } +}; } // namespace MinimalSocket::tcp diff --git a/src/header/MinimalSocket/udp/UdpSocket.h b/src/header/MinimalSocket/udp/UdpSocket.h index 2d3bd86e..d800fde3 100644 --- a/src/header/MinimalSocket/udp/UdpSocket.h +++ b/src/header/MinimalSocket/udp/UdpSocket.h @@ -19,55 +19,44 @@ namespace MinimalSocket::udp { */ static constexpr std::size_t MAX_UDP_RECV_MESSAGE = 65507; -class UdpConnected; +template class UdpConnected; -/** - * @brief This kind of udp is agnostic of the remote address (which can also - * change over the time) exchanging messages with socket. - * This udp can be reached by any other udp, on the port reserved when opening - * this socket. - * At the same time, this udp can send messages to any other non connected udp - * sockets. - */ -class UdpBinded : public NonCopiable, - public SenderTo, - public ReceiverUnkownSender, - public PortToBindAware, - public RemoteAddressFamilyAware, - public Openable { -public: - UdpBinded(UdpBinded &&o); - UdpBinded &operator=(UdpBinded &&o); +class UdpBase : public NonCopiable, + public SenderTo, + public PortToBindAware, + public RemoteAddressFamilyAware, + public Openable { +protected: + UdpBase(UdpBase &&o); - /** - * @brief The port is not reserved in this c'tor which - * simply initialize this object. Such a thing is done when - * when opening this socket - * @param port_to_bind the port to reserve by this udp - * @param accepted_connection_family the kind of udp that can reach this one - */ - UdpBinded(Port port_to_bind = ANY_PORT, - AddressFamily accepted_connection_family = AddressFamily::IP_V4); + void stealBase(UdpBase &o); + + UdpBase(Port port_to_bind, AddressFamily accepted_connection_family, + bool blockMode); + void open_() override; +}; + +class UdpBlocking : public UdpBase, public ReceiverUnkownSender { +public: /** - * @brief Connects the udo socket to the specified remote address. + * @brief Connects the udp socket to the specified remote address. * This leads to transfer the ownership of the underlying socket to the * returned object while this socket is left empty and closed. * @param remote_address the address to use for connecting the socket * @return a socket connected to the passed remote address */ - UdpConnected connect(const Address &remote_address); + UdpConnected connect(const Address &remote_address); /** * @brief similar to connect(const Address &). Here, the remote address is not - * explicitly specified, but is assumed to be equal to the first udp sending a - * message to this one. The first sent message is not lost, and can be moved - * into initial_message if not set to nullptr. - * This is a blocking operation. - * @param initial_message the initial message sent from the remote peer to - * detect its address. + * explicitly specified, but is assumed to be equal to the one of the first + * socket sending a message to this one. The first sent message is not lost, + * and can be moved into initial_message if not set to nullptr. + * Notice that this is a blocking operation. + * @param initial_message the initial message sent from the remote peer. */ - UdpConnected connect(std::string *initial_message = nullptr); + UdpConnected connect(std::string *initial_message = nullptr); /** * @brief similar to connect(std::string *initial_message), but non blocking. @@ -78,54 +67,131 @@ class UdpBinded : public NonCopiable, * @param initial_message the initial message sent from the remote peer to * detect its address. */ - std::optional connect(const Timeout &timeout, - std::string *initial_message = nullptr); + std::optional> + connect(const Timeout &timeout, std::string *initial_message = nullptr); protected: - void open_() override; + template + UdpBlocking(Args &&...args) : UdpBase{std::forward(args)...} {} }; -/** - * @brief A udp that is permanently connected to a specific remote address. - * Messages that are sent to this udp from different remote peer are ignored. - * Attention!! Differently from tcp connections, udp socket are not connection - * oriented. - * The attribute "connected" for this socket simply means that messages - * incoming from udp sockets different from the remote address are filtered out. - * At the same time, the remote address might also not exists at all. - */ -class UdpConnected : public NonCopiable, - public Sender, - public Receiver, - public PortToBindAware, - public RemoteAddressAware, - public Openable { +class UdpNonBlocking : public UdpBase, public ReceiverUnkownSender { public: - UdpConnected(UdpConnected &&o); - UdpConnected &operator=(UdpConnected &&o); + /** + * @brief Connects the udp socket to the specified remote address. + * This leads to transfer the ownership of the underlying socket to the + * returned object while this socket is left empty and closed. + * @param remote_address the address to use for connecting the socket + * @return a socket connected to the passed remote address + */ + UdpConnected connect(const Address &remote_address); /** - * @brief The connection to the remote address is not done in this c'tor which - * simply initialize this object. Such a thing is done when - * when opening this socket. - * @param remote_address remote address of the peer - * @param port the port to reserve by this udp + * @brief similar to connect(const Address &). Here, the remote address is not + * explicitly specified, but is assumed to be equal to the first socket + * sending a message to this one. The first sent message is not lost, and can + * be moved into initial_message if not set to nullptr. + * Notice that this is a non blocking operation, meaning that if no message + * was sent to this socket before calling this method, a nullopt is + * immediately returned. + * @param initial_message the initial message sent from the remote peer to + * detect its address. */ - UdpConnected(const Address &remote_address, Port port = ANY_PORT); + std::optional> + connect(std::string *initial_message = nullptr); + +protected: + template + UdpNonBlocking(Args &&...args) : UdpBase{std::forward(args)...} {} +}; +template class Udp_ {}; +template <> class Udp_ : public UdpBlocking { +protected: + template + Udp_(Args &&...args) : UdpBlocking{std::forward(args)...} {} +}; +template <> class Udp_ : public UdpNonBlocking { +protected: + template + Udp_(Args &&...args) : UdpNonBlocking{std::forward(args)...} {} +}; + +/** + * @brief This kind of udp is agnostic of the remote address (which can also + * change over the time) of the socket(s) exchanging messages with this one. + * This udp can be reached by any other udp, on the port reserved when opening + * this socket. + * At the same time, this udp can send messages to any other udp + * sockets. + */ +template class Udp : public Udp_ { +public: /** - * @brief disconnect the underlying socket, generating an unbinded udp that - * reserves the same port reserved by this one. This leaves this onbject - * empty and closed. + * @brief After construction, the socket is left closed. + * The port is actually reserved after opening the socket. + * @param port_to_bind the port to reserve by this udp + * @param accepted_connection_family the kind of udp that can reach this one */ - UdpBinded disconnect(); + Udp(Port port_to_bind = ANY_PORT, + AddressFamily accepted_connection_family = AddressFamily::IP_V4) + : Udp_{port_to_bind, accepted_connection_family, BlockMode} {} + + Udp(Udp &&o) : Udp_{std::forward>(o)} {} + Udp &operator=(Udp &&o) { + this->stealBase(o); + return *this; + } +}; +class UdpConnectedBase : public NonCopiable, + public Sender, + public PortToBindAware, + public RemoteAddressAware, + public Openable { protected: + UdpConnectedBase(UdpConnectedBase &&o); + + void stealBase(UdpConnectedBase &o); + + UdpConnectedBase(const Address &remote_address, Port port, bool blockMode); + void open_() override; }; /** - * @brief builds from 0 a connected udp socket. The connection is done to the + * @brief A udp that is permanently "connected" to a specific remote address. + * Messages sent from senders with an address different from the specified + * remote ones are ignored. + * + * Attention!! Differently from tcp connections, udp + * socket are not connection oriented. The attribute "connected" for this socket + * simply means that the os filter out message incoming from peers different + * from the specified one. At the same time, the "connected" remote peer + * might also not exist at the time of opening or using the socket. + */ +template +class UdpConnected : public UdpConnectedBase, public Receiver { +public: + /** + * @brief After construction, the socket is left closed. + * The port is actually reserved after opening the socket. + * @param remote_address remote address of the peer + * @param port the port to reserve by this udp + */ + UdpConnected(const Address &remote_address, Port port_to_bind = ANY_PORT) + : UdpConnectedBase{remote_address, port_to_bind, BlockMode} {} + + UdpConnected(UdpConnected &&o) + : UdpConnectedBase{std::forward(o)} {} + UdpConnected &operator=(UdpConnected &&o) { + this->stealBase(o); + return *this; + } +}; + +/** + * @brief builds from zero a connected udp socket. The connection is done to the * first udp sending a message on the specified port. * This is a blocking operation. * @param port the port to reserve by the udp to build @@ -134,17 +200,8 @@ class UdpConnected : public NonCopiable, * @param initial_message the message sent from the remote peer to detect its * address */ -UdpConnected makeUdpConnectedToUnknown(Port port, - AddressFamily accepted_connection_family, - std::string *initial_message = nullptr); - -/** - * @brief non blocking version of makeUdpConnectedToUnknown(const Port &, const - * AddressFamily &, std::string *). In case no remote peer sends at least 1 byte - * within the timeout, a nullopt is returned. - */ -std::optional +UdpConnected makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, - const Timeout &timeout, std::string *initial_message = nullptr); + } // namespace MinimalSocket::udp diff --git a/src/src/SocketFunctions.cpp b/src/src/SocketFunctions.cpp index f51d6bdd..2a272ea0 100644 --- a/src/src/SocketFunctions.cpp +++ b/src/src/SocketFunctions.cpp @@ -11,11 +11,15 @@ #include "SocketFunctions.h" #include "Utils.h" +#if defined(__unix__) || defined(__APPLE__) +#include +#endif + namespace MinimalSocket { namespace { #ifdef _WIN32 #define REBIND_OPTION SO_REUSEADDR -#else +#elif defined(__unix__) || defined(__APPLE__) #define REBIND_OPTION SO_REUSEPORT #endif } // namespace @@ -133,4 +137,44 @@ void connect(SocketID socket_id, const Address &remote_address) { } }); } + +void turnToNonBlocking(SocketID socket_id) { +#ifdef _WIN32 + // https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ioctlsocket + u_long iMode = 1; + if (ioctlsocket(socket_id, FIONBIO, &iMode) != NO_ERROR) { + throw Error{"Unable to set up the non blocking mode"}; + } +#elif defined(__unix__) || defined(__APPLE__) + // https://jameshfisher.com/2017/04/05/set_socket_nonblocking/ + int flags = ::fcntl(socket_id, F_GETFL); + if (::fcntl(socket_id, F_SETFL, flags | O_NONBLOCK) == -1) { + throw Error{"Unable to set up the non blocking mode"}; + } +#endif +} + +int isTimeoutErrorCode(int code) { + return +#ifdef _WIN32 + code == WSAETIMEDOUT || code == WSAEWOULDBLOCK; +#elif defined(__unix__) + code == EAGAIN || code == EWOULDBLOCK; +#elif defined(__APPLE__) + code == EAGAIN; +#endif +} + +bool checkResult(int value, int errorValue, const std::string &error_msg, + bool canBeTimedOut) { + if (value != errorValue) { + return false; + } + SocketError maybe_error(error_msg); + if (canBeTimedOut && isTimeoutErrorCode(maybe_error.getErrorCode())) { + // just out of time: tolerable + return true; + } + throw maybe_error; +} } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/SocketFunctions.h b/src/src/SocketFunctions.h index eaed8e76..1586cfa2 100644 --- a/src/src/SocketFunctions.h +++ b/src/src/SocketFunctions.h @@ -17,4 +17,10 @@ Port bind(SocketID socket_id, AddressFamily family, Port port, void listen(SocketID socket_id, std::size_t backlog_size); void connect(SocketID socket_id, const Address &remote_address); + +void turnToNonBlocking(SocketID socket_id); + +// return true in case the timeout was reached +bool checkResult(int value, int errorValue, const std::string &error_msg, + bool canBeTimedOut); } // namespace MinimalSocket diff --git a/src/src/SocketHandler.cpp b/src/src/SocketHandler.cpp index 5e75091f..724582c4 100644 --- a/src/src/SocketHandler.cpp +++ b/src/src/SocketHandler.cpp @@ -10,6 +10,10 @@ #include "SocketHandler.h" #include "Utils.h" +#if defined(__unix__) || defined(__APPLE__) +#include +#endif + namespace MinimalSocket { #ifdef _WIN32 WSALazyInitializer::WSALazyInitializer(const WSAVersion &version) @@ -129,4 +133,5 @@ void SocketHandler::reset(SocketType type, AddressFamily family) { throw err; } } + } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/Address.cpp b/src/src/core/Address.cpp index a850b46d..a184d5de 100644 --- a/src/src/core/Address.cpp +++ b/src/src/core/Address.cpp @@ -27,7 +27,7 @@ Address::Address(const std::string &hostIp, Port port) return; } - this->host.clear(); + throw Error{'\'', hostIp, "' is an invalid host ip "}; } namespace { @@ -48,14 +48,6 @@ bool Address::operator==(const Address &o) const { (this->family == o.family); } -bool operator==(std::nullptr_t, const Address &subject) { - return subject.getHost().empty(); -} - -bool operator==(const Address &subject, std::nullptr_t) { - return subject.getHost().empty(); -} - std::string to_string(const Address &subject) { std::stringstream stream; stream << subject.getHost() << ':' << subject.getPort(); @@ -64,11 +56,13 @@ std::string to_string(const Address &subject) { std::optional deduceAddressFamily(const std::string &host_address) { - Address temp(host_address, 0); - if (nullptr == temp) { - return std::nullopt; + std::optional res; + try { + Address temp(host_address, 0); + res = temp.getFamily(); + } catch (const Error &) { } - return temp.getFamily(); + return res; } bool isValidAddress(const std::string &host_address) { diff --git a/src/src/core/Receiver.cpp b/src/src/core/Receiver.cpp index 6871c2e8..742e25e8 100644 --- a/src/src/core/Receiver.cpp +++ b/src/src/core/Receiver.cpp @@ -9,12 +9,13 @@ #include #include "../SocketAddress.h" +#include "../SocketFunctions.h" #ifndef _WIN32 #include #endif namespace MinimalSocket { -void ReceiverBase::updateTimeout_(const Timeout &timeout) { +void ReceiverWithTimeout::updateTimeout_(const Timeout &timeout) { if (timeout == receive_timeout) { return; } @@ -44,103 +45,154 @@ void ReceiverBase::updateTimeout_(const Timeout &timeout) { } } -namespace { -#ifdef _WIN32 -static constexpr int TIMEOUT_CODE = 10060; -#else -static constexpr int TIMEOUT_CODE = EAGAIN; -#endif - -void check_received_bytes(int &recvBytes, const Timeout &timeout) { - if (recvBytes != SCK_SOCKET_ERROR) { - return; - } - SocketError error_with_code("receive failed"); - recvBytes = 0; - if ((error_with_code.getErrorCode() == TIMEOUT_CODE) && - (timeout != NULL_TIMEOUT)) { - // just out of time: tolerable - return; +std::size_t ReceiverBlocking::receive(BufferView message, + const Timeout &timeout) { + std::scoped_lock lock{recv_mtx}; + updateTimeout_(timeout); + clear(message); + + int recvBytes = ::recv(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", + timeout != NULL_TIMEOUT) || + recvBytes > message.buffer_size) { + return 0; } - throw error_with_code; + return static_cast(recvBytes); } -} // namespace - -std::size_t Receiver::receive(BufferView message, const Timeout &timeout) { - std::size_t res = 0; - lazyUpdateAndUseTimeout( - timeout, [&message, &res, this](const Timeout &timeout) { - clear(message); +std::size_t ReceiverNonBlocking::receive(BufferView message) { + std::scoped_lock lock{recv_mtx}; + clear(message); - int recvBytes = ::recv(getHandler().accessId(), message.buffer, - static_cast(message.buffer_size), 0); - check_received_bytes(recvBytes, timeout); - if (recvBytes > message.buffer_size) { - // if here, the message received is probably corrupted - recvBytes = 0; - } - res = static_cast(recvBytes); - }); - - return res; + int recvBytes = ::recv(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", true) || + recvBytes > message.buffer_size) { + return 0; + } + return static_cast(recvBytes); } -std::string Receiver::receive(std::size_t expected_max_bytes, - const Timeout &timeout) { +namespace { +template +std::string receive_into_string(std::size_t expected_max_bytes, Pred pred, + const Args &...args) { std::string buffer; buffer.resize(expected_max_bytes); auto buffer_temp = makeBufferView(buffer); - auto recvBytes = receive(buffer_temp, timeout); + auto recvBytes = pred(buffer_temp, args...); buffer.resize(recvBytes); return buffer; } +} // namespace + +std::string ReceiverBlocking::receive(std::size_t expected_max_bytes, + const Timeout &timeout) { + return receive_into_string( + expected_max_bytes, + [this](BufferView message, const Timeout &timeout) { + return this->receive(message, timeout); + }, + timeout); +} -std::optional -ReceiverUnkownSender::receive(BufferView message, const Timeout &timeout) { - std::optional res; - - lazyUpdateAndUseTimeout( - timeout, [&message, &res, this](const Timeout &timeout) { - clear(message); - - char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; - SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; - - int recvBytes = - ::recvfrom(getHandler().accessId(), message.buffer, - static_cast(message.buffer_size), 0, - reinterpret_cast(&sender_address[0]), - &sender_address_length); - check_received_bytes(recvBytes, timeout); - if (recvBytes > message.buffer_size) { - // if here, the message received is probably corrupted - return; - } - if (0 == recvBytes) { - // if here, timeout was reached - return; - } - - res = ReceiveResult{ - toAddress(reinterpret_cast(sender_address)), - static_cast(recvBytes)}; - }); +std::string ReceiverNonBlocking::receive(std::size_t expected_max_bytes) { + return receive_into_string(expected_max_bytes, [this](BufferView message) { + return this->receive(message); + }); +} + +std::optional +ReceiverUnkownSenderBlocking::receive(BufferView message, + const Timeout &timeout) { + std::scoped_lock lock{recv_mtx}; + updateTimeout_(timeout); + clear(message); + + std::optional res; + + char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; + + int recvBytes = + ::recvfrom(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&sender_address[0]), + &sender_address_length); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", + timeout != NULL_TIMEOUT) || + recvBytes > message.buffer_size) { + return std::nullopt; + } + + res = ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; return res; } -std::optional -ReceiverUnkownSender::receive(std::size_t expected_max_bytes, - const Timeout &timeout) { +std::optional +ReceiverUnkownSenderNonBlocking::receive(BufferView message) { + std::scoped_lock lock{recv_mtx}; + clear(message); + + std::optional res; + + char sender_address[MAX_POSSIBLE_ADDRESS_SIZE]; + SocketAddressLength sender_address_length = MAX_POSSIBLE_ADDRESS_SIZE; + + int recvBytes = + ::recvfrom(getHandler().accessId(), message.buffer, + static_cast(message.buffer_size), 0, + reinterpret_cast(&sender_address[0]), + &sender_address_length); + if (checkResult(recvBytes, SCK_SOCKET_ERROR, "receive failed", true) || + recvBytes > message.buffer_size) { + return std::nullopt; + } + + res = ReceiveResult{ + toAddress(reinterpret_cast(sender_address)), + static_cast(recvBytes)}; + + return res; +} + +namespace { +template +std::optional +receive_unknown_into_string(std::size_t expected_max_bytes, Pred pred, + const Args &...args) { std::string buffer; buffer.resize(expected_max_bytes); auto buffer_temp = makeBufferView(buffer); - auto result = receive(buffer_temp, timeout); + auto result = pred(buffer_temp, args...); if (!result) { return std::nullopt; } buffer.resize(result->received_bytes); return ReceiveStringResult{std::move(result->sender), std::move(buffer)}; } +} // namespace + +std::optional +ReceiverUnkownSenderBlocking::receive(std::size_t expected_max_bytes, + const Timeout &timeout) { + return receive_unknown_into_string( + expected_max_bytes, + [this](BufferView message, const Timeout &timeout) { + return this->receive(message, timeout); + }, + timeout); +} + +std::optional +ReceiverUnkownSenderNonBlocking::receive(std::size_t expected_max_bytes) { + return receive_unknown_into_string( + expected_max_bytes, + [this](BufferView message) { return this->receive(message); }); +} + } // namespace MinimalSocket diff --git a/src/src/core/Sender.cpp b/src/src/core/Sender.cpp index 80ec5eab..ef5c2c31 100644 --- a/src/src/core/Sender.cpp +++ b/src/src/core/Sender.cpp @@ -9,6 +9,7 @@ #include #include "../SocketAddress.h" +#include "../SocketFunctions.h" #include "../Utils.h" namespace MinimalSocket { @@ -16,11 +17,10 @@ bool Sender::send(const BufferViewConst &message) { std::scoped_lock lock(send_mtx); int sentBytes = ::send(getHandler().accessId(), message.buffer, static_cast(message.buffer_size), 0); - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - throw SocketError{"send failed"}; + if (checkResult(sentBytes, SCK_SOCKET_ERROR, "send failed", !isBlocking())) { + return false; } - return (sentBytes == static_cast(message.buffer_size)); + return true; } bool Sender::send(const std::string &message) { @@ -62,12 +62,10 @@ bool SenderTo::sendTo(const BufferViewConst &message, sizeof(SocketAddressIpv6)); }); } - if (sentBytes == SCK_SOCKET_ERROR) { - sentBytes = 0; - auto err = SocketError{"sendto failed"}; - throw err; + if (checkResult(sentBytes, SCK_SOCKET_ERROR, "send failed", !isBlocking())) { + return false; } - return (sentBytes == static_cast(message.buffer_size)); + return true; } bool SenderTo::sendTo(const std::string &message, const Address &recipient) { diff --git a/src/src/core/Socket.cpp b/src/src/core/Socket.cpp index 37155b74..50678045 100644 --- a/src/src/core/Socket.cpp +++ b/src/src/core/Socket.cpp @@ -8,6 +8,7 @@ #include #include +#include "../SocketFunctions.h" #include "../SocketHandler.h" #include "../Utils.h" @@ -38,6 +39,7 @@ int Socket::getSocketDescriptor() const { void Socket::steal(Socket &giver) { this->socket_id_wrapper = std::move(giver.socket_id_wrapper); giver.resetHandler(); + isBlocking_ = giver.isBlocking_; } const SocketHandler &Socket::getHandler() const { return *socket_id_wrapper; } @@ -47,19 +49,31 @@ void Socket::resetHandler() { socket_id_wrapper = std::make_unique(); } -bool Openable::open(const Timeout &timeout) { +void Socket::setUp() { + if (!isBlocking_) { + turnToNonBlocking(getHandler().accessId()); + } +} + +void OpenableBase::steal(OpenableBase &giver) { + std::scoped_lock lock(this->open_procedure_mtx, giver.open_procedure_mtx); + const bool o_value = giver.opened; + this->opened = o_value; + giver.opened = false; + this->Socket::steal(giver); +} + +namespace { +template +std::unique_ptr doOpen(std::mutex &mtx, std::atomic_bool &opened, + Pred pred) { if (opened) { throw Error{"Already opened"}; } - std::scoped_lock lock(open_procedure_mtx); + std::scoped_lock lock(mtx); std::unique_ptr exception; try { - if (NULL_TIMEOUT == timeout) { - this->open_(); - } else { - try_within_timeout([this]() { this->open_(); }, - [this]() { this->resetHandler(); }, timeout); - } + pred(); opened = true; } catch (const SocketError &e) { exception = std::make_unique(e); @@ -70,19 +84,33 @@ bool Openable::open(const Timeout &timeout) { } catch (...) { exception = std::make_unique("Not opened for an unknown reason"); } - if (nullptr != exception) { - this->resetHandler(); - throw *exception; - } + return exception; +} +} // namespace + +bool Openable::open() { + auto excpt = doOpen(open_procedure_mtx, opened, [this]() { this->open_(); }); + if (excpt != nullptr) { + resetHandler(); + throw *excpt; + }; return opened; } -void Openable::steal(Openable &giver) { - std::scoped_lock lock(this->open_procedure_mtx, giver.open_procedure_mtx); - const bool o_value = giver.opened; - this->opened = o_value; - giver.opened = false; - this->Socket::steal(giver); +bool OpenableWithTimeout::open(const Timeout &timeout) { + auto excpt = doOpen(open_procedure_mtx, opened, [&]() { + if (NULL_TIMEOUT == timeout) { + this->open_(); + } else { + try_within_timeout([this]() { this->open_(); }, + [this]() { this->resetHandler(); }, timeout); + } + }); + if (excpt != nullptr) { + resetHandler(); + throw *excpt; + }; + return opened; } } // namespace MinimalSocket \ No newline at end of file diff --git a/src/src/core/SocketContext.cpp b/src/src/core/SocketContext.cpp index 6f016a2a..4b84def0 100644 --- a/src/src/core/SocketContext.cpp +++ b/src/src/core/SocketContext.cpp @@ -8,6 +8,9 @@ #include #include +#include "../SocketFunctions.h" +#include "../SocketHandler.h" + namespace MinimalSocket { Address RemoteAddressAware::getRemoteAddress() const { std::scoped_lock lock(remote_address_mtx); @@ -15,10 +18,5 @@ Address RemoteAddressAware::getRemoteAddress() const { } RemoteAddressAware::RemoteAddressAware(const Address &address) - : remote_address(address) { - if (nullptr == getRemoteAddress()) { - throw Error{"Invalid address"}; - } -} - + : remote_address(address) {} } // namespace MinimalSocket diff --git a/src/src/tcp/TcpClient.cpp b/src/src/tcp/TcpClient.cpp index 740e17df..91472d0a 100644 --- a/src/src/tcp/TcpClient.cpp +++ b/src/src/tcp/TcpClient.cpp @@ -12,22 +12,27 @@ #include "../Utils.h" namespace MinimalSocket::tcp { -TcpClient::TcpClient(TcpClient &&o) : RemoteAddressAware(o) { this->steal(o); } -TcpClient &TcpClient::operator=(TcpClient &&o) { +TcpClientBase::TcpClientBase(TcpClientBase &&o) : RemoteAddressAware(o) { + this->steal(o); +} + +void TcpClientBase::stealBase(TcpClientBase &o) { this->steal(o); copy_as(*this, o); - return *this; } -TcpClient::TcpClient(const Address &server_address) - : RemoteAddressAware(server_address) {} +TcpClientBase::TcpClientBase(const Address &server_address, bool block_mode) + : RemoteAddressAware(server_address) { + if (!block_mode) { + setNonBlocking(); + } +} -void TcpClient::open_() { +void TcpClientBase::open_() { auto &socket = getHandler(); const auto remote_address = getRemoteAddress(); socket.reset(SocketType::TCP, remote_address.getFamily()); MinimalSocket::connect(socket.accessId(), remote_address); + this->Socket::setUp(); } - -TcpClient clone(const TcpClient &o) { return TcpClient{o.getRemoteAddress()}; } } // namespace MinimalSocket::tcp diff --git a/src/src/tcp/TcpServer.cpp b/src/src/tcp/TcpServer.cpp index cb22217e..2a8cee89 100644 --- a/src/src/tcp/TcpServer.cpp +++ b/src/src/tcp/TcpServer.cpp @@ -13,22 +13,66 @@ #include "../Utils.h" namespace MinimalSocket::tcp { -TcpServer::TcpServer(TcpServer &&o) +TcpConnectionBlocking::TcpConnectionBlocking(const Address &remote_address) + : RemoteAddressAware{remote_address} {} + +TcpConnectionBlocking::TcpConnectionBlocking(TcpConnectionBlocking &&o) + : RemoteAddressAware(o) { + this->steal(o); +} + +TcpConnectionBlocking & +TcpConnectionBlocking::operator=(TcpConnectionBlocking &&o) { + this->steal(o); + copy_as(*this, o); + return *this; +} + +TcpConnectionNonBlocking TcpConnectionBlocking::turnToNonBlocking() { + return TcpConnectionNonBlocking{std::move(*this)}; +} + +TcpConnectionNonBlocking::TcpConnectionNonBlocking(TcpConnectionNonBlocking &&o) + : RemoteAddressAware{o} { + this->steal(o); +} + +TcpConnectionNonBlocking & +TcpConnectionNonBlocking::operator=(TcpConnectionNonBlocking &&o) { + this->steal(o); + copy_as(*this, o); + return *this; +} + +TcpConnectionNonBlocking::TcpConnectionNonBlocking( + TcpConnectionBlocking &&connection) + : RemoteAddressAware{connection} { + this->steal(connection); + turnToNonBlocking(getHandler().accessId()); +} + +TcpServerBase::TcpServerBase(TcpServerBase &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { this->steal(o); } -TcpServer &TcpServer::operator=(TcpServer &&o) { + +void TcpServerBase::stealBase(TcpServerBase &o) { this->steal(o); copy_as(*this, o); copy_as(*this, o); - return *this; } -TcpServer::TcpServer(Port port_to_bind, AddressFamily accepted_client_family) +TcpServerBase::TcpServerBase(Port port_to_bind, + AddressFamily accepted_client_family, + bool block_mode) : PortToBindAware(port_to_bind), - RemoteAddressFamilyAware(accepted_client_family) {} + RemoteAddressFamilyAware(accepted_client_family) { + if (!block_mode) { + setNonBlocking(); + } +} -void TcpServer::open_() { +void TcpServerBase::open_() { auto &socket = getHandler(); const auto port = getPortToBind(); const auto family = getRemoteAddressFamily(); @@ -36,79 +80,71 @@ void TcpServer::open_() { auto binded_port = MinimalSocket::bind(socket.accessId(), family, port, shallBeFreePort()); setPort(binded_port); + this->Socket::setUp(); MinimalSocket::listen(socket.accessId(), client_queue_size); } -void TcpServer::setClientQueueSize(const std::size_t queue_size) { +void TcpServerBase::setClientQueueSize(const std::size_t queue_size) { if (wasOpened()) { throw Error{"Can't set client queue size of an alrady opened tcp server"}; } client_queue_size = queue_size; } -TcpConnection TcpServer::acceptNewClient() { - auto temp = acceptNewClient(NULL_TIMEOUT); - return std::move(temp.value()); +struct TcpServerBase::AcceptedSocket { + SocketID fd = SCK_INVALID_SOCKET; + SocketAddressLength address_length = MAX_POSSIBLE_ADDRESS_SIZE; + char address[MAX_POSSIBLE_ADDRESS_SIZE]; +}; + +void TcpServerBase::acceptClient_(AcceptedSocket &recipient) { + auto &[accepted_client_socket_id, acceptedClientAddress_length, + acceptedClientAddress] = recipient; + + // accept: wait for a client to call connect and hit this server and get a + // pointer to this client. + accepted_client_socket_id = + ::accept(getHandler().accessId(), + reinterpret_cast(&acceptedClientAddress[0]), + &acceptedClientAddress_length); + checkResult(static_cast(accepted_client_socket_id), SCK_INVALID_SOCKET, + "accepting a new client", !isBlocking()); +} + +TcpConnectionBlocking +TcpServerBase::makeClient(const AcceptedSocket &acceptedSocket) { + auto accepted_client_parsed_address = toAddress( + reinterpret_cast(acceptedSocket.address)); + TcpConnectionBlocking result{accepted_client_parsed_address}; + result.getHandler().reset(acceptedSocket.fd); + return result; } -std::optional -TcpServer::acceptNewClient(const Timeout &timeout) { +TcpConnectionBlocking AcceptorBlocking::acceptNewClient() { std::scoped_lock lock(accept_mtx); if (!this->wasOpened()) { throw Error("Tcp server was not opened before starting to accept clients"); } - char acceptedClientAddress[MAX_POSSIBLE_ADDRESS_SIZE]; - SocketAddressLength acceptedClientAddress_length = MAX_POSSIBLE_ADDRESS_SIZE; - SocketID accepted_client_socket_id = SCK_INVALID_SOCKET; - - auto accept_client = [&]() { - // accept: wait for a client to call connect and hit this server and get a - // pointer to this client. - accepted_client_socket_id = - ::accept(getHandler().accessId(), - reinterpret_cast(&acceptedClientAddress[0]), - &acceptedClientAddress_length); - if (accepted_client_socket_id == SCK_INVALID_SOCKET) { - auto err = SocketError{"accepting a new client"}; - throw err; - } - }; - - try { - if (NULL_TIMEOUT == timeout) { - accept_client(); - } else { - try_within_timeout([&]() { accept_client(); }, - [this]() { this->resetHandler(); }, timeout); - } - } catch (const TimeOutError &) { - TcpServer reopened = TcpServer{getPortToBind(), getRemoteAddressFamily()}; - reopened.open(); - *this = std::move(reopened); - return std::nullopt; - } catch (...) { - std::rethrow_exception(std::current_exception()); - } + AcceptedSocket acceptedSocket; + acceptClient_(acceptedSocket); - auto accepted_client_parsed_address = - toAddress(reinterpret_cast(acceptedClientAddress)); - std::optional result; - auto &accepted = - result.emplace(TcpConnection{accepted_client_parsed_address}); - accepted.getHandler().reset(accepted_client_socket_id); - return result; + return makeClient(acceptedSocket); } -TcpConnection::TcpConnection(const Address &remote_address) - : RemoteAddressAware(remote_address) {} +std::optional AcceptorNonBlocking::acceptNewClient() { + std::scoped_lock lock(accept_mtx); + if (!this->wasOpened()) { + throw Error("Tcp server was not opened before starting to accept clients"); + } -TcpConnection::TcpConnection(TcpConnection &&o) : RemoteAddressAware(o) { - this->steal(o); -} -TcpConnection &TcpConnection::operator=(TcpConnection &&o) { - copy_as(*this, o); - this->steal(o); - return *this; + AcceptedSocket acceptedSocket; + acceptClient_(acceptedSocket); + + if (acceptedSocket.fd == SCK_INVALID_SOCKET) { + return std::nullopt; + } + return makeClient(acceptedSocket); } + } // namespace MinimalSocket::tcp diff --git a/src/src/udp/UdpSocket.cpp b/src/src/udp/UdpSocket.cpp index 91ddfe78..ee654a47 100644 --- a/src/src/udp/UdpSocket.cpp +++ b/src/src/udp/UdpSocket.cpp @@ -12,49 +12,54 @@ #include "../Utils.h" namespace MinimalSocket::udp { -UdpBinded::UdpBinded(Port port_to_bind, - AddressFamily accepted_connection_family) +UdpBase::UdpBase(Port port_to_bind, AddressFamily accepted_connection_family, + bool blockMode) : PortToBindAware(port_to_bind), - RemoteAddressFamilyAware(accepted_connection_family) {} + RemoteAddressFamilyAware(accepted_connection_family) { + if (!blockMode) { + setNonBlocking(); + } +} -UdpBinded::UdpBinded(UdpBinded &&o) +UdpBase::UdpBase(UdpBase &&o) : PortToBindAware(o), RemoteAddressFamilyAware(o) { this->steal(o); } -UdpBinded &UdpBinded::operator=(UdpBinded &&o) { + +void UdpBase::stealBase(UdpBase &o) { copy_as(*this, o); copy_as(*this, o); this->steal(o); - return *this; } -void UdpBinded::open_() { +void UdpBase::open_() { getHandler().reset(SocketType::UDP, getRemoteAddressFamily()); auto binded_port = MinimalSocket::bind(getHandler().accessId(), getRemoteAddressFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); + this->Socket::setUp(); } -UdpConnected UdpBinded::connect(const Address &remote_address) { +UdpConnected UdpBlocking::connect(const Address &remote_address) { if (remote_address.getFamily() != getRemoteAddressFamily()) { throw Error{"Passed address has invalid family"}; } - UdpConnected result(remote_address, getPortToBind()); + UdpConnected result(remote_address, getPortToBind()); if (wasOpened()) { MinimalSocket::connect(getHandler().accessId(), remote_address); } this->transfer(result); - return std::move(result); + return result; } -UdpConnected UdpBinded::connect(std::string *initial_message) { +UdpConnected UdpBlocking::connect(std::string *initial_message) { auto result = this->connect(NULL_TIMEOUT, initial_message); return std::move(result.value()); } -std::optional UdpBinded::connect(const Timeout &timeout, - std::string *initial_message) { +std::optional> +UdpBlocking::connect(const Timeout &timeout, std::string *initial_message) { auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE, timeout); if (!maybe_received) { return std::nullopt; @@ -65,54 +70,69 @@ std::optional UdpBinded::connect(const Timeout &timeout, return connect(maybe_received->sender); } -UdpConnected::UdpConnected(const Address &remote_address, Port port) - : PortToBindAware(port), RemoteAddressAware(remote_address) {} +UdpConnected UdpNonBlocking::connect(const Address &remote_address) { + if (remote_address.getFamily() != getRemoteAddressFamily()) { + throw Error{"Passed address has invalid family"}; + } + UdpConnected result(remote_address, getPortToBind()); + if (wasOpened()) { + MinimalSocket::connect(getHandler().accessId(), remote_address); + } + this->transfer(result); + return result; +} -UdpConnected::UdpConnected(UdpConnected &&o) +std::optional> +UdpNonBlocking::connect(std::string *initial_message) { + auto maybe_received = this->receive(MAX_UDP_RECV_MESSAGE); + if (!maybe_received) { + return std::nullopt; + } + if (nullptr != initial_message) { + *initial_message = std::move(maybe_received->received_message); + } + return connect(maybe_received->sender); +} + +UdpConnectedBase::UdpConnectedBase(const Address &remote_address, Port port, + bool blockMode) + : PortToBindAware(port), RemoteAddressAware(remote_address) { + if (!blockMode) { + setNonBlocking(); + } +} + +UdpConnectedBase::UdpConnectedBase(UdpConnectedBase &&o) : PortToBindAware(o), RemoteAddressAware(o) { this->steal(o); } -UdpConnected &UdpConnected::operator=(UdpConnected &&o) { + +void UdpConnectedBase::stealBase(UdpConnectedBase &o) { copy_as(*this, o); copy_as(*this, o); this->steal(o); - return *this; } -void UdpConnected::open_() { +void UdpConnectedBase::open_() { const auto &remote_address = getRemoteAddress(); getHandler().reset(SocketType::UDP, remote_address.getFamily()); auto socket_id = getHandler().accessId(); auto binded_port = MinimalSocket::bind(socket_id, remote_address.getFamily(), getPortToBind(), shallBeFreePort()); setPort(binded_port); + this->Socket::setUp(); MinimalSocket::connect(socket_id, remote_address); } -UdpBinded UdpConnected::disconnect() { - resetHandler(); - UdpBinded result(getPortToBind(), getRemoteAddress().getFamily()); - result.open(); - return std::move(result); -} - -UdpConnected makeUdpConnectedToUnknown(Port port, - AddressFamily accepted_connection_family, - std::string *initial_message) { - auto result = makeUdpConnectedToUnknown(port, accepted_connection_family, - NULL_TIMEOUT, initial_message); - return std::move(result.value()); -} - -std::optional +UdpConnected makeUdpConnectedToUnknown(Port port, AddressFamily accepted_connection_family, - const Timeout &timeout, std::string *initial_message) { - UdpBinded primal_socket(port, accepted_connection_family); + Udp primal_socket(port, accepted_connection_family); auto success = primal_socket.open(); if (!success) { - return std::nullopt; + throw Error{"Unable to open the primal upd socket"}; } - return primal_socket.connect(timeout, initial_message); + return primal_socket.connect(initial_message); } + } // namespace MinimalSocket::udp diff --git a/tests/ConnectionsUtils.cpp b/tests/ConnectionsUtils.cpp index e6dfdaf5..21ebd868 100644 --- a/tests/ConnectionsUtils.cpp +++ b/tests/ConnectionsUtils.cpp @@ -16,11 +16,11 @@ TcpPeers::TcpPeers(const Port &port, const AddressFamily &family) ParallelSection::biSection( [&](Barrier &br) { // server - tcp::TcpServer server(port, family); + tcp::TcpServer server(port, family); REQUIRE(server.open()); br.arrive_and_wait(); auto accepted = server.acceptNewClient(); - server_side = std::make_unique(std::move(accepted)); + server_side.emplace(std::move(accepted)); }, [&](Barrier &br) { // client @@ -29,10 +29,62 @@ TcpPeers::TcpPeers(const Port &port, const AddressFamily &family) }); } -UdpPeers::UdpPeers(const Port &port_a, const Port &port_b, - const AddressFamily &family) +template <> +UdpPeers>::UdpPeers(const Port &port_a, const Port &port_b, + const AddressFamily &family) : peer_a(port_a, family), peer_b(port_b, family) { REQUIRE(peer_a.open()); REQUIRE(peer_b.open()); } + +template <> +Address +UdpPeers>::extractRemoteAddress(const udp::Udp &subject) { + return Address{subject.getPortToBind(), subject.getRemoteAddressFamily()}; +} + +template <> +UdpPeers>::UdpPeers(const Port &port_a, const Port &port_b, + const AddressFamily &family) + : peer_a(port_a, family), peer_b(port_b, family) { + REQUIRE(peer_a.open()); + REQUIRE(peer_b.open()); +} + +template <> +Address UdpPeers>::extractRemoteAddress( + const udp::Udp &subject) { + return Address{subject.getPortToBind(), subject.getRemoteAddressFamily()}; +} + +template <> +UdpPeers>::UdpPeers(const Port &port_a, + const Port &port_b, + const AddressFamily &family) + : peer_a(Address{port_b, family}, port_a), + peer_b(Address{port_a, family}, port_b) { + REQUIRE(peer_a.open()); + REQUIRE(peer_b.open()); +} +template <> +Address UdpPeers>::extractRemoteAddress( + const udp::UdpConnected &subject) { + return subject.getRemoteAddress(); +} + +template <> +UdpPeers>::UdpPeers(const Port &port_a, + const Port &port_b, + const AddressFamily &family) + : peer_a(Address{port_b, family}, port_a), + peer_b(Address{port_a, family}, port_b) { + REQUIRE(peer_a.open()); + REQUIRE(peer_b.open()); +} +template <> +Address UdpPeers>::extractRemoteAddress( + const udp::UdpConnected &subject) { + return subject.getRemoteAddress(); +} + } // namespace MinimalSocket::test diff --git a/tests/ConnectionsUtils.h b/tests/ConnectionsUtils.h index 4204f3a5..361e0883 100644 --- a/tests/ConnectionsUtils.h +++ b/tests/ConnectionsUtils.h @@ -11,42 +11,45 @@ #include #include +#include +#include + namespace MinimalSocket::test { class TcpPeers { public: TcpPeers(const Port &port, const AddressFamily &family); - tcp::TcpConnection &getServerSide() { return *server_side; } - tcp::TcpClient &getClientSide() { return client_side; } + std::pair *> get() { + return std::make_pair(&server_side.value(), &client_side); + } private: - std::unique_ptr server_side; - tcp::TcpClient client_side; + std::optional server_side; + tcp::TcpClient client_side; }; -class UdpPeers { +template class UdpPeers { public: UdpPeers(const Port &port_a, const Port &port_b, const AddressFamily &family); - udp::UdpBinded &getPeerA() { return peer_a; } - udp::UdpBinded &getPeerB() { return peer_b; } - - Address addressPeerA() const { - return Address{peer_a.getPortToBind(), peer_a.getRemoteAddressFamily()}; - }; - Address addressPeerB() const { - return Address{peer_b.getPortToBind(), peer_b.getRemoteAddressFamily()}; - }; + std::tuple get() { + return std::make_tuple(&peer_a, extractRemoteAddress(peer_a), &peer_b, + extractRemoteAddress(peer_b)); + } private: - udp::UdpBinded peer_a; - udp::UdpBinded peer_b; + static Address extractRemoteAddress(const Udp_ &subject); + + Udp_ peer_a; + Udp_ peer_b; }; -#define UDP_PEERS(PORT_A, PORT_B, FAMILY) \ - UdpPeers peers(PORT_A, PORT_B, FAMILY); \ - auto &requester = peers.getPeerA(); \ - const auto requester_address = peers.addressPeerA(); \ - auto &responder = peers.getPeerB(); \ - const auto responder_address = peers.addressPeerB(); +#define UDP_PEERS(TYPE, FAMILY) \ + UdpPeers peers{PortFactory::get().makePort(), \ + PortFactory::get().makePort(), family}; \ + auto tmp = peers.get(); \ + auto *requester = std::get<0>(tmp); \ + auto &requester_address = std::get<1>(tmp); \ + auto *responder = std::get<2>(tmp); \ + auto &responder_address = std::get<3>(tmp); } // namespace MinimalSocket::test diff --git a/tests/ParallelSection.cpp b/tests/ParallelSection.cpp index 64002b86..04f40655 100644 --- a/tests/ParallelSection.cpp +++ b/tests/ParallelSection.cpp @@ -6,11 +6,13 @@ **/ #include "ParallelSection.h" + #include +#include namespace MinimalSocket::test { namespace { -std::function make_thread(Barrier &br, const Task &task) { +std::function make_task(Barrier &br, const Task &task) { return [&task = task, &br = br]() mutable { br.arrive_and_wait(); task(br); @@ -22,12 +24,12 @@ void ParallelSection::run() { if (tasks.size() < 2) { throw std::runtime_error{"invalid number of tasks for parallel region"}; } - barrier.emplace(tasks.size()); + auto &br = barrier.emplace(tasks.size()); std::vector spinners; - for (auto it = tasks.begin(); it != tasks.end() - 1; ++it) { - spinners.emplace_back(make_thread(barrier.value(), *it)); - } - spinners.emplace_back(make_thread(barrier.value(), tasks.back())); + std::for_each(tasks.begin() + 1, tasks.end(), [&](const Task &t) { + spinners.emplace_back(make_task(br, t)); + }); + make_task(br, tasks.front())(); for (auto &sp : spinners) { sp.join(); } diff --git a/tests/PortFactory.cpp b/tests/PortFactory.cpp index ad8eead1..9c9971ad 100644 --- a/tests/PortFactory.cpp +++ b/tests/PortFactory.cpp @@ -8,18 +8,22 @@ #include "PortFactory.h" namespace MinimalSocket::test { +PortFactory &PortFactory::get() { + static PortFactory res = PortFactory{}; + return res; +} + namespace { static constexpr std::uint16_t INITIAL_PORT = 9999; static constexpr std::uint16_t DELTA_PORT = 10; } // namespace -std::mutex PortFactory::port_mtx = std::mutex{}; -Port PortFactory::port = INITIAL_PORT; - Port PortFactory::makePort() { std::lock_guard lock(port_mtx); auto result = port; port += DELTA_PORT; return result; } + +PortFactory::PortFactory() : port{INITIAL_PORT} {} } // namespace MinimalSocket::test diff --git a/tests/PortFactory.h b/tests/PortFactory.h index 3d50d126..3fdec065 100644 --- a/tests/PortFactory.h +++ b/tests/PortFactory.h @@ -13,10 +13,14 @@ namespace MinimalSocket::test { class PortFactory { public: - static Port makePort(); + static PortFactory &get(); + + Port makePort(); private: - static std::mutex port_mtx; - static Port port; + PortFactory(); + + std::mutex port_mtx; + Port port; }; } // namespace MinimalSocket::test diff --git a/tests/RollingView.cpp b/tests/RollingView.cpp new file mode 100644 index 00000000..6a0900e4 --- /dev/null +++ b/tests/RollingView.cpp @@ -0,0 +1,56 @@ +#include "RollingView.h" + +namespace MinimalSocket::test { +RollingView::RollingView(const std::string &buff) : buffer{buff} {} + +RollingView::RollingView(std::size_t buff_size) { buffer.resize(buff_size); } + +void sliced_send(Sender &subject, const std::string &to_send, + std::size_t delta_send) { + RollingView buff{to_send}; + buff.forEachView(delta_send, [&](const std::string_view &view) { + bool ok = subject.send(BufferViewConst{view.data(), view.size()}); + if (!ok) { + throw std::runtime_error{"wasn't able to send all data"}; + } + return delta_send; + }); +} + +void sliced_send(SenderTo &subject, const std::string &to_send, + const Address &to_send_address, std::size_t delta_send) { + RollingView buff{to_send}; + buff.forEachView(delta_send, [&](const std::string_view &view) { + bool ok = subject.sendTo(BufferViewConst{view.data(), view.size()}, + to_send_address); + if (!ok) { + throw std::runtime_error{"wasn't able to send all data"}; + } + return delta_send; + }); +} + +std::string sliced_receive(Receiver &subject, std::size_t to_receive, + std::size_t delta_receive) { + RollingView buff{to_receive}; + buff.forEachView(delta_receive, [&](const std::string_view &view) { + BufferView buff{const_cast(view.data()), view.size()}; + return subject.receive(buff); + }); + return buff.getBuffer(); +} + +std::string sliced_receive(ReceiverUnkownSender &subject, + std::size_t to_receive, std::size_t delta_receive) { + RollingView buff{to_receive}; + buff.forEachView(delta_receive, [&](const std::string_view &view) { + BufferView buff{const_cast(view.data()), view.size()}; + auto maybe_bytes_received = subject.receive(buff); + if (!maybe_bytes_received.has_value()) { + throw std::runtime_error{"wasn'table tor receive the data"}; + } + return maybe_bytes_received->received_bytes; + }); + return buff.getBuffer(); +} +} // namespace MinimalSocket::test diff --git a/tests/RollingView.h b/tests/RollingView.h new file mode 100644 index 00000000..ae698b64 --- /dev/null +++ b/tests/RollingView.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include +#include + +namespace MinimalSocket::test { +class RollingView { +public: + RollingView(const std::string &buff); + RollingView(std::size_t buff_size); + + template + void forEachView(std::size_t offset, Pred pred) const { + std::size_t view_begin = 0; + std::size_t view_end = std::min(offset, buffer.size()); + while (true) { + std::size_t processed = pred( + std::string_view{buffer.data() + view_begin, view_end - view_begin}); + if (processed != offset) { + throw std::runtime_error{"Wrong number of bytes were processed"}; + } + if (view_end == buffer.size()) { + break; + } + view_begin = view_end; + view_end += offset; + view_end = std::min(view_end, buffer.size()); + } + } + + const auto &getBuffer() const { return buffer; } + +private: + std::string buffer; +}; + +void sliced_send(Sender &subject, const std::string &to_send, + std::size_t delta_send); + +void sliced_send(SenderTo &subject, const std::string &to_send, + const Address &to_send_address, std::size_t delta_send); + +std::string sliced_receive(Receiver &subject, std::size_t to_receive, + std::size_t delta_receive); + +std::string sliced_receive(ReceiverUnkownSender &subject, + std::size_t to_receive, std::size_t delta_receive); +} // namespace MinimalSocket::test diff --git a/tests/SlicedOps.cpp b/tests/SlicedOps.cpp deleted file mode 100644 index 2ae73b1a..00000000 --- a/tests/SlicedOps.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "SlicedOps.h" - -namespace MinimalSocket::test { -MovingPointerBuffer::MovingPointerBuffer(const std::string &buff) - : buffer(buff) { - init(); -} - -MovingPointerBuffer::MovingPointerBuffer(const std::size_t buff_size) { - buffer.resize(buff_size); - init(); -} - -std::size_t MovingPointerBuffer::remainingBytes() const { - return buffer.size() - buffer_cursor; -} - -void MovingPointerBuffer::shift(const std::size_t stride) { - buffer_cursor += stride; - buffer_pointer += stride; -} - -void MovingPointerBuffer::init() { - buffer_cursor = 0; - buffer_pointer = buffer.data(); -} - -void sliced_send(Sender &subject, const std::string &to_send, - const std::size_t delta_send) { - MovingPointerBuffer buffer(to_send); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_send = - std::min(delta_send, buffer.remainingBytes()); - subject.send(BufferViewConst{buffer.data(), bytes_to_send}); - buffer.shift(bytes_to_send); - } -} - -void sliced_send(SenderTo &subject, const std::string &to_send, - const Address &to_send_address, const std::size_t delta_send) { - MovingPointerBuffer buffer(to_send); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_send = - std::min(delta_send, buffer.remainingBytes()); - subject.sendTo(BufferViewConst{buffer.data(), bytes_to_send}, - to_send_address); - buffer.shift(bytes_to_send); - } -} - -std::string sliced_receive(Receiver &subject, const std::size_t to_receive, - const std::size_t delta_receive) { - MovingPointerBuffer buffer(to_receive); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_receive = - std::min(delta_receive, buffer.remainingBytes()); - auto bytes_received = - subject.receive(BufferView{buffer.data(), bytes_to_receive}); - buffer.shift(bytes_received); - } - return buffer.asString(); -} - -std::string sliced_receive(ReceiverUnkownSender &subject, - const std::size_t to_receive, - const std::size_t delta_receive) { - MovingPointerBuffer buffer(to_receive); - while (buffer.remainingBytes() != 0) { - std::size_t bytes_to_receive = - std::min(delta_receive, buffer.remainingBytes()); - auto maybe_bytes_received = - subject.receive(BufferView{buffer.data(), bytes_to_receive}); - if (maybe_bytes_received) { - buffer.shift(maybe_bytes_received->received_bytes); - } - } - return buffer.asString(); -} -} // namespace MinimalSocket::test diff --git a/tests/SlicedOps.h b/tests/SlicedOps.h deleted file mode 100644 index 4b15de0f..00000000 --- a/tests/SlicedOps.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -namespace MinimalSocket::test { -class MovingPointerBuffer { -public: - MovingPointerBuffer(const std::string &buff); - MovingPointerBuffer(const std::size_t buff_size); - - const std::string &asString() const { return buffer; } - char *data() { return buffer_pointer; } - const char *data() const { return buffer_pointer; } - - std::size_t remainingBytes() const; - void shift(const std::size_t stride); - -private: - void init(); - - std::string buffer; - std::size_t buffer_cursor; - char *buffer_pointer; -}; - -void sliced_send(Sender &subject, const std::string &to_send, - const std::size_t delta_send); - -void sliced_send(SenderTo &subject, const std::string &to_send, - const Address &to_send_address, const std::size_t delta_send); - -std::string sliced_receive(Receiver &subject, const std::size_t to_receive, - const std::size_t delta_receive); - -std::string sliced_receive(ReceiverUnkownSender &subject, - const std::size_t to_receive, - const std::size_t delta_receive); -} // namespace MinimalSocket::test diff --git a/tests/TestAddress.cpp b/tests/TestAddress.cpp index 503960d0..708ed90d 100644 --- a/tests/TestAddress.cpp +++ b/tests/TestAddress.cpp @@ -2,29 +2,27 @@ #include #include -#include #include +#include using namespace MinimalSocket; namespace { - static constexpr Port TEST_PORT = 100; +static constexpr Port TEST_PORT = 100; } #ifdef _WIN32 TEST_CASE("Invalid WSA version", "[address]") { - WSAManager::setWsaVersion({ 0,0 }); - CHECK_THROWS_AS(Address("127.0.0.1", TEST_PORT), Error); - WSAManager::setWsaVersion({ 2,2 }); - CHECK_NOTHROW(Address("127.0.0.1", TEST_PORT)); + WSAManager::setWsaVersion({0, 0}); + CHECK_THROWS_AS(Address("127.0.0.1", TEST_PORT), Error); + WSAManager::setWsaVersion({2, 2}); + CHECK_NOTHROW(Address("127.0.0.1", TEST_PORT)); } #endif - TEST_CASE("parse valid ipv4 hosts", "[address]") { auto host = GENERATE("192.168.125.34", "127.0.0.1", "0.0.0.0"); Address converted(host, TEST_PORT); - CHECK_FALSE(nullptr == converted); CHECK(converted.getFamily() == AddressFamily::IP_V4); CHECK(converted.getHost() == host); CHECK(converted.getPort() == TEST_PORT); @@ -35,7 +33,6 @@ TEST_CASE("parse valid ipv6 hosts", "[address]") { GENERATE("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8::1:0", "0000:0000:0000:0000:0000:0000:0000:0001", "::1"); Address converted(host, TEST_PORT); - CHECK_FALSE(nullptr == converted); CHECK(converted.getFamily() == AddressFamily::IP_V6); CHECK(converted.getHost() == host); CHECK(converted.getPort() == TEST_PORT); @@ -43,6 +40,5 @@ TEST_CASE("parse valid ipv6 hosts", "[address]") { TEST_CASE("parse invalid hosts", "[address]") { auto host = GENERATE("192.125.34.34.34", "10000.0.0.1", "192.125.db8::1:0"); - Address converted(host, TEST_PORT); - CHECK(nullptr == converted); + CHECK_THROWS_AS(Address(host, TEST_PORT), Error); } diff --git a/tests/TestOpenTimeout.cpp b/tests/TestOpenTimeout.cpp index 01227dad..5cb36b0f 100644 --- a/tests/TestOpenTimeout.cpp +++ b/tests/TestOpenTimeout.cpp @@ -8,7 +8,7 @@ using namespace MinimalSocket; namespace { -class OpenableTest : public Openable { +class OpenableTest : public OpenableWithTimeout { public: OpenableTest(const Timeout &duration) : open_duration(duration) {} diff --git a/tests/TestRobustness.cpp b/tests/TestRobustness.cpp index ab0b1713..5732e358 100644 --- a/tests/TestRobustness.cpp +++ b/tests/TestRobustness.cpp @@ -29,40 +29,59 @@ static const std::string MESSAGE = "A simple message"; template void close(SocketT &subject) { SocketT{std::move(subject)}; } + +struct ThrownOrReceivedNothing { + template ThrownOrReceivedNothing(Pred pred) { + try { + received_nothing = pred(); + } catch (const SocketError &) { + throwned = true; + } + } + + operator bool() const { return throwned || received_nothing; } + +private: + bool throwned = false; + bool received_nothing = false; +}; } // namespace TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); SECTION("on connected sockets") { test::TcpPeers peers(port, family); + auto [server_side, client_side] = peers.get(); SECTION("close client while receiving") { ParallelSection::biSection( - [&peers](auto &) { - CHECK(peers.getClientSide().receive(500).empty()); + [&client_side = client_side](auto &) { + CHECK(ThrownOrReceivedNothing{ + [&]() { return client_side->receive(500).empty(); }}); }, - [&peers](auto &) { - std::this_thread::sleep_for(std::chrono::milliseconds{50}); - close(peers.getClientSide()); + [&client_side = client_side](auto &) { + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + close(*client_side); }); } SECTION("close server side while receiving") { ParallelSection::biSection( - [&peers](auto &) { - CHECK(peers.getClientSide().receive(500).empty()); + [&server_side = server_side](auto &) { + CHECK(ThrownOrReceivedNothing{ + [&]() { return server_side->receive(500).empty(); }}); }, - [&peers](auto &) { - std::this_thread::sleep_for(std::chrono::milliseconds{50}); - close(peers.getServerSide()); + [&server_side = server_side](auto &) { + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + close(*server_side); }); } } SECTION("close while accepting client") { - tcp::TcpServer server(port, family); + tcp::TcpServer server(port, family); REQUIRE(server.open()); ParallelSection::biSection( [&server](auto &) { @@ -76,21 +95,20 @@ TEST_CASE("Thread safe d'tor tcp case", "[robustness]") { } TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); test::TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + auto [server_side, client_side] = peers.get(); const std::size_t threads = 3; ParallelSection sections; - sections.add([&](auto &) { - client_side.send(make_repeated_message(MESSAGE, threads)); + sections.add([&client_side = client_side](auto &) { + client_side->send(make_repeated_message(MESSAGE, threads)); }); for (std::size_t t = 0; t < threads; ++t) { - sections.add([&](auto &) { - const auto received_request = server_side.receive(MESSAGE.size()); + sections.add([&server_side = server_side](auto &) { + const auto received_request = server_side->receive(MESSAGE.size()); CHECK(received_request == MESSAGE); }); } @@ -98,21 +116,21 @@ TEST_CASE("Receive from multiple threads tcp case", "[robustness]") { } TEST_CASE("Send from multiple threads tcp case", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); test::TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + auto [server_side, client_side] = peers.get(); const std::size_t threads = 3; ParallelSection sections; for (std::size_t t = 0; t < threads; ++t) { - sections.add([&](auto &) { client_side.send(MESSAGE); }); + sections.add( + [&client_side = client_side](auto &) { client_side->send(MESSAGE); }); } - sections.add([&](auto &) { + sections.add([&server_side = server_side](auto &) { for (std::size_t t = 0; t < threads; ++t) { - const auto received_request = server_side.receive(MESSAGE.size()); + const auto received_request = server_side->receive(MESSAGE.size()); CHECK(received_request == MESSAGE); } }); @@ -122,35 +140,41 @@ TEST_CASE("Send from multiple threads tcp case", "[robustness]") { TEST_CASE("Thread safe d'tor udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - udp::UdpBinded connection(PortFactory::makePort()); + udp::Udp connection(PortFactory::get().makePort()); ParallelSection::biSection( - [&](auto &) { CHECK_THROWS_AS(connection.receive(500), Error); }, [&](auto &) { - std::this_thread::sleep_for(std::chrono::milliseconds{50}); + CHECK(ThrownOrReceivedNothing{[&]() { + auto res = connection.receive(500); + // if here it did not thrown, but 0 bytes are expected to have been + // actually received + REQUIRE(res.has_value()); + return res->received_message.empty(); + }}); + }, + [&](auto &) { + std::this_thread::sleep_for(std::chrono::milliseconds{200}); close(connection); }); } -/* - TEST_CASE("Receive from multiple threads udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family) + UDP_PEERS(udp::Udp, family); const std::size_t threads = 3; ParallelSection sections; sections.add([&](Barrier &br) { for (std::size_t t = 0; t < threads; ++t) { - requester.sendTo(MESSAGE, responder_address); + requester->sendTo(MESSAGE, responder_address); } br.arrive_and_wait(); }); for (std::size_t t = 0; t < threads; ++t) { sections.add([&](Barrier &br) { br.arrive_and_wait(); - const auto received_request = responder.receive(MESSAGE.size()); + const auto received_request = responder->receive(MESSAGE.size()); CHECK(received_request); CHECK(received_request->received_message == MESSAGE); }); @@ -161,39 +185,38 @@ TEST_CASE("Receive from multiple threads udp case", "[robustness]") { TEST_CASE("Send from multiple threads udp case", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family) + UDP_PEERS(udp::Udp, family); const std::size_t threads = 3; ParallelSection sections; for (std::size_t t = 0; t < threads; ++t) { sections.add([&](Barrier &br) { - requester.sendTo(MESSAGE, responder_address); + requester->sendTo(MESSAGE, responder_address); br.arrive_and_wait(); }); } sections.add([&](Barrier &br) { br.arrive_and_wait(); for (std::size_t t = 0; t < threads; ++t) { - const auto received_request = responder.receive(MESSAGE.size()); + const auto received_request = responder->receive(MESSAGE.size()); CHECK(received_request); CHECK(received_request->received_message == MESSAGE); } }); sections.run(); } -*/ TEST_CASE("Use tcp socket before opening it", "[robustness]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); SECTION("server") { - tcp::TcpServer socket(port, family); + tcp::TcpServer socket(port, family); CHECK_THROWS_AS(socket.acceptNewClient(), Error); } SECTION("client") { - tcp::TcpClient socket(Address{port, family}); + tcp::TcpClient socket(Address{port, family}); CHECK_THROWS_AS(socket.receive(500), SocketError); CHECK_THROWS_AS(socket.send("dummy"), SocketError); } @@ -202,10 +225,10 @@ TEST_CASE("Use tcp socket before opening it", "[robustness]") { TEST_CASE("Use udp socket before opening it", "[robustness]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - udp::UdpBinded socket(PortFactory::makePort(), family); + udp::Udp socket(PortFactory::get().makePort(), family); CHECK_THROWS_AS(socket.receive(500), SocketError); CHECK_THROWS_AS( - socket.sendTo("dummy", Address{PortFactory::makePort(), family}), + socket.sendTo("dummy", Address{PortFactory::get().makePort(), family}), SocketError); CHECK_THROWS_AS(socket.connect(), SocketError); } diff --git a/tests/TestTCP.cpp b/tests/TestTCP.cpp index f09f7c0a..aed9058b 100644 --- a/tests/TestTCP.cpp +++ b/tests/TestTCP.cpp @@ -9,7 +9,7 @@ #include "ConnectionsUtils.h" #include "ParallelSection.h" #include "PortFactory.h" -#include "SlicedOps.h" +#include "RollingView.h" using namespace MinimalSocket; using namespace MinimalSocket::tcp; @@ -21,11 +21,11 @@ static const std::string response = "Welcome"; struct SenderReceiver { Sender &sender; - Receiver &receiver; + Receiver &receiver; }; template SenderReceiver makeSenderReceiver(T &subject) { Sender &as_sender = subject; - Receiver &as_receiver = subject; + Receiver &as_receiver = subject; return SenderReceiver{as_sender, as_receiver}; } @@ -52,12 +52,12 @@ void send_response(const SenderReceiver &requester, } // namespace TEST_CASE("Establish tcp connection", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); #if !defined(_WIN32) SECTION("expected failure") { - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); CHECK_THROWS_AS(client.open(), Error); CHECK_FALSE(client.wasOpened()); } @@ -65,43 +65,40 @@ TEST_CASE("Establish tcp connection", "[tcp]") { SECTION("expected success") { test::TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + auto [server_side, client_side] = peers.get(); - REQUIRE(client_side.wasOpened()); + REQUIRE(client_side->wasOpened()); const std::size_t cycles = 5; - const std::string request = "Hello"; - const std::string response = "Welcome"; SECTION("client send, server respond") { - send_response(makeSenderReceiver(client_side), - makeSenderReceiver(server_side)); + send_response(makeSenderReceiver(*client_side), + makeSenderReceiver(*server_side)); } SECTION("server send, client respond") { - send_response(makeSenderReceiver(server_side), - makeSenderReceiver(client_side)); + send_response(makeSenderReceiver(*server_side), + makeSenderReceiver(*client_side)); } SECTION("receive with timeout") { const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - auto received_request = server_side.receive(request.size(), timeout); + auto received_request = server_side->receive(request.size(), timeout); CHECK(received_request.empty()); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; ParallelSection::biSection( - [&](auto &) { + [&, client_side = client_side](auto &) { std::this_thread::sleep_for(wait); - client_side.send(request); + client_side->send(request); }, - [&](auto &) { + [&, server_side = server_side](auto &) { auto received_request = - server_side.receive(request.size(), timeout); + server_side->receive(request.size(), timeout); CHECK(received_request == request); }); } @@ -110,17 +107,17 @@ TEST_CASE("Establish tcp connection", "[tcp]") { } TEST_CASE("Establish many tcp connections to same server", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpServer server(port, family); + TcpServer server(port, family); server.open(); const std::size_t clients_numb = 5; SECTION("sequencial connnections") { - std::list accepted_clients; - std::list clients; + std::list accepted_clients; + std::list> clients; ParallelSection::biSection( [&](auto &) { for (std::size_t c = 0; c < clients_numb; ++c) { @@ -143,7 +140,7 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } }); Task ask_connection = [&](auto &) { - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); CHECK(client.open()); }; for (std::size_t c = 0; c < clients_numb; ++c) { @@ -154,15 +151,15 @@ TEST_CASE("Establish many tcp connections to same server", "[tcp]") { } TEST_CASE("Open multiple times tcp clients", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpServer server(port, family); + TcpServer server(port, family); server.open(); std::size_t cycles = 5; - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); for (std::size_t c = 0; c < cycles; ++c) { ParallelSection::biSection([&](auto &) { server.acceptNewClient(); }, @@ -175,33 +172,32 @@ TEST_CASE("Open multiple times tcp clients", "[tcp]") { } TEST_CASE("Open tcp client with timeout", "[tcp]") { - const auto port = PortFactory::makePort(); + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const auto timeout = Timeout{500}; - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); SECTION("expect fail within timeout") { #ifdef _WIN32 CHECK_FALSE(client.open(timeout)); #else - CHECK_THROWS_AS( - client.open(timeout), - Error); // linux throw if no server tcp were previously created, while - // windows seems to does not have this check + // linux throw if no server tcp were previously created, while windows seems + // to does not have this check + CHECK_THROWS_AS(client.open(timeout), Error); #endif CHECK_FALSE(client.wasOpened()); } SECTION("expect success within timeout") { const auto wait = Timeout{250}; - TcpServer server(port, family); + TcpServer server(port, family); REQUIRE(server.open()); ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - TcpConnection conn = server.acceptNewClient(); + auto conn = server.acceptNewClient(); auto received_request = conn.receive(request.size()); CHECK(received_request == request); }, @@ -215,7 +211,7 @@ TEST_CASE("Open tcp client with timeout", "[tcp]") { TEST_CASE("Reserve random port for tcp server", "[tcp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpServer server(ANY_PORT, family); + TcpServer server(ANY_PORT, family); REQUIRE(server.open()); const auto port = server.getPortToBind(); REQUIRE(port != 0); @@ -229,7 +225,7 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { }, [&](Barrier &br) { // client - TcpClient client(Address(port, family)); + TcpClient client(Address(port, family)); br.arrive_and_wait(); REQUIRE(client.open()); REQUIRE(client.wasOpened()); @@ -237,109 +233,105 @@ TEST_CASE("Reserve random port for tcp server", "[tcp]") { }); } -TEST_CASE("Accept client with timeout", "[tcp]") { +#if !defined(__APPLE__) +TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - const auto port = PortFactory::makePort(); - TcpServer server(port, family); - REQUIRE(server.open()); - const auto server_address = Address(port, family); - - const auto timeout = Timeout{500}; - - SECTION("expect fail within timeout") { - // connect first client - TcpClient client_first = TcpClient{server_address}; - std::unique_ptr server_side_first; - ParallelSection::biSection([&](auto &) { CHECK(client_first.open()); }, - [&](auto &) { - auto accepted = server.acceptNewClient(); - server_side_first = - std::make_unique( - std::move(accepted)); - }); + TcpPeers peers(port, family); + auto [server_side, client_side] = peers.get(); - // expect second accept to fail - CHECK_FALSE(server.acceptNewClient(timeout)); - CHECK(server.wasOpened()); + const std::string request = "This is a simulated long message"; - // check first accepted connection is still valid - ParallelSection::biSection( - [&](auto &) { - auto received_request = server_side_first->receive(request.size()); - CHECK(received_request == request); - }, - [&](auto &) { - // client - client_first.send(request); - }); + const std::size_t delta = 4; - // connect second client after accept unsuccess and check they can exchange - // messages + SECTION("split receive") { ParallelSection::biSection( - [&](Barrier &br) { - TcpClient client_second = TcpClient{server_address}; - br.arrive_and_wait(); - CHECK(client_second.open()); - client_second.send(request); - }, - [&](Barrier &br) { - br.arrive_and_wait(); - auto server_side_second = server.acceptNewClient(); - auto received_request = server_side_second.receive(request.size()); + [&, client_side = client_side](auto &) { client_side->send(request); }, + [&, server_side = server_side](auto &) { + auto received_request = + sliced_receive(*server_side, request.size(), 4); CHECK(received_request == request); }); } - SECTION("expect success within timeout") { - const auto wait = Timeout{250}; + SECTION("split send") { ParallelSection::biSection( - [&](Barrier &br) { - TcpClient client = TcpClient{server_address}; + [&, client_side = client_side](Barrier &br) { + sliced_send(*client_side, request, 4); br.arrive_and_wait(); - std::this_thread::sleep_for(wait); - CHECK(client.open()); }, - [&](Barrier &br) { + [&, server_side = server_side](Barrier &br) { br.arrive_and_wait(); - CHECK(server.acceptNewClient(timeout)); + auto received_request = server_side->receive(request.size()); + CHECK(received_request == request); }); } } +#endif -#if !defined(__APPLE__) -TEST_CASE("Send Receive messages split into multiple pieces (tcp)", "[tcp]") { - const auto port = PortFactory::makePort(); +TEST_CASE("Establish tcp connection non blocking", "[tcp]") { + const auto port = PortFactory::get().makePort(); const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - TcpPeers peers(port, family); - auto &server_side = peers.getServerSide(); - auto &client_side = peers.getClientSide(); + tcp::TcpServer server{port, family}; + REQUIRE(server.open()); - const std::string request = "This is a simulated long message"; + ParallelSection::biSection( + [&](Barrier &br) { + CHECK_FALSE(server.acceptNewClient().has_value()); + br.arrive_and_wait(); + std::this_thread::sleep_for(std::chrono::milliseconds{500}); + auto accepted = server.acceptNewClient(); + REQUIRE(accepted.has_value()); + auto received_request = accepted->receive(request.size()); + CHECK(received_request == request); + }, + [&](Barrier &br) { + br.arrive_and_wait(); + TcpClient client{Address{port, family}}; + client.open(); + REQUIRE(client.wasOpened()); + client.send(request); + }); +} - const std::size_t delta = 4; +TEST_CASE("Receive non blocking (tcp)", "[tcp]") { + const auto port = PortFactory::get().makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - SECTION("split receive") { - ParallelSection::biSection([&](auto &) { client_side.send(request); }, - [&](auto &) { - auto received_request = sliced_receive( - server_side, request.size(), 4); - CHECK(received_request == request); - }); + std::optional server_side; + tcp::TcpClient client_side{Address{port, family}}; + ParallelSection::biSection( + [&](Barrier &br) { + tcp::TcpServer server{port, family}; + REQUIRE(server.open()); + br.arrive_and_wait(); + auto accepted = server.acceptNewClient(); + server_side.emplace(accepted.turnToNonBlocking()); + }, + [&](Barrier &br) { + br.arrive_and_wait(); + REQUIRE(client_side.open()); + }); + + SECTION("client side non blocking receive") { + CHECK(client_side.receive(request.size()).empty()); + server_side->send(request); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = client_side.receive(request.size()); + CHECK(received_request == request); } - SECTION("split send") { - ParallelSection::biSection( - [&](Barrier &br) { - sliced_send(client_side, request, 4); - br.arrive_and_wait(); - }, - [&](Barrier &br) { - br.arrive_and_wait(); - auto received_request = server_side.receive(request.size()); - CHECK(received_request == request); - }); + SECTION("server side non blocking receive") { + CHECK(server_side->receive(request.size()).empty()); + client_side.send(request); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = server_side->receive(request.size()); + CHECK(received_request == request); } } -#endif diff --git a/tests/TestUDP.cpp b/tests/TestUDP.cpp index c393ab43..96db2ad8 100644 --- a/tests/TestUDP.cpp +++ b/tests/TestUDP.cpp @@ -6,7 +6,7 @@ #include "ConnectionsUtils.h" #include "ParallelSection.h" #include "PortFactory.h" -#include "SlicedOps.h" +#include "RollingView.h" using namespace MinimalSocket; using namespace MinimalSocket::udp; @@ -20,21 +20,22 @@ bool are_same(const Address &a, const Address &b, const AddressFamily &family) { return (family == AddressFamily::IP_V4) ? (a == b) : (a.getPort() == b.getPort()); } -} // namespace + +}; // namespace TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); + UDP_PEERS(udp::Udp, family); ParallelSection::biSection( [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { - CHECK(requester.sendTo(request, responder_address)); + CHECK(requester->sendTo(request, responder_address)); br.arrive_and_wait(); br.arrive_and_wait(); - auto received_response = requester.receive(response.size()); + auto received_response = requester->receive(response.size()); REQUIRE(received_response); CHECK(received_response->received_message == response); CHECK(are_same(received_response->sender, responder_address, family)); @@ -43,11 +44,11 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); REQUIRE(received_request); CHECK(received_request->received_message == request); CHECK(are_same(received_request->sender, requester_address, family)); - responder.sendTo(response, requester_address); + responder->sendTo(response, requester_address); br.arrive_and_wait(); } }); @@ -56,7 +57,7 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK_FALSE(received_request); } @@ -65,10 +66,10 @@ TEST_CASE("Exchange messages between UdpBinded and UdpBinded", "[udp]") { ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - requester.sendTo(request, responder_address); + requester->sendTo(request, responder_address); }, [&](auto &) { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); REQUIRE(received_request); CHECK(received_request->received_message == request); CHECK( @@ -82,33 +83,24 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - - UdpConnected requester(responder_address, requester_port); - REQUIRE(requester.open()); - UdpConnected responder(requester_address, responder_port); - REQUIRE(responder.open()); + UDP_PEERS(udp::UdpConnected, family); ParallelSection::biSection( [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { - CHECK(requester.send(request)); + CHECK(requester->send(request)); br.arrive_and_wait(); br.arrive_and_wait(); - auto received_response = requester.receive(response.size()); + auto received_response = requester->receive(response.size()); CHECK(received_response == response); } }, [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); CHECK(received_request == request); - responder.send(response); + responder->send(response); br.arrive_and_wait(); } }); @@ -117,7 +109,7 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK(received_request.empty()); } @@ -126,10 +118,10 @@ TEST_CASE("Exchange messages between UdpConnected and UdpConnected", "[udp]") { ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - requester.send(request); + requester->send(request); }, [&](auto &) { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK(received_request == request); }); } @@ -141,47 +133,38 @@ TEST_CASE( "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - - UdpConnected requester(responder_address, requester_port); - REQUIRE(requester.open()); - UdpConnected responder(requester_address, responder_port); - REQUIRE(responder.open()); + UDP_PEERS(udp::UdpConnected, family); auto exchange_messages_before = GENERATE(true, false); if (exchange_messages_before) { ParallelSection::biSection( [&](Barrier &br) { - CHECK(requester.send(request)); + CHECK(requester->send(request)); br.arrive_and_wait(); br.arrive_and_wait(); - auto received_response = requester.receive(response.size()); + auto received_response = requester->receive(response.size()); CHECK(received_response == response); }, [&](Barrier &br) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); CHECK(received_request == request); - responder.send(response); + responder->send(response); br.arrive_and_wait(); }); } - UdpBinded second_requester(PortFactory::makePort(), family); + udp::Udp second_requester(PortFactory::get().makePort(), family); REQUIRE(second_requester.open()); const auto timeout = Timeout{500}; const auto wait = Timeout{250}; ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - second_requester.sendTo(request, Address(responder_port, family)); + second_requester.sendTo(request, responder_address); }, [&](auto &) { - auto received_request = responder.receive(request.size(), timeout); + auto received_request = responder->receive(request.size(), timeout); CHECK(received_request.empty()); }); } @@ -190,38 +173,26 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); const std::size_t cycles = 5; - const auto requester_port = PortFactory::makePort(); - const Address requester_address = Address(requester_port, family); - const auto responder_port = PortFactory::makePort(); - const Address responder_address = Address(responder_port, family); - - UdpBinded responder(responder_port, family); - REQUIRE(responder.open()); - - std::unique_ptr requester_only_bind = - std::make_unique(requester_port, family); - REQUIRE(requester_only_bind->open()); + UDP_PEERS(udp::Udp, family); // connect requester to responder auto deduce_sender = GENERATE(true, false); - std::unique_ptr requester_connected; + std::optional> requester_connected; if (deduce_sender) { ParallelSection::biSection( [&](Barrier &br) { - responder.sendTo("1", requester_address); + responder->sendTo("1", requester_address); br.arrive_and_wait(); }, [&](Barrier &br) { br.arrive_and_wait(); - auto socket_connected = requester_only_bind->connect(); + auto socket_connected = requester->connect(); CHECK(are_same(socket_connected.getRemoteAddress(), responder_address, family)); - requester_connected = - std::make_unique(std::move(socket_connected)); + requester_connected.emplace(std::move(socket_connected)); }); } else { - requester_connected = std::make_unique( - requester_only_bind->connect(responder_address)); + requester_connected.emplace(requester->connect(responder_address)); } REQUIRE(requester_connected->wasOpened()); @@ -240,39 +211,10 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { [&](Barrier &br) { for (std::size_t c = 0; c < cycles; ++c) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); - REQUIRE(received_request); - CHECK(received_request->received_message == request); - responder.sendTo(response, requester_address); - br.arrive_and_wait(); - } - }); - - // try to disconnect requester - requester_only_bind = - std::make_unique(requester_connected->disconnect()); - REQUIRE(requester_only_bind->wasOpened()); - - // try message exchange - ParallelSection::biSection( - [&](Barrier &br) { - for (std::size_t c = 0; c < cycles; ++c) { - CHECK(requester_only_bind->sendTo(request, responder_address)); - br.arrive_and_wait(); - br.arrive_and_wait(); - auto received_response = - requester_only_bind->receive(response.size()); - REQUIRE(received_response); - CHECK(received_response->received_message == response); - } - }, - [&](Barrier &br) { - for (std::size_t c = 0; c < cycles; ++c) { - br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); REQUIRE(received_request); CHECK(received_request->received_message == request); - responder.sendTo(response, requester_address); + responder->sendTo(response, requester_address); br.arrive_and_wait(); } }); @@ -281,12 +223,12 @@ TEST_CASE("Metamorphosis of udp connections", "[udp]") { TEST_CASE("Open connection with timeout", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); + UDP_PEERS(udp::Udp, family); const auto timeout = Timeout{500}; SECTION("expect fail within timeout") { - CHECK_FALSE(requester.connect(timeout)); + CHECK_FALSE(requester->connect(timeout)); } SECTION("expect success within timeout") { @@ -294,10 +236,10 @@ TEST_CASE("Open connection with timeout", "[udp]") { ParallelSection::biSection( [&](auto &) { std::this_thread::sleep_for(wait); - responder.sendTo("1", requester_address); + responder->sendTo("1", requester_address); }, [&](auto &) { - auto connected_result = requester.connect(timeout); + auto connected_result = requester->connect(timeout); REQUIRE(connected_result); CHECK(are_same(connected_result->getRemoteAddress(), responder_address, family)); @@ -309,13 +251,13 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); auto requester_port = ANY_PORT; - UdpBinded requester(requester_port, family); + udp::Udp requester(requester_port, family); REQUIRE(requester.open()); requester_port = requester.getPortToBind(); const Address requester_address = Address(requester_port, family); - auto responder_port = GENERATE(PortFactory::makePort(), ANY_PORT); - UdpBinded responder(responder_port, family); + auto responder_port = GENERATE(PortFactory::get().makePort(), ANY_PORT); + udp::Udp responder(responder_port, family); REQUIRE(responder.open()); responder_port = responder.getPortToBind(); const Address responder_address = Address(responder_port, family); @@ -342,12 +284,10 @@ TEST_CASE("Reserve random port for udp connection", "[udp]") { } /* - -TEST_CASE("Send Receive messages split into multiple pieces (udp)", - "[udp][!mayfail]") { +TEST_CASE("Send Receive messages split into multiple pieces (udp)", "[udp]") { const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); - UDP_PEERS(PortFactory::makePort(), PortFactory::makePort(), family); + UDP_PEERS(udp::Udp, family); const std::string request = "This is a simulated long message"; @@ -357,13 +297,13 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", SECTION("split receive") { ParallelSection::biSection( [&](Barrier &br) { - requester.sendTo(request, responder_address); + requester->sendTo(request, responder_address); br.arrive_and_wait(); }, [&](Barrier &br) { br.arrive_and_wait(); auto received_request = - sliced_receive(responder, request.size(), 4); + sliced_receive(*responder, request.size(), 4); CHECK(received_request == request); }); } @@ -371,12 +311,12 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", SECTION("split send") { ParallelSection::biSection( [&](Barrier &br) { - sliced_send(requester, request, responder_address, 4); + sliced_send(*requester, request, responder_address, 4); br.arrive_and_wait(); }, [&](Barrier &br) { br.arrive_and_wait(); - auto received_request = responder.receive(request.size()); + auto received_request = responder->receive(request.size()); CHECK(received_request); CHECK(received_request->received_message == request); }); @@ -384,8 +324,8 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } SECTION("connected") { - auto requester_conn = requester.connect(responder_address); - auto responder_conn = responder.connect(requester_address); + auto requester_conn = requester->connect(responder_address); + auto responder_conn = responder->connect(requester_address); SECTION("split receive") { ParallelSection::biSection( [&](Barrier &br) { @@ -414,5 +354,36 @@ TEST_CASE("Send Receive messages split into multiple pieces (udp)", } } } - */ + +TEST_CASE("Receive from unknown non blocking", "[udp]") { + const auto port = PortFactory::get().makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + UDP_PEERS(udp::Udp, family); + + CHECK_FALSE(responder->receive(request.size()).has_value()); + requester->sendTo(request, responder_address); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = responder->receive(request.size()); + REQUIRE(received_request.has_value()); + CHECK(received_request->received_message == request); + CHECK(received_request->sender == requester_address); +} + +TEST_CASE("Receive non blocking (udp)", "[udp]") { + const auto port = PortFactory::get().makePort(); + const auto family = GENERATE(AddressFamily::IP_V4, AddressFamily::IP_V6); + + UDP_PEERS(udp::UdpConnected, family); + + CHECK(responder->receive(request.size()).empty()); + requester->send(request); +#if defined(__APPLE__) + std::this_thread::sleep_for(std::chrono::seconds{3}); +#endif + auto received_request = responder->receive(request.size()); + CHECK(received_request == request); +} From 6376352ca50a4b4655acba4f0c299329e9ded194 Mon Sep 17 00:00:00 2001 From: 2bit Date: Fri, 24 May 2024 18:43:57 +0900 Subject: [PATCH 7/7] fix missing include --- samples/udp/UdpResponderNonBlocking.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/samples/udp/UdpResponderNonBlocking.cpp b/samples/udp/UdpResponderNonBlocking.cpp index 58a23903..612e68f5 100644 --- a/samples/udp/UdpResponderNonBlocking.cpp +++ b/samples/udp/UdpResponderNonBlocking.cpp @@ -17,6 +17,9 @@ #include #include #include + +#include + using namespace std; int main(const int argc, const char **argv) {