From 84fb8ab36df33f510edec01de40c4022a18fdea1 Mon Sep 17 00:00:00 2001 From: duanhf2012 Date: Sat, 28 Mar 2020 09:57:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4origin2.0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 674 ----------- README.md | 585 ---------- Test/.vscode/launch.json | 21 - Test/.vscode/settings.json | 3 - Test/build.bat | 4 - Test/config/cluster.json | 55 - Test/config/nodeconfig.json | 50 - Test/logicservice/SubNet1_Service.go | 122 -- Test/main.go | 56 - Test/msgpb/gen.bat | 1 - Test/msgpb/test.pb.go | 132 --- Test/msgpb/test.proto | 18 - Test/workspace.code-workspace | 13 - Test2/Helloworld/.vscode/launch.json | 21 - Test2/Helloworld/.vscode/settings.json | 3 - Test2/Helloworld/build.bat | 4 - Test2/Helloworld/config/cluster.json | 37 - Test2/Helloworld/config/nodeconfig.json | 20 - Test2/Helloworld/main.go | 16 - Test2/Helloworld/workspace.code-workspace | 10 - _config.yml | 1 - cluster/cluster.go | 786 ++----------- cluster/config.go | 341 ------ cluster/parsecfg.go | 192 ++++ doc/origin分布式框架.pptx | Bin 333086 -> 0 bytes event/event.go | 80 ++ event/eventtype.go | 14 + example/GateService/GateService.go | 43 + example/GateService/pbprocessor.go | 79 ++ example/config/cluster/subnet/cluster.json | 23 + example/config/cluster/subnet/service.json | 13 + example/main.exe.pid | 1 + example/main.go | 202 ++++ example/serviceTest/OriginServeOne.go | 64 ++ example/serviceTest/OriginServerTwo.go | 27 + log/example_test.go | 29 + log/log.go | 153 +++ network/agent.go | 6 + network/conn.go | 14 + network/httpserver.go | 98 -- network/netserver.go | 7 + network/processor.go | 10 + network/processor/gobprocessor.go | 1 + network/processor/jsonprocessor.go | 17 + network/processor/msgpackprocessor.go | 1 + network/processor/processor.go | 11 + network/processor/protobufprocessor.go | 1 + network/tcp_client.go | 130 +++ network/tcp_conn.go | 113 ++ network/tcp_msg.go | 154 +++ network/tcp_server.go | 127 +++ network/tcpsocketclient.go | 48 - network/tcpsocketserver.go | 331 ------ network/websocketclient.go | 259 ----- network/websocketserver.go | 328 ------ network/wsagentserver.go | 323 ------ node/node.go | 115 ++ originnode/node.go | 175 --- rpc/client.go | 514 +++------ rpc/debug.go | 90 -- rpc/gobrpc/processor.go | 1 + rpc/jsonprocessor.go | 22 + rpc/netrpc.go | 1 + rpc/rpchandler.go | 377 ++++++ rpc/server.go | 1015 ++++------------- service/DeadForMonitor.go | 78 -- service/Logger.go | 37 - service/Module.go | 442 +++---- service/Service.go | 153 ++- service/servicemanager.go | 132 --- service/servicemgr.go | 53 + servicelist/servicelist.go | 37 - sysmodule/DBModule.go | 237 ++-- sysmodule/DBModule_test.go | 89 -- sysmodule/HttpClientPoolModule.go | 4 +- sysmodule/HttpClientPoolModule_test.go | 29 - sysmodule/LogModule.go | 190 --- sysmodule/RedisModule.go | 95 +- sysmodule/RedisModule_test.go | 116 -- sysmodule/SystemModuleDef.go | 7 - sysservice/logservice.go | 37 - sysservice/originhttp/httpserverervice.go | 511 --------- sysservice/pprofservice.go | 73 -- .../synccacheservice/synccacheservice.go | 167 --- sysservice/tcpservice.go | 137 +++ sysservice/tcpsocketpbservice.go | 172 --- sysservice/wsagentservice.go | 41 - sysservice/wsserverservice.go | 47 - sysservice/wsservice.go | 4 + util/Log.go | 10 - util/Timer.go | 94 -- util/{ => aesencrypt}/aes_encrypt.go | 17 +- util/{Coroutine.go => coroutine/coroutine.go} | 8 +- util/{ => deepcopy}/deepcopy.go | 7 +- util/example_test.go | 35 - util/has/hash.go | 1 + util/hash/hash.go | 2 +- util/queue.go | 101 -- util/queue/.gitignore | 23 - util/queue/.travis.yml | 7 - util/queue/LICENSE | 21 - util/queue/README.md | 16 - util/{ => rand}/rand.go | 2 +- util/{ => semaphore}/semaphore.go | 2 +- util/sync_queue.go | 102 -- util/timer/cronexpr.go | 268 +++++ util/timer/example_test.go | 67 ++ util/timer/timer.go | 134 +++ util/{ => umap}/map.go | 6 +- util/{mapex.go => umap/mapEx.go} | 31 +- util/uuid/uuid.go | 15 +- 111 files changed, 3657 insertions(+), 8382 deletions(-) delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 Test/.vscode/launch.json delete mode 100644 Test/.vscode/settings.json delete mode 100644 Test/build.bat delete mode 100644 Test/config/cluster.json delete mode 100644 Test/config/nodeconfig.json delete mode 100644 Test/logicservice/SubNet1_Service.go delete mode 100644 Test/main.go delete mode 100644 Test/msgpb/gen.bat delete mode 100644 Test/msgpb/test.pb.go delete mode 100644 Test/msgpb/test.proto delete mode 100644 Test/workspace.code-workspace delete mode 100644 Test2/Helloworld/.vscode/launch.json delete mode 100644 Test2/Helloworld/.vscode/settings.json delete mode 100644 Test2/Helloworld/build.bat delete mode 100644 Test2/Helloworld/config/cluster.json delete mode 100644 Test2/Helloworld/config/nodeconfig.json delete mode 100644 Test2/Helloworld/main.go delete mode 100644 Test2/Helloworld/workspace.code-workspace delete mode 100644 _config.yml delete mode 100644 cluster/config.go create mode 100644 cluster/parsecfg.go delete mode 100644 doc/origin分布式框架.pptx create mode 100644 event/event.go create mode 100644 event/eventtype.go create mode 100644 example/GateService/GateService.go create mode 100644 example/GateService/pbprocessor.go create mode 100644 example/config/cluster/subnet/cluster.json create mode 100644 example/config/cluster/subnet/service.json create mode 100644 example/main.exe.pid create mode 100644 example/main.go create mode 100644 example/serviceTest/OriginServeOne.go create mode 100644 example/serviceTest/OriginServerTwo.go create mode 100644 log/example_test.go create mode 100644 log/log.go create mode 100644 network/agent.go create mode 100644 network/conn.go delete mode 100644 network/httpserver.go create mode 100644 network/netserver.go create mode 100644 network/processor.go create mode 100644 network/processor/gobprocessor.go create mode 100644 network/processor/jsonprocessor.go create mode 100644 network/processor/msgpackprocessor.go create mode 100644 network/processor/processor.go create mode 100644 network/processor/protobufprocessor.go create mode 100644 network/tcp_client.go create mode 100644 network/tcp_conn.go create mode 100644 network/tcp_msg.go create mode 100644 network/tcp_server.go delete mode 100644 network/tcpsocketclient.go delete mode 100644 network/tcpsocketserver.go delete mode 100644 network/websocketclient.go delete mode 100644 network/websocketserver.go delete mode 100644 network/wsagentserver.go create mode 100644 node/node.go delete mode 100644 originnode/node.go delete mode 100644 rpc/debug.go create mode 100644 rpc/gobrpc/processor.go create mode 100644 rpc/jsonprocessor.go create mode 100644 rpc/netrpc.go create mode 100644 rpc/rpchandler.go delete mode 100644 service/DeadForMonitor.go delete mode 100644 service/Logger.go delete mode 100644 service/servicemanager.go create mode 100644 service/servicemgr.go delete mode 100644 servicelist/servicelist.go delete mode 100644 sysmodule/DBModule_test.go delete mode 100644 sysmodule/HttpClientPoolModule_test.go delete mode 100644 sysmodule/LogModule.go delete mode 100644 sysmodule/RedisModule_test.go delete mode 100644 sysmodule/SystemModuleDef.go delete mode 100644 sysservice/logservice.go delete mode 100644 sysservice/originhttp/httpserverervice.go delete mode 100644 sysservice/pprofservice.go delete mode 100644 sysservice/synccacheservice/synccacheservice.go create mode 100644 sysservice/tcpservice.go delete mode 100644 sysservice/tcpsocketpbservice.go delete mode 100644 sysservice/wsagentservice.go delete mode 100644 sysservice/wsserverservice.go create mode 100644 sysservice/wsservice.go delete mode 100644 util/Log.go delete mode 100644 util/Timer.go rename util/{ => aesencrypt}/aes_encrypt.go (72%) rename util/{Coroutine.go => coroutine/coroutine.go} (89%) rename util/{ => deepcopy}/deepcopy.go (95%) delete mode 100644 util/example_test.go create mode 100644 util/has/hash.go delete mode 100644 util/queue.go delete mode 100644 util/queue/.gitignore delete mode 100644 util/queue/.travis.yml delete mode 100644 util/queue/LICENSE delete mode 100644 util/queue/README.md rename util/{ => rand}/rand.go (98%) rename util/{ => semaphore}/semaphore.go (91%) delete mode 100644 util/sync_queue.go create mode 100644 util/timer/cronexpr.go create mode 100644 util/timer/example_test.go create mode 100644 util/timer/timer.go rename util/{ => umap}/map.go (98%) rename util/{mapex.go => umap/mapEx.go} (88%) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - 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 -. diff --git a/README.md b/README.md deleted file mode 100644 index 632c840..0000000 --- a/README.md +++ /dev/null @@ -1,585 +0,0 @@ -origin 游戏服务器引擎简介 -================== - - -origin 是一个由 Go 语言(golang)编写的分布式开源游戏服务器引擎。origin适用于各类游戏服务器的开发,包括 H5(HTML5)游戏服务器。 - -origin 解决的问题: -* origin总体设计如go语言设计一样,总是尽可能的提供简洁和易用的模式,快速开发。 -* 能够根据业务需求快速并灵活的制定服务器架构。 -* 利用多核优势,将不同的service配置到不同的node,并能高效的协同工作。 -* 将整个引擎抽象三大对象,node,service,module。通过统一的组合模式管理游戏中各功能模块的关系。 -* 有丰富并健壮的工具库。 - -Hello world! ---------------- -下面我们来一步步的建立origin服务器,先下载[origin引擎](https://github.com/duanhf2012/origin "origin引擎"),或者使用如下命令: -```go -go get -v -u github.com/duanhf2012/origin -``` -于是下载到GOPATH环境目录中,在src中加入main.go,内容如下: -```go -package main - -import ( - "github.com/duanhf2012/origin/originnode" -) - -func main() { - node := originnode.NewOriginNode() - if node == nil { - return - } - - node.Init() - node.Start() -} -``` -一个origin服务器需要创建一个node对象,然后必需有Init和Start的流程。 - -origin引擎三大对象关系 ---------------- -* Node: 可以认为每一个Node代表着一个origin进程 -* Service:一个独立的服务可以认为是一个大的功能模块,他是Node的子集,创建完成并安装Node对象中。服务可以支持外部RPC和HTTP接口对外功能。 -* Module: 这是origin最小对象单元,强烈建议所有的业务模块都划分成各个小的Module组合。Module可以建立树状关系。Service本身也是Module的类型。 - -origin集群核心配置文件config/cluser.json如下: ---------------- -``` -{ - "SubNet": [{ - "Remark": "Manual,Full,Auto", - "SubNetMode": "Full", - "SubNetName": "SubNet1", - "PublicServiceList": ["logiclog"], - "NodeList": [{ - "NodeID": 1, - "NodeName": "N_Node1", - "ServiceList": [ - "HttpServerService", - "SubNet1_Service", - "SubNet1_Service1" - ], - "ClusterNode":["SubNet2.N_Node1","N_Node2"] - }, - { - "NodeID": 2, - "NodeName": "N_Node2", - "ServiceList": [ - "SubNet1_Service2" - ], - "ClusterNode":[] - } - ] - }, - - - { - "Remark": "Manual,Full,Auto", - "SubNetMode": "Full", - "SubNetName": "SubNet2", - "PublicServiceList": ["logiclog"], - "NodeList": [{ - "NodeID": 3, - "NodeName": "N_Node1", - "ServiceList": [ - "SubNet2_Service1" - ], - "ClusterNode":["URQ.N_Node1"] - }, - { - "NodeID": 4, - "NodeName": "N_Node4", - "ServiceList": [ - "SubNet2_Service2" - ], - "ClusterNode":[] - } - ] - } - ] -} -``` -origin集群配置以子网的模式配置,在每个子网下配置多个Node服务器,子网在应对复杂的系统时可以应用到各个子系统,方便每个子系统的隔离。 - -origin所有的结点与服务通过配置进行关联,配置文件分为两大配置结点: -* SubNet:配置子网,以上配置中包括子网名为SubNet1与SubNet2,每个子网包含多个Node结点。 - * SubNetMode:子网模式,Manual手动模式,Full通过配置全自动连接集群模式(推荐模式),Auto自动模式 - * SubNetName:子网名称 - * PublicServiceList:用于公共服务配置,所有的结点默认会加载该服务列表 - * SubNetMode:子网集群模式, - * NodeList:Node所有的列表 - * NodeId: Node编号,用于标识唯一的进程id - * NodeName: Node名称,用于区分Node结点功能。例如,可以是GameServer - * ServiceList:结点中允许开启的服务列表 - * ClusterNode:将与列表中的Node产生集群关系。允许访问这些结点中所有的服务。允许集群其他子网结点,例如:URQ.N_Node1 - -origin集群核心配置文件config/nodeconfig.json如下: ---------------- -``` -{ - "Public": { - "LogLevel": 1, - "HttpPort": 9400, - "WSPort": 9500, - - "CAFile": [{ - "CertFile": "", - "KeyFile": "" - }, - { - "CertFile": "", - "KeyFile": "" - } - ], - - "Environment": "FS_Dev_LFY", - "IsListenLog": 1, - "IsSendErrorMail": 0 - }, - "NodeList": [{ - "NodeID": 1, - "NodeAddr": "127.0.0.1:8081", - "LogLevel": 1, - "HttpPort": 7001, - "WSPort": 7000, - "CertFile": "", - "KeyFile": "" - }, - { - "NodeID": 2, - "NodeAddr": "127.0.0.1:8082" - }, - { - "NodeID": 3, - "NodeAddr": "127.0.0.1:8083" - }, - { - "NodeID": 4, - "NodeAddr": "127.0.0.1:8084" - } - ] -} -``` -针对cluster.json中NodeId在该文件中配置具体环境的信息 -* Public:公共服务配置 - * LogLevel:日志等级 1:DEBUG 2:INFO 3:WARN 4:ERROR 5:FATAL。 - * HttpPort:当Node需要提供http服务时,安装HttpServerService后将使用该监听端口。 - * WSPort:当Node需要提供Websocket服务时,安装HttpServerService后将使用该监听端口。 - * CAFile:证书配置文件,支持多个。 -* NodeList: - * NodeID:结点ID,在cluster.json中所有的Node都需要在该文件中配置。 - * NodeAddr:监听RPC地址与端口。 - * 其他:该配置可以继承Public中所有的配置,可以在其中自定义LogLevel,HttpPort等。 - - - -origin第一个服务: ---------------- -我们准备的NodeId为1的结点下新建两个服务,分别是CTestService1与CTestService2。 -* config/cluster.json内容如下 -``` -{ - "SubNet": [{ - "Remark": "Manual,Full,Auto", - "SubNetMode": "Full", - "SubNetName": "SubNet1", - "PublicServiceList": ["logiclog"], - "NodeList": [{ - "NodeID": 1, - "NodeName": "N_Node1", - "ServiceList": [ - "TestService1", - "TestService2" - ], - "ClusterNode":[] - }, - { - "NodeID": 2, - "NodeName": "N_Node2", - "ServiceList": [ - "TestService3" - ], - "ClusterNode":[] - } - ] - } - ] -} -``` -* config/nodeconfig.json内容如下 -``` -{ - "Public": { - "LogLevel": 1, - "HttpPort": 9400, - "WSPort": 9500, - "Environment": "Test", - "IsListenLog": 1, - "IsSendErrorMail": 0 - }, - - "NodeList": [{ - "NodeID": 1, - "NodeAddr": "127.0.0.1:8081" - }, - { - "NodeID": 2, - "NodeAddr": "127.0.0.1:8082" - } - ] -} -``` - - -* main.go运行代码 - -```go -package main - -import ( - "github.com/duanhf2012/origin/originnode" -) - -func main() { - - node := originnode.NewOriginNode() - if node == nil { - return - } - - node.Init() - node.Start() -} - -``` - -* TestService1.go运行代码 -```go -package main - -import ( - "fmt" - - "github.com/duanhf2012/origin/cluster" - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/service" -) - -type TestService1 struct { - service.BaseService -} - -func init() { - originnode.InitService(&TestService1{}) -} - -//OnInit ... -func (ws *TestService1) OnInit() error { - - return nil -} - -//OnRun ... -func (ws *TestService1) OnRun() bool { - var i InputData - var j int - i.A1 = 3 - i.A2 = 4 - err := cluster.Call("TestService2.RPC_Add", &i, &j) - if err == nil { - fmt.Printf(" TestService2.RPC_Add is %d\n", j) - } - - err = cluster.Call("TestService3.RPC_Multi", &i, &j) - if err == nil { - fmt.Printf(" TestService2.RPC_Multi is %d\n", j) - } - - return false -} - -``` - - - -* TestService2.go运行代码 -```go -package main - -import ( - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/service" -) - -type InputData struct { - A1 int - A2 int -} - -type TestService2 struct { - service.BaseService -} - -func init() { - originnode.InitService(&TestService2{}) -} - -//OnInit ... -func (ws *TestService2) OnInit() error { - - return nil -} - -//OnRun ... -func (ws *TestService2) OnRun() bool { - return false -} - -//服务要对外的接口规划如下: -//RPC_MethodName(arg *DataType1, ret *DataType2) error -//如果不符合规范,在加载服务时,该函数将不会被映射,其他服务将不允能调用。 -func (slf *TestService2) RPC_Add(arg *InputData, ret *int) error { - - *ret = arg.A1 + arg.A2 - - return nil -} -``` - -* TestService3.go运行代码 -```go -package main - -import ( - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/service" -) - -type TestService3 struct { - service.BaseService -} - -func init() { - originnode.InitService(&TestService3{}) -} - -//OnInit ... -func (ws *TestService3) OnInit() error { - return nil -} - -//OnRun ... -func (ws *TestService3) OnRun() bool { - return false -} - -func (slf *TestService3) RPC_Multi(arg *InputData, ret *int) error { - *ret = arg.A1 * arg.A2 - return nil -} -``` - - - -通过以下命令运行: -``` -OriginServer.exe NodeId=2 -OriginServer.exe NodeId=1 -``` -其中NodeId=1的进程输出结果: -``` -TestService2.RPC_Add is 7 -TestService2.RPC_Multi is 12 -``` -通过日志可以确认,在Node启动时分别驱动Service的OnInit,OnRun,OnEndRun,上面的日志中TestService1.OnRun会被调用, -因为在OnRun的返回是false,所以OnRun只会进入一次。当返回true时,会重复循环进入OnRun。如果你不需要OnRun可以不定义OnRun函数。 -示例中,我们分别调用了本进程的TestService2服务中的RPC_Add的RPC接口,以及NodeId为2进程中服务为TestService3的接口RPC_Multi。 - -origin服务间通信: ---------------- -origin是通过rpc的方式互相调用,当前结点只能访问cluster.json中有配置ClusterNode的结点或本地结点中所有的服务接口,下面我们来用实际例子来说明,如下代码所示: -``` -package main - -import ( - "Server/service/websockservice" - "fmt" - "time" - - "github.com/duanhf2012/origin/cluster" - - "github.com/duanhf2012/origin/sysservice" - - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/service" -) - -type CTestService1 struct { - service.BaseService -} - -//输入参数,注意变量名首字母大写 -type InputData struct { - A1 int - A2 int -} - -func (slf *CTestService1) OnRun() bool { - var ret int - input := InputData{100, 11} - - //调用服务名.接口名称,传入传出参数必需为地址,符合RPC_Add接口规范 - //以下可以采用其他,注意如果在服务名前加入下划线"_"表示访问本node中的服务 - //_servicename.methodname - //servicename.methodname - err := cluster.Call("CTestService2.RPC_Add", &input, &ret) - fmt.Print(err, "\n", ret) - - return false -} - -type CTestService2 struct { - service.BaseService -} - -//注意格式一定要RPC_开头,函数第一个参数为输入参数,第二个为输出参数,只允许指针类型 -//返回值必需为error,如果不满足格式,装载服务时将被会忽略。 -func (slf *CTestService2) RPC_Add(arg *InputData, ret *int) error { - *ret = arg.A1 + arg.A2 - return nil -} - -func main() { - node := originnode.NewOriginNode() - if node == nil { - return - } - //也可以通过以下方式安装服务CTestService1与CTestService2 - node.SetupService(&CTestService1{}, &CTestService2{}) - node.Init() - node.Start() -} - -``` -输入结果为: -``` - -111 -``` -cluster.Call只允许调用一个结点中的服务,如果服务在多个结点中,是不允许的。注意,Call方式是阻塞模式,只有当被调服务响应时才返回,或者超过最大超时时间。如果不想阻塞,可以采用Go方式调用。例如:cluster.Go(true,"CTestService2.RPC_Send", &input) -第一个参数代码是否广播,如果调用的服务接口在多个Node中存在,都将被调用。还可以向指定的NodeId调用,例如: -``` -func (slf *CCluster) CallNode(nodeid int, servicemethod string, args interface{}, reply interface{}) error -func (slf *CCluster) GoNode(nodeid int, args interface{}, servicemethod string) error -``` -在实际使用时,注意抽象service,只有合理的划分service,origin是以service为最小集群单元放到不同的node中,以达到动态移动service功能到不同的node进程中。 - -origin中Module使用: ---------------- -module在origin引擎中是最小的对象单元,service本质上也是一个复杂的module。它同样有着以下方法: -``` -OnInit() error //Module初始化时调用 -OnRun() bool //Module运行时调用 -OnEndRun() -``` -在使用规则上和service是一样的,因为本质上是一样的对象。看以下简单示例: -``` -package main - -import ( - "fmt" - "time" - - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/service" -) - -type CTestService1 struct { - service.BaseService -} - -type CTestModule1 struct { - service.BaseModule -} - -func (slf *CTestModule1) OnInit() error { - fmt.Printf("CTestModule1::OnInit\n") - return nil -} - -func (slf *CTestModule1) OnRun() bool { - fmt.Printf("CTestModule1::OnRun\n") - time.Sleep(time.Second * 1) - return true -} - -func (slf *CTestModule1) OnEndRun() { - fmt.Printf("CTestModule1::OnEndRun\n") -} - -func (slf *CTestModule1) Add(a int, b int) int { - return a + b -} - -func (slf *CTestService1) OnRun() bool { - testmodule := CTestModule1{} - - //可以设置自定义id - //testmodule.SetModuleId(PLAYERID) - - //添加module到slf对象中 - moduleId := slf.AddModule(&testmodule) - - //获取module对象 - pModule := slf.GetModuleById(moduleId) - //转换为CTestModule1类型 - ret := pModule.(*CTestModule1).Add(3, 4) - fmt.Printf("ret is %d\n", ret) - - time.Sleep(time.Second * 4) - //释放module - slf.ReleaseModule(moduleId) - - return false -} - -func main() { - node := originnode.NewOriginNode() - if node == nil { - return - } - node.SetupService(&CTestService1{}) - node.Init() - node.Start() -} -``` -执行结果如下: -``` -ret is 7 -CTestModule1::OnInit -CTestModule1::OnRun -CTestModule1::OnRun -CTestModule1::OnRun -CTestModule1::OnRun -CTestModule1::OnEndRun -``` -以上创建新的Module加入到当前服务对象中,可以获取释放动作。同样CTestModule1模块也可以加入子模块,使用方法一样。以上日志每秒钟CTestModule1::OnRun打印一次,4秒后ReleaseModule,对象被释放,执行CTestModule1::OnEndRun。 - -origin中其他重要服务: ---------------- -* github.com\duanhf2012\origin\sysservice集成了系统常用的服务 - * httpserverervice:提供对外的http服务 - * wsserverservice :websocket服务 - * logservice :日志服务 - -以上服务请参照github.com\duanhf2012\origin\Test目录使用方法 -* github.com\duanhf2012\origin\sysmodule集成了系统常用的Module - * DBModule: mysql数据库操作模块,支持异步调用 - * RedisModule: Redis操作模块,支持异步调用 - * LogModule: 日志模块,支持区分日志等级 - * HttpClientPoolModule:http客户端模块 - - - - diff --git a/Test/.vscode/launch.json b/Test/.vscode/launch.json deleted file mode 100644 index 87a1481..0000000 --- a/Test/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - - "configurations": [ - { - "name": "N_All", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceRoot}/main.go", - "env": { - "GOPATH":"${workspaceRoot}/../../../../../" - }, - "args": ["NodeId=1"], - "output": "./OriginServer.exe" - } - ] -} \ No newline at end of file diff --git a/Test/.vscode/settings.json b/Test/.vscode/settings.json deleted file mode 100644 index ef5cde8..0000000 --- a/Test/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "go.gopath": "${workspaceRoot}/../../../../../" -} diff --git a/Test/build.bat b/Test/build.bat deleted file mode 100644 index d55ef7c..0000000 --- a/Test/build.bat +++ /dev/null @@ -1,4 +0,0 @@ -SET CGO_ENABLED=0 -SET GOOS=linux -SET GOARCH=amd64 -go build -v \ No newline at end of file diff --git a/Test/config/cluster.json b/Test/config/cluster.json deleted file mode 100644 index 329fab7..0000000 --- a/Test/config/cluster.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "SubNet": [{ - "Remark": "Manual,Full,Auto", - "SubNetMode": "Full", - "SubNetName": "SubNet1", - "PublicServiceList": ["logiclog"], - "NodeList": [{ - "NodeID": 1, - "NodeName": "N_Node1", - "ServiceList": [ - "HttpServerService", - "SubNet1_Service", - "TcpSocketPbService", - "ls" - ], - "ClusterNode":["SubNet2.N_Node1","N_Node2"] - }, - { - "NodeID": 2, - "NodeName": "N_Node2", - "ServiceList": [ - "SubNet1_Service2" - ], - "ClusterNode":[] - } - ] - }, - - - { - "Remark": "Manual,Full,Auto", - "SubNetMode": "Full", - "SubNetName": "SubNet2", - "PublicServiceList": ["logiclog"], - "NodeList": [{ - "NodeID": 3, - "NodeName": "N_Node1", - "ServiceList": [ - "SubNet2_Service1" - ], - "ClusterNode":["URQ.N_Node1"] - } - ] - } - ] -} - - - - - - - - - diff --git a/Test/config/nodeconfig.json b/Test/config/nodeconfig.json deleted file mode 100644 index 35a3724..0000000 --- a/Test/config/nodeconfig.json +++ /dev/null @@ -1,50 +0,0 @@ -{ -"Public":{ - "LogLevel":1, - "HttpPort":9400, - "WSPort":9401, - "ListenPort":9412, - "CAFile":[ - { - "CertFile":"", - "KeyFile":"" - }, - { - "CertFile":"", - "KeyFile":"" - } - ], - - "Environment":"FS_Dev_LFY", - "IsListenLog":1, - "IsSendErrorMail":0 -}, - -"NodeList":[ -{ - "NodeID":1, - "NodeAddr": "127.0.0.1:8181", - "LogLevel":1, - "HttpPort":7001, - "WSPort":7000, - "CertFile":"", - "KeyFile":"" -}, -{ - "NodeID":2, - "NodeAddr": "127.0.0.1:8082" -} -, -{ - "NodeID":3, - "NodeAddr": "127.0.0.1:8083" -} -, -{ - "NodeID":40, - "NodeAddr": "127.0.0.1:8084", - "LogLevel":1 -} -] - -} \ No newline at end of file diff --git a/Test/logicservice/SubNet1_Service.go b/Test/logicservice/SubNet1_Service.go deleted file mode 100644 index 7dc01fe..0000000 --- a/Test/logicservice/SubNet1_Service.go +++ /dev/null @@ -1,122 +0,0 @@ -package logicservice - -import ( - "fmt" - "github.com/duanhf2012/origin/Test/msgpb" - "github.com/duanhf2012/origin/cluster" - "github.com/duanhf2012/origin/network" - "github.com/duanhf2012/origin/sysservice" - "github.com/golang/protobuf/proto" - "time" - - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysservice/originhttp" -) - -type SubNet1_Service struct { - service.BaseService -} - -func init() { - originnode.InitService(&SubNet1_Service{}) -} - -//OnInit ... -func (ws *SubNet1_Service) OnInit() error { - sysservice.GetTcpSocketPbService("ls").RegConnectEvent(ws.ConnEventHandler) - sysservice.GetTcpSocketPbService("ls").RegDisconnectEvent(ws.DisconnEventHandler) - sysservice.GetTcpSocketPbService("ls").RegExceptMessage(ws.ExceptMessage) - sysservice.GetTcpSocketPbService("ls").RegMessage(110, &msgpb.Test{}, ws.MessageHandler) - - /* - sysservice.GetTcpSocketPbService("lc").RegConnectEvent(ws.ConnEventHandler2) - sysservice.GetTcpSocketPbService("lc").RegDisconnectEvent(ws.DisconnEventHandler2) - sysservice.GetTcpSocketPbService("lc").RegExceptMessage(ws.ExceptMessage2) - sysservice.GetTcpSocketPbService("lc").RegMessage(110, &msgpb.Test{}, ws.MessageHandler2) -*/ - - - //originhttp.Post(" / aaa/bb/ :user/:pass/", ws.HTTP_UserIntegralInfo) - - originhttp.Get(" /aaa/bbb", ws.HTTP_UserIntegralInfo) - originhttp.SetStaticResource(originhttp.METHOD_GET, "/file/", "d:\\") - - return nil -} - -type InputData struct { - A1 int - A2 int -} - -//OnRun ... -func (ws *SubNet1_Service) OnRun() bool { - time.Sleep(time.Second * 10) - var cli network.TcpSocketClient - cli.Connect("127.0.0.1:9402") - test := msgpb.Test{} - test.AssistCount = proto.Int32(343) - - cli.SendMsg(110, &test) - cli.SendMsg(110, &test) - - var a InputData - var rs int - a.A1 = 3 - a.A2 = 4 - cluster.Call("SubNet1_Service2.RPC_Sub",&a,&rs) - fmt.Print(rs) - return false -} - -func (ws *SubNet1_Service) MessageHandler(clientid uint64, msgtype uint16, msg proto.Message) { - fmt.Print("recv:",clientid, ":", msg,"\n") - sysservice.GetTcpSocketPbService("ls").SendMsg(clientid,msgtype,msg) -/*test core dump - var a map[int]int - a[33] = 3 - fmt.Print(a[44]) - */ -} - -func (ws *SubNet1_Service) ConnEventHandler(clientid uint64) { - fmt.Print("connected..",clientid,"\n") -} - -func (ws *SubNet1_Service) DisconnEventHandler(clientid uint64) { - fmt.Print("disconnected..",clientid,"\n") -} - -func (ws *SubNet1_Service) ExceptMessage(clientid uint64, pPack *network.MsgBasePack, err error) { - fmt.Print("except..",clientid,",",pPack,"\n") -} - - - -/////////////////////////// - -func (ws *SubNet1_Service) MessageHandler2(clientid uint64, msgtype uint16, msg proto.Message) { - fmt.Print("recv:",clientid, ":", msg,"\n") - //pClient.SendMsg(msgtype,msg) - sysservice.GetTcpSocketPbService("ls").SendMsg(clientid,msgtype,msg) -} - -func (ws *SubNet1_Service) ConnEventHandler2(clientid uint64) { - fmt.Print("connected..",clientid,"\n") -} - -func (ws *SubNet1_Service) DisconnEventHandler2(clientid uint64) { - fmt.Print("disconnected..",clientid,"\n") -} - -func (ws *SubNet1_Service) ExceptMessage2(clientid uint64, pPack *network.MsgBasePack, err error) { - fmt.Print("except..",clientid,",",pPack,"\n") -} - - -func (slf *SubNet1_Service) HTTP_UserIntegralInfo(request *originhttp.HttpRequest, resp *originhttp.HttpRespone) error { - ret, ok := request.Query("a") - fmt.Print(ret, ok) - return nil -} \ No newline at end of file diff --git a/Test/main.go b/Test/main.go deleted file mode 100644 index 5290f40..0000000 --- a/Test/main.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - _ "github.com/duanhf2012/origin/Test/logicservice" - "github.com/duanhf2012/origin/cluster" - "github.com/duanhf2012/origin/network" - "github.com/duanhf2012/origin/originnode" - "github.com/duanhf2012/origin/sysservice" - "github.com/duanhf2012/origin/sysservice/originhttp" - "time" -) - - -type TcpSocketServerReciver struct { - -} - -func (slf *TcpSocketServerReciver) OnConnected(pClient *network.SClient){ - -} - -func (slf *TcpSocketServerReciver) OnDisconnect(pClient *network.SClient){ - -} - - - -func main() { - - node := originnode.NewOriginNode() - if node == nil { - return - } - - //打开Module死循环监控 - node.EnableMonitorModule(time.Minute*5) - originhttp.SetStaticResource(originhttp.METHOD_GET,"/img/","d:/") - nodeCfg, _ := cluster.ReadNodeConfig("./config/nodeconfig.json", cluster.GetNodeId()) - httpserver := originhttp.NewHttpServerService(nodeCfg.HttpPort) // http服务 - for _, ca := range nodeCfg.CAFile { - httpserver.SetHttps(ca.CertFile, ca.KeyFile) - } - - pTcpService := sysservice.NewTcpSocketPbService(":9412") - pTcpService.SetServiceName("ls") -/* - pTcpService2 := sysservice.NewTcpSocketPbService(":9005") - pTcpService2.SetServiceName("lc") -*/ - httpserver.SetPrintRequestTime(true) - - node.SetupService(httpserver,pTcpService) - - node.Init() - node.Start() -} diff --git a/Test/msgpb/gen.bat b/Test/msgpb/gen.bat deleted file mode 100644 index 546eec0..0000000 --- a/Test/msgpb/gen.bat +++ /dev/null @@ -1 +0,0 @@ -protoc --go_out=. test.proto \ No newline at end of file diff --git a/Test/msgpb/test.pb.go b/Test/msgpb/test.pb.go deleted file mode 100644 index 2fb5f59..0000000 --- a/Test/msgpb/test.pb.go +++ /dev/null @@ -1,132 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: test.proto - -package msgpb - -import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -//* -// @brief base_score_info -type Test struct { - WinCount *int32 `protobuf:"varint,1,opt,name=win_count,json=winCount" json:"win_count,omitempty"` - LoseCount *int32 `protobuf:"varint,2,opt,name=lose_count,json=loseCount" json:"lose_count,omitempty"` - ExceptionCount *int32 `protobuf:"varint,3,opt,name=exception_count,json=exceptionCount" json:"exception_count,omitempty"` - KillCount *int32 `protobuf:"varint,4,opt,name=kill_count,json=killCount" json:"kill_count,omitempty"` - DeathCount *int32 `protobuf:"varint,5,opt,name=death_count,json=deathCount" json:"death_count,omitempty"` - AssistCount *int32 `protobuf:"varint,6,opt,name=assist_count,json=assistCount" json:"assist_count,omitempty"` - Rating *int64 `protobuf:"varint,7,opt,name=rating" json:"rating,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Test) Reset() { *m = Test{} } -func (m *Test) String() string { return proto.CompactTextString(m) } -func (*Test) ProtoMessage() {} -func (*Test) Descriptor() ([]byte, []int) { - return fileDescriptor_c161fcfdc0c3ff1e, []int{0} -} - -func (m *Test) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Test.Unmarshal(m, b) -} -func (m *Test) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Test.Marshal(b, m, deterministic) -} -func (m *Test) XXX_Merge(src proto.Message) { - xxx_messageInfo_Test.Merge(m, src) -} -func (m *Test) XXX_Size() int { - return xxx_messageInfo_Test.Size(m) -} -func (m *Test) XXX_DiscardUnknown() { - xxx_messageInfo_Test.DiscardUnknown(m) -} - -var xxx_messageInfo_Test proto.InternalMessageInfo - -func (m *Test) GetWinCount() int32 { - if m != nil && m.WinCount != nil { - return *m.WinCount - } - return 0 -} - -func (m *Test) GetLoseCount() int32 { - if m != nil && m.LoseCount != nil { - return *m.LoseCount - } - return 0 -} - -func (m *Test) GetExceptionCount() int32 { - if m != nil && m.ExceptionCount != nil { - return *m.ExceptionCount - } - return 0 -} - -func (m *Test) GetKillCount() int32 { - if m != nil && m.KillCount != nil { - return *m.KillCount - } - return 0 -} - -func (m *Test) GetDeathCount() int32 { - if m != nil && m.DeathCount != nil { - return *m.DeathCount - } - return 0 -} - -func (m *Test) GetAssistCount() int32 { - if m != nil && m.AssistCount != nil { - return *m.AssistCount - } - return 0 -} - -func (m *Test) GetRating() int64 { - if m != nil && m.Rating != nil { - return *m.Rating - } - return 0 -} - -func init() { - proto.RegisterType((*Test)(nil), "msgpb.test") -} - -func init() { proto.RegisterFile("test.proto", fileDescriptor_c161fcfdc0c3ff1e) } - -var fileDescriptor_c161fcfdc0c3ff1e = []byte{ - // 178 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0x8c, 0x5d, 0x0a, 0x82, 0x40, - 0x14, 0x46, 0x99, 0xfc, 0x29, 0xaf, 0x51, 0xe0, 0x43, 0x08, 0x11, 0x59, 0x2f, 0xf9, 0xd4, 0x26, - 0xda, 0x81, 0x1b, 0x08, 0xb3, 0xc1, 0x86, 0x6c, 0x46, 0xbc, 0x37, 0x6c, 0xc5, 0xad, 0x23, 0x66, - 0xee, 0xe4, 0xe3, 0x77, 0xce, 0xe1, 0x03, 0x20, 0x89, 0x74, 0xee, 0x07, 0x43, 0x26, 0x8b, 0x5e, - 0xd8, 0xf6, 0xb7, 0xe3, 0x57, 0x40, 0x68, 0x69, 0xb6, 0x85, 0x64, 0x54, 0xfa, 0xda, 0x98, 0xb7, - 0xa6, 0x5c, 0x14, 0xa2, 0x8c, 0xaa, 0xc5, 0xa8, 0xf4, 0xc5, 0xee, 0x6c, 0x07, 0xd0, 0x19, 0x94, - 0xde, 0xce, 0x9c, 0x4d, 0x2c, 0x61, 0x7d, 0x82, 0xb5, 0xfc, 0x34, 0xb2, 0x27, 0x65, 0xfe, 0x0f, - 0x81, 0x6b, 0x56, 0x13, 0x9e, 0x7e, 0x9e, 0xaa, 0xeb, 0x7c, 0x13, 0xf2, 0x8f, 0x25, 0xac, 0xf7, - 0x90, 0xde, 0x65, 0x4d, 0x0f, 0xef, 0x23, 0xe7, 0xc1, 0x21, 0x0e, 0x0e, 0xb0, 0xac, 0x11, 0x15, - 0x92, 0x2f, 0x62, 0x57, 0xa4, 0xcc, 0x38, 0xd9, 0x40, 0x3c, 0xd4, 0xa4, 0x74, 0x9b, 0xcf, 0x0b, - 0x51, 0x06, 0x95, 0x5f, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x46, 0xa0, 0x89, 0x59, 0xfc, 0x00, - 0x00, 0x00, -} diff --git a/Test/msgpb/test.proto b/Test/msgpb/test.proto deleted file mode 100644 index 001411d..0000000 --- a/Test/msgpb/test.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto2"; - -package msgpb; - - -/** - * @brief base_score_info - */ -message test{ - optional int32 win_count = 1; // 玩家胜局局数 - optional int32 lose_count = 2; // 玩家负局局数 - optional int32 exception_count = 3; // 玩家异常局局数 - optional int32 kill_count = 4; // 总人头数 - optional int32 death_count = 5; // 总死亡数 - optional int32 assist_count = 6; // 总总助攻数 - optional int64 rating = 7; // 评价积分 -} - diff --git a/Test/workspace.code-workspace b/Test/workspace.code-workspace deleted file mode 100644 index cd39b81..0000000 --- a/Test/workspace.code-workspace +++ /dev/null @@ -1,13 +0,0 @@ -{ - "folders": [ - { - "path": "." - }, - { - "path": "D:\\GOPATH\\src\\github.com" - } - ], - "settings": { - "http.systemCertificates": false - } -} \ No newline at end of file diff --git a/Test2/Helloworld/.vscode/launch.json b/Test2/Helloworld/.vscode/launch.json deleted file mode 100644 index cf043fa..0000000 --- a/Test2/Helloworld/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - - "configurations": [ - { - "name": "N_All", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceRoot}/main.go", - "env": { - "GOPATH":"${workspaceRoot}/../../../../../../" - }, - "args": ["NodeId=1"], - "output": "./OriginServer.exe" - } - ] -} \ No newline at end of file diff --git a/Test2/Helloworld/.vscode/settings.json b/Test2/Helloworld/.vscode/settings.json deleted file mode 100644 index ef5cde8..0000000 --- a/Test2/Helloworld/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "go.gopath": "${workspaceRoot}/../../../../../" -} diff --git a/Test2/Helloworld/build.bat b/Test2/Helloworld/build.bat deleted file mode 100644 index d55ef7c..0000000 --- a/Test2/Helloworld/build.bat +++ /dev/null @@ -1,4 +0,0 @@ -SET CGO_ENABLED=0 -SET GOOS=linux -SET GOARCH=amd64 -go build -v \ No newline at end of file diff --git a/Test2/Helloworld/config/cluster.json b/Test2/Helloworld/config/cluster.json deleted file mode 100644 index 3980558..0000000 --- a/Test2/Helloworld/config/cluster.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "SubNet": [{ - "Remark": "Manual,Full,Auto", - "SubNetMode": "Full", - "SubNetName": "SubNet1", - "PublicServiceList": ["logiclog"], - "NodeList": [{ - "NodeID": 1, - "NodeName": "N_Node1", - "ServiceList": [ - "TestService1", - "TestService2", - "HttpServerService" - ], - "ClusterNode":[] - }, - { - "NodeID": 2, - "NodeName": "N_Node2", - "ServiceList": [ - "TestService3" - ], - "ClusterNode":[] - } - ] - } - ] -} - - - - - - - - - diff --git a/Test2/Helloworld/config/nodeconfig.json b/Test2/Helloworld/config/nodeconfig.json deleted file mode 100644 index a09df0a..0000000 --- a/Test2/Helloworld/config/nodeconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "Public": { - "LogLevel": 1, - "HttpPort": 9400, - "WSPort": 9500, - "Environment": "Test", - "IsListenLog": 1, - "IsSendErrorMail": 0 - }, - - "NodeList": [{ - "NodeID": 1, - "NodeAddr": "127.0.0.1:8081" - }, - { - "NodeID": 2, - "NodeAddr": "127.0.0.1:8082" - } - ] -} \ No newline at end of file diff --git a/Test2/Helloworld/main.go b/Test2/Helloworld/main.go deleted file mode 100644 index 8873e98..0000000 --- a/Test2/Helloworld/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "github.com/duanhf2012/origin/originnode" -) - -func main() { - - node := originnode.NewOriginNode() - if node == nil { - return - } - - node.Init() - node.Start() -} diff --git a/Test2/Helloworld/workspace.code-workspace b/Test2/Helloworld/workspace.code-workspace deleted file mode 100644 index 1283249..0000000 --- a/Test2/Helloworld/workspace.code-workspace +++ /dev/null @@ -1,10 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": { - "http.systemCertificates": false - } -} \ No newline at end of file diff --git a/_config.yml b/_config.yml deleted file mode 100644 index b849713..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-leap-day \ No newline at end of file diff --git a/cluster/cluster.go b/cluster/cluster.go index b8c3aea..cf4a077 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -1,723 +1,135 @@ package cluster import ( - "errors" "fmt" - "math/rand" - "net" - "os" - "sort" + "github.com/duanhf2012/originnet/log" + "github.com/duanhf2012/originnet/rpc" + "github.com/duanhf2012/originnet/service" "strings" - "time" - - "github.com/duanhf2012/origin/rpc" - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" ) -type RpcClient struct { - nodeid int - pclient *rpc.Client - serverAddr string +var configdir = "./config/" + +type SubNet struct { + NodeList []NodeInfo } -func (slf *RpcClient) IsConnected() bool { - return (slf.pclient != nil) && (slf.pclient.IsClosed() == false) +type NodeInfo struct { + NodeId int + ListenAddr string + NodeName string + ServiceList []string } -type CCluster struct { - port int - cfg *ClusterConfig - nodeclient map[int]*RpcClient - - reader net.Conn - writer net.Conn - - LocalRpcClient *rpc.Client - innerLocalServiceList map[string]bool +type NodeRpcInfo struct { + nodeinfo NodeInfo + client *rpc.Client } -func (slf *CCluster) ReadNodeInfo(nodeid int) error { - mapNodeData, err := ReadAllNodeConfig("./config/nodeconfig.json") + + +var cluster Cluster + +type Cluster struct { + localsubnet SubNet //本子网 + mapSubNetInfo map[string] SubNet //子网名称,子网信息 + + mapSubNetNodeInfo map[string]map[int]NodeInfo //map[子网名称]map[NodeId]NodeInfo + localSubNetMapNode map[int]NodeInfo //本子网内 map[NodeId]NodeInfo + localSubNetMapService map[string][]NodeInfo //本子网内所有ServiceName对应的结点列表 + localNodeMapService map[string]interface{} //本Node支持的服务 + localNodeInfo NodeInfo + + localNodeServiceCfg map[string]interface{} //map[servicename]数据 + + mapRpc map[int] NodeRpcInfo//nodeid + + rpcServer rpc.Server +} + + +func SetConfigDir(cfgdir string){ + configdir = cfgdir +} + +func (slf *Cluster) Init(currentNodeId int) error{ + //1.初始化配置 + err := slf.InitCfg(currentNodeId) if err != nil { return err } - slf.cfg, err = ReadCfg("./config/cluster.json", nodeid, mapNodeData) - if err != nil { - return err + slf.rpcServer.Init(slf) + + //2.建议rpc连接 + slf.mapRpc = map[int] NodeRpcInfo{} + for _,nodeinfo := range slf.localSubNetMapNode { + rpcinfo := NodeRpcInfo{} + rpcinfo.nodeinfo = nodeinfo + rpcinfo.client = &rpc.Client{} + if nodeinfo.NodeId == currentNodeId { + rpcinfo.client.Connect("localhost") + //rpcinfo.client.Connect(nodeinfo.ListenAddr) + }else{ + rpcinfo.client.Connect(nodeinfo.ListenAddr) + } + slf.mapRpc[nodeinfo.NodeId] = rpcinfo } return nil } -func (slf *CCluster) GetClusterClient(id int) *rpc.Client { - if id == GetNodeId() { - return slf.LocalRpcClient +func (slf *Cluster) FindRpcHandler(servicename string) rpc.IRpcHandler { + pService := service.GetService(servicename) + if pService == nil { + return nil } - v, ok := slf.nodeclient[id] + return pService.GetRpcHandler() +} + +func (slf *Cluster) Start() { + slf.rpcServer.Start(slf.localNodeInfo.ListenAddr) +} + +func GetCluster() *Cluster{ + return &cluster +} + +func (slf *Cluster) GetRpcClient(nodeid int) *rpc.Client { + c,ok := slf.mapRpc[nodeid] if ok == false { return nil } - return v.pclient + return c.client } -func (slf *CCluster) GetBindUrl() string { - return slf.cfg.currentNode.ServerAddr -} +func GetRpcClient(serviceMethod string) ([]*rpc.Client,error) { + serviceAndMethod := strings.Split(serviceMethod,".") + if len(serviceAndMethod)!=2 { + return nil,fmt.Errorf("servicemethod param %s is error!",serviceMethod) + } -func (slf *CCluster) AcceptRpc(tpcListen *net.TCPListener) error { - for { - conn, err := tpcListen.Accept() - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "tpcListen.Accept error:%v", err) - return err + //1.找到对应的rpcnodeid + var rpcClientList []*rpc.Client + nodeidList := GetCluster().GetNodeIdByService(serviceAndMethod[0]) + if len(nodeidList) ==0 { + return rpcClientList,fmt.Errorf("Cannot Find %s nodeid",serviceMethod) + } + + for _,nodeid:= range nodeidList { + pClient := GetCluster().GetRpcClient(nodeid) + if pClient==nil { + log.Error("Cannot connect node id %d",nodeid) + continue } - go rpc.ServeConn(conn) + rpcClientList = append(rpcClientList,pClient) } - return nil + return rpcClientList,nil } -func (slf *CCluster) ListenService() error { - - bindStr := slf.GetBindUrl() - parts := strings.Split(bindStr, ":") - if len(parts) < 2 { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "ListenService address %s is error.", bindStr) - os.Exit(1) - } - bindStr = "0.0.0.0:" + parts[1] - - // - tcpaddr, err := net.ResolveTCPAddr("tcp4", bindStr) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "ResolveTCPAddr error:%v", err) - os.Exit(1) - return err - } - - tcplisten, err2 := net.ListenTCP("tcp", tcpaddr) - if err2 != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "ListenTCP error:%v", err2) - os.Exit(1) - return err2 - } - go slf.AcceptRpc(tcplisten) - slf.ReSetLocalRpcClient() - return nil -} - -func (slf *CCluster) ReSetLocalRpcClient() { - slf.reader, slf.writer = net.Pipe() - go rpc.ServeConn(slf.reader) - slf.LocalRpcClient = rpc.NewClient(slf.writer,true) -} - -type CPing struct { - TimeStamp int64 -} - -type CPong struct { - TimeStamp int64 -} - -func (slf *CPing) Ping(ping *CPing, pong *CPong) error { - pong.TimeStamp = ping.TimeStamp - return nil -} - -func (slf *CCluster) GetClusterMode() string { - return slf.cfg.GetClusterMode() -} - -func (slf *CCluster) ConnService() error { - ping := CPing{0} - pong := CPong{0} - rpc.RegisterName("CPing", "", &ping) - - //连接集群服务器 - for _, nodeList := range slf.cfg.mapClusterNodeService { - for _, node := range nodeList { - if node.NodeID == slf.cfg.currentNode.NodeID { - continue - } - slf.nodeclient[node.NodeID] = &RpcClient{node.NodeID, nil, node.ServerAddr} - } - } - - //判断集群模式 - - for { - for _, rpcClient := range slf.nodeclient { - - //连接状态发送ping - if rpcClient.IsConnected() == true { - ping.TimeStamp = 0 - err := rpcClient.pclient.Call("CPing.Ping", &ping, &pong) - - if err != nil { - rpcClient.pclient.Close() - rpcClient.pclient = nil - continue - } - - continue - } - - //非连接状态重新连接 - if rpcClient.pclient != nil { - rpcClient.pclient.Close() - rpcClient.pclient = nil - } - - client, err := rpc.Dial("tcp", rpcClient.serverAddr) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_WARN, "Connect nodeid:%d,address:%s fail", rpcClient.nodeid, rpcClient.serverAddr) - continue - } - service.GetLogger().Printf(sysmodule.LEVER_INFO, "Connect nodeid:%d,address:%s succ", rpcClient.nodeid, rpcClient.serverAddr) - - v, _ := slf.nodeclient[rpcClient.nodeid] - v.pclient = client - } - - if slf.LocalRpcClient.IsClosed() { - slf.ReSetLocalRpcClient() - } - time.Sleep(time.Second * 4) - } - - return nil -} - -func (slf *CCluster) Init(currentNodeid int) error { - slf.nodeclient = make(map[int]*RpcClient) - - return slf.ReadNodeInfo(currentNodeid) -} - -func (slf *CCluster) Start() error { - service.InstanceServiceMgr().FetchService(slf.OnFetchService) - - //监听服务 - slf.ListenService() - - //集群 - go slf.ConnService() - - return nil -} - -//servicename.methodname -//_servicename.methodname -func (slf *CCluster) Call(NodeServiceMethod string, args interface{}, reply interface{}) error { - var callServiceName string - var serviceName string - nodeidList := slf.GetNodeList(NodeServiceMethod, &callServiceName, &serviceName) - if len(nodeidList) > 1 || len(nodeidList) < 1 { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s) find nodes count %d is error.", NodeServiceMethod, len(nodeidList)) - return fmt.Errorf("CCluster.Call(%s) find nodes count %d is error.", NodeServiceMethod, len(nodeidList)) - } - - nodeid := nodeidList[0] - if nodeid == GetNodeId() { - //判断服务是否已经完成初始化 - iService := service.InstanceServiceMgr().FindService(serviceName) - if iService == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s): NodeId %d cannot find service.", NodeServiceMethod, nodeid) - return fmt.Errorf("CCluster.Call(%s): NodeId %d cannot find service..", NodeServiceMethod, nodeid) - } - if iService.IsInit() == false { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s): NodeId %d is not init.", NodeServiceMethod, nodeid) - return fmt.Errorf("CCluster.Call(%s): NodeId %d is not init.", NodeServiceMethod, nodeid) - } - return slf.LocalRpcClient.Call(callServiceName, args, reply) - } else { - pclient := slf.GetClusterClient(nodeid) - if pclient == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s): NodeId %d is not find.", NodeServiceMethod, nodeid) - return fmt.Errorf("CCluster.Call(%s): NodeId %d is not find.", NodeServiceMethod, nodeid) - } - err := pclient.Call(callServiceName, args, reply) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s) is fail:%v.", callServiceName, err) - } - return err - } - - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s) fail.", NodeServiceMethod) - return fmt.Errorf("CCluster.Call(%s) fail.", NodeServiceMethod) -} - -func (slf *CCluster) GetNodeList(NodeServiceMethod string, rpcServerMethod *string, rpcServiceName *string) []int { - var nodename string - var servicename string - var methodname string - var nodeidList []int - - parts := strings.Split(NodeServiceMethod, ".") - if len(parts) == 2 { - servicename = parts[0] - methodname = parts[1] - } else if len(parts) == 3 { - nodename = parts[0] - servicename = parts[1] - methodname = parts[2] - } else { - return nodeidList - } - - if nodename == "" { - nodeidList = make([]int, 0) - if servicename[:1] == "_" { - servicename = servicename[1:] - nodeidList = append(nodeidList, GetNodeId()) - } else { - nodeidList = slf.cfg.GetIdByService(servicename, "") - } - } else { - nodeidList = slf.GetIdByNodeService(nodename, servicename) - } - - if rpcServiceName != nil { - *rpcServiceName = servicename - } - - if rpcServerMethod != nil { - *rpcServerMethod = servicename + "." + methodname - } - - return nodeidList -} - -//GetNodeIdByServiceName 根据服务名查找nodeid servicename服务名 bOnline是否需要查找在线服务 -func (slf *CCluster) GetNodeIdByServiceName(servicename string, bOnline bool) []int { - nodeIDList := slf.cfg.GetIdByService(servicename, "") - - if bOnline { - ret := make([]int, 0, len(nodeIDList)) - for _, nodeid := range nodeIDList { - if slf.CheckNodeIsConnectedByID(nodeid) { - ret = append(ret, nodeid) - } - } - return ret - } - - return nodeIDList -} - -//根据Service获取负载均衡信息 -//负载均衡的策略是从配置获取所有配置了该服务的NodeId 并按NodeId排序 每个node负责处理数组index所在的那一部分 -func (slf *CCluster) GetBalancingInfo(currentNodeId int, servicename string, inSubNet bool) (*BalancingInfo, error) { - subNetName := "" - if inSubNet { - if node, ok := slf.cfg.mapIdNode[currentNodeId]; ok { - subNetName = node.SubNetName - } else { - return nil, fmt.Errorf("[cluster.GetBalancingInfo] cannot find node %d", currentNodeId) - } - } - lst := slf.cfg.GetIdByService(servicename, subNetName) - // if len(lst) <= 0 { - // return nil, fmt.Errorf("[cluster.GetBalancingInfo] cannot find service %s in any node", servicename) - // } - sort.Ints(lst) - ret := &BalancingInfo{ - NodeId: currentNodeId, - ServiceName: servicename, - TotalNum: len(lst), - MyIndex: -1, - NodeList: lst, - } - if _, ok := slf.cfg.mapIdNode[currentNodeId]; ok { - for i, v := range lst { - if v == currentNodeId { - ret.MyIndex = i - break - } - } - } - return ret, nil -} - -func (slf *CCluster) CheckNodeIsConnectedByID(nodeid int) bool { - if nodeid == GetNodeId() { - return true - } - - pclient := slf.GetRpcClientByNodeId(nodeid) - if pclient == nil { - return false - } - - return pclient.IsConnected() -} - -func (slf *CCluster) GetRpcClientByNodeId(nodeid int) *RpcClient { - - pclient, ok := slf.nodeclient[nodeid] - if ok == false { - return nil - } - - return pclient -} - -func (slf *CCluster) Go(bCast bool, NodeServiceMethod string, args interface{}, queueModle bool) error { - return slf.goImpl(bCast, NodeServiceMethod, args, queueModle, true) -} - - - -func (slf *CCluster) goImpl(bCast bool, NodeServiceMethod string, args interface{}, queueModle bool, log bool) error { - var callServiceName string - var serviceName string - nodeidList := slf.GetNodeList(NodeServiceMethod, &callServiceName, &serviceName) - if len(nodeidList) < 1 { - err := fmt.Errorf("CCluster.Go(%s) not find nodes.", NodeServiceMethod) - if log { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, err.Error()) - } - return err - } - - if bCast == false && len(nodeidList) > 1 { - return fmt.Errorf("CCluster.Go(%s) find more nodes", NodeServiceMethod) - } - - for _, nodeid := range nodeidList { - if nodeid == GetNodeId() { - iService := service.InstanceServiceMgr().FindService(serviceName) - if iService == nil { - return fmt.Errorf("CCluster.Go(%s) cannot find service %s", NodeServiceMethod, serviceName) - } - if iService.IsInit() == false { - err := fmt.Errorf("CCluster.Call(%s): NodeId %d is not init.", NodeServiceMethod, nodeid) - if log { - service.GetLogger().Printf(sysmodule.LEVER_WARN, err.Error()) - } - return err - } - - replyCall := slf.LocalRpcClient.Go(callServiceName, args, nil, nil, queueModle) - if replyCall.Error != nil { - err := fmt.Errorf("CCluster.Go(%s) fail:%v.", NodeServiceMethod, replyCall.Error) - if log { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, err.Error()) - } else { - return err - } - } - } else { - pclient := slf.GetClusterClient(nodeid) - if pclient == nil { - err := fmt.Errorf("CCluster.Go(%s) NodeId %d not find client", NodeServiceMethod, nodeid) - if log { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, err.Error()) - } - return err - } - replyCall := pclient.Go(callServiceName, args, nil, nil, queueModle) - if replyCall.Error != nil { - err := fmt.Errorf("CCluster.Go(%s) fail:%v.", NodeServiceMethod, replyCall.Error) - if log { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, err.Error()) - } - return err - } - } - } - - return nil -} - -func (slf *CCluster) CallNode(nodeid int, servicemethod string, args interface{}, reply interface{}) error { - pclient := slf.GetClusterClient(nodeid) - if pclient == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.CallNode(%d,%s) NodeId not find client", nodeid, servicemethod) - return fmt.Errorf("Call: NodeId %d is not find.", nodeid) - } - - err := pclient.Call(servicemethod, args, reply) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.CallNode(%d,%s) fail:%v", nodeid, servicemethod, err) - } - - return err -} - -func (slf *CCluster) GoNode(nodeid int, args interface{}, servicemethod string, queueModle bool) error { - pclient := slf.GetClusterClient(nodeid) - if pclient == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.GoNode(%d,%s) NodeId not find client", nodeid, servicemethod) - return fmt.Errorf("CCluster.GoNode(%d,%s) NodeId not find client", nodeid, servicemethod) - } - - replyCall := pclient.Go(servicemethod, args, nil, nil, queueModle) - if replyCall.Error != nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.GoNode(%d,%s) fail:%v", nodeid, servicemethod, replyCall.Error) - } - - return replyCall.Error - -} - -func (ws *CCluster) OnFetchService(iservice service.IService) error { - rpc.RegisterName(iservice.GetServiceName(), "RPC_", iservice) - return nil -} - -func (slf *CCluster) CallRandomService(NodeServiceMethod string, args interface{}, reply interface{}) error { - var servicename string - parts := strings.Split(NodeServiceMethod, ".") - if len(parts) == 2 { - servicename = parts[0] - } else { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.CallRandomService(%s) method err", NodeServiceMethod) - return fmt.Errorf("CCluster.GoNode(%s) NodeId method err", NodeServiceMethod) - } - - nodeList := slf.GetNodeIdByServiceName(servicename, true) - if len(nodeList) < 1 { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.CallRandomService(%s) no node is online", NodeServiceMethod) - return fmt.Errorf("CCluster.GoNode(%s) no node is online", NodeServiceMethod) - } - - nodeIndex := rand.Intn(len(nodeList)) - nodeID := nodeList[nodeIndex] - - return slf.CallNode(nodeID, NodeServiceMethod, args, reply) -} - -func (slf *CCluster) GoRandomService(NodeServiceMethod string, args interface{}, queueModle bool) error { - var servicename string - parts := strings.Split(NodeServiceMethod, ".") - if len(parts) == 2 { - servicename = parts[0] - } else { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.CallRandomService(%s) method err", NodeServiceMethod) - return fmt.Errorf("CCluster.GoNode(%s) NodeId method err", NodeServiceMethod) - } - - nodeList := slf.GetNodeIdByServiceName(servicename, true) - if len(nodeList) < 1 { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.CallRandomService(%s) no node is online", NodeServiceMethod) - return fmt.Errorf("CCluster.GoNode(%s) no node is online", NodeServiceMethod) - } - - nodeIndex := rand.Intn(len(nodeList)) - nodeID := nodeList[nodeIndex] - - return slf.GoNode(nodeID, args, NodeServiceMethod, queueModle) -} - -//向远程服务器调用 -//Node.servicename.methodname -//servicename.methodname -func Call(NodeServiceMethod string, args interface{}, reply interface{}) error { - return InstanceClusterMgr().Call(NodeServiceMethod, args, reply) -} - -func (slf *CCluster) CallEx(NodeServiceMethod string, args interface{}, reply interface{}) *RpcCallResult { - return slf.rawcall(NodeServiceMethod, args, reply, false) -} - -type RpcCallResult struct { - chanRet chan *rpc.Call - err error - rets *rpc.Call -} - -func (slf *RpcCallResult) Make() { - slf.chanRet = make(chan *rpc.Call, 1) - slf.rets = nil -} - -func (slf *RpcCallResult) WaitReturn(waittm time.Duration) error { - if slf.chanRet == nil { - return errors.New("cannot make rpccallresult") - } - - if waittm <= 0 { - select { - case ret := <-slf.chanRet: - return ret.Error - } - } else { - // - select { - case ret := <-slf.chanRet: - return ret.Error - case <-time.After(waittm): - return errors.New("is time out") - } - } - - return errors.New("unknow error.") -} - -func (slf *CCluster) rawcall(NodeServiceMethod string, args interface{}, reply interface{}, queueModle bool) *RpcCallResult { - var rpcRet RpcCallResult - rpcRet.Make() - - var callServiceName string - var serviceName string - nodeidList := slf.GetNodeList(NodeServiceMethod, &callServiceName, &serviceName) - if len(nodeidList) > 1 || len(nodeidList) < 1 { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s) find nodes count %d is error.", NodeServiceMethod, len(nodeidList)) - rpcRet.err = fmt.Errorf("CCluster.Call(%s) find nodes count %d is error.", NodeServiceMethod, len(nodeidList)) - return &rpcRet - } - - nodeid := nodeidList[0] - if nodeid == GetNodeId() { - //判断服务是否已经完成初始化 - iService := service.InstanceServiceMgr().FindService(serviceName) - if iService == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s): NodeId %d cannot find service.", NodeServiceMethod, nodeid) - rpcRet.err = fmt.Errorf("CCluster.Call(%s): NodeId %d cannot find service..", NodeServiceMethod, nodeid) - return &rpcRet - } - - if iService.IsInit() == false { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s): NodeId %d is not init.", NodeServiceMethod, nodeid) - rpcRet.err = fmt.Errorf("CCluster.Call(%s): NodeId %d is not init.", NodeServiceMethod, nodeid) - return &rpcRet - } - - rpcRet.rets = slf.LocalRpcClient.Go(callServiceName, args, reply, rpcRet.chanRet, queueModle) - return &rpcRet - } - - pclient := slf.GetClusterClient(nodeid) - if pclient == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "CCluster.Call(%s): NodeId %d is not find.", NodeServiceMethod, nodeid) - rpcRet.err = fmt.Errorf("CCluster.Call(%s): NodeId %d is not find.", NodeServiceMethod, nodeid) - return &rpcRet - } - - rpcRet.rets = pclient.Go(callServiceName, args, reply, rpcRet.chanRet, queueModle) - - return &rpcRet - -} - -func CallEx(NodeServiceMethod string, args interface{}, reply interface{}) *RpcCallResult { - return InstanceClusterMgr().rawcall(NodeServiceMethod, args, reply, false) -} - -func CallNode(NodeId int, servicemethod string, args interface{}, reply interface{}) error { - return InstanceClusterMgr().CallNode(NodeId, servicemethod, args, reply) -} - -func GoNode(NodeId int, servicemethod string, args interface{}) error { - return InstanceClusterMgr().GoNode(NodeId, args, servicemethod, false) -} - -func Go(NodeServiceMethod string, args interface{}) error { - return InstanceClusterMgr().Go(false, NodeServiceMethod, args, false) -} - -func CastGo(NodeServiceMethod string, args interface{}) error { - return InstanceClusterMgr().Go(true, NodeServiceMethod, args, false) -} - -func GoNodeQueue(NodeId int, servicemethod string, args interface{}) error { - return InstanceClusterMgr().GoNode(NodeId, args, servicemethod, true) -} - -func GoQueue(NodeServiceMethod string, args interface{}) error { - return InstanceClusterMgr().Go(false, NodeServiceMethod, args, true) -} - -//在GoQueue的基础上增加是否写日志参数 -func GoQueueEx(NodeServiceMethod string, args interface{}, log bool) error { - return InstanceClusterMgr().goImpl(false, NodeServiceMethod, args, true, log) -} - -func CastGoQueue(NodeServiceMethod string, args interface{}) error { - return InstanceClusterMgr().Go(true, NodeServiceMethod, args, true) -} - -//GetNodeIdByServiceName 根据服务名查找nodeid serviceName服务名 bOnline是否需要查找在线服务 -func GetNodeIdByServiceName(serviceName string, bOnline bool) []int { - return InstanceClusterMgr().GetNodeIdByServiceName(serviceName, bOnline) -} - -//获取服务的负载均衡信息 -//负载均衡的策略是从配置获取所有配置了该服务的NodeId 并按NodeId排序 每个node负责处理数组index所在的那一部分 -func GetBalancingInfo(currentNodeId int, servicename string, inSubNet bool) (*BalancingInfo, error) { - return InstanceClusterMgr().GetBalancingInfo(currentNodeId, servicename, inSubNet) -} - -//随机选择在线的node发送 -func CallRandomService(NodeServiceMethod string, args interface{}, reply interface{}) error { - return InstanceClusterMgr().CallRandomService(NodeServiceMethod, args, reply) -} -func GoRandomService(NodeServiceMethod string, args interface{}) error { - return InstanceClusterMgr().GoRandomService(NodeServiceMethod, args, false) -} -func GoRandomServiceQueue(NodeServiceMethod string, args interface{}) error { - return InstanceClusterMgr().GoRandomService(NodeServiceMethod, args, true) -} - -var _self *CCluster - -func InstanceClusterMgr() *CCluster { - if _self == nil { - _self = new(CCluster) - _self.innerLocalServiceList = make(map[string]bool) - return _self - } - return _self -} - -func (slf *CCluster) GetIdByNodeService(NodeName string, serviceName string) []int { - return slf.cfg.GetIdByNodeService(NodeName, serviceName) -} - -func (slf *CCluster) HasLocalService(serviceName string) bool { - _, ok := slf.innerLocalServiceList[serviceName] - return slf.cfg.HasLocalService(serviceName) || ok -} - -func (slf *CCluster) HasInit(serviceName string) bool { - return slf.cfg.HasLocalService(serviceName) -} - -func GetNodeId() int { - return _self.cfg.currentNode.NodeID -} - -func (slf *CCluster) AddLocalService(iservice service.IService) { - servicename := fmt.Sprintf("%T", iservice) - parts := strings.Split(servicename, ".") - if len(parts) != 2 { - service.GetLogger().Printf(service.LEVER_ERROR, "BaseService.Init: service name is error: %q", servicename) - } - - servicename = parts[1] - slf.innerLocalServiceList[servicename] = true -} - -func GetNodeName(nodeid int) string { - return _self.cfg.GetNodeNameByNodeId(nodeid) -} - -func DynamicCall(address string, serviceMethod string, args interface{}, reply interface{}) error { - rpcClient, err := rpc.DialTimeOut("tcp", address, time.Second*1) - if err != nil { - return err - } - defer rpcClient.Close() - err = rpcClient.Call(serviceMethod, args, reply) - if err != nil { - return err - } - - return nil -} +func GetRpcServer() *rpc.Server{ + return &cluster.rpcServer +} \ No newline at end of file diff --git a/cluster/config.go b/cluster/config.go deleted file mode 100644 index 9ab4561..0000000 --- a/cluster/config.go +++ /dev/null @@ -1,341 +0,0 @@ -package cluster - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "strings" -) - -//负载均衡信息 -type BalancingInfo struct { - NodeId int //我的nodeId - ServiceName string //负载均衡的ServiceName - - TotalNum int //总共有多少个协同Node - MyIndex int //负责的index [0, TotalNum) - NodeList []int //所有协同的node列表 按NodeId升序排列 -} - -//判断hash后的Id是否命中我的NodeId -func (slf *BalancingInfo) Hit(hashId int) bool { - if hashId >= 0 && slf.TotalNum > 0 && slf.MyIndex >= 0 { - return hashId%slf.TotalNum == slf.MyIndex - } - return false -} - -//判断命中的NodeId,-1表示无法取得 -func (slf *BalancingInfo) GetHitNodeId(hashId int) int { - if hashId >= 0 && slf.TotalNum > 0 { - if idx := hashId % slf.TotalNum; idx >= 0 && idx < len(slf.NodeList) { - return slf.NodeList[idx] - } - } - return -1 -} - -type CNodeCfg struct { - NodeID int - NodeName string - ServiceList []string - ClusterNode []string -} - -type CNode struct { - SubNetName string - NodeID int - NodeName string //SubNetName.NodeName - ServerAddr string - ServiceList map[string]bool -} - -type SubNetNodeInfo struct { - SubNetMode string - SubNetName string - PublicServiceList []string - NodeList []CNodeCfg //配置列表 - - //mapClusterNodeService map[string][]CNode //map[nodename] []CNode - //mapClusterServiceNode map[string][]CNode //map[servicename] []CNode -} - -type ClusterConfig struct { - SubNet []SubNetNodeInfo - //CurrentSubNetIdx int - mapIdNode map[int]CNode - currentNode CNode //当前node - - mapClusterNodeService map[string][]CNode //map[nodename] []CNode - mapClusterServiceNode map[string][]CNode //map[servicename] []CNode -} - -func GenNodeName(subnetName string, nodename string) string { - parts := strings.Split(nodename, ".") - if len(parts) < 2 { - return subnetName + "." + nodename - } - - return nodename -} - -func AddCluster(clusterNodeNameList *[]string, nodename string) bool { - for _, n := range *clusterNodeNameList { - if n == nodename { - return false - } - } - - *clusterNodeNameList = append(*clusterNodeNameList, nodename) - return true -} - -func ReadCfg(path string, nodeid int, mapNodeData map[int]NodeData) (*ClusterConfig, error) { - clsCfg := &ClusterConfig{} - clsCfg.mapIdNode = map[int]CNode{} - clsCfg.mapClusterNodeService = make(map[string][]CNode, 1) - clsCfg.mapClusterServiceNode = make(map[string][]CNode, 1) - - //1.加载解析配置 - d, err := ioutil.ReadFile(path) - if err != nil { - fmt.Printf("Read File %s Error!", path) - return nil, err - } - - err = json.Unmarshal(d, clsCfg) - if err != nil { - fmt.Printf("Read File %s ,%s Error!", path, err) - return nil, err - } - - //存储所有的nodeid对应cnode信息 - var clusterNodeNameList []string - for _, c := range clsCfg.SubNet { - for _, v := range c.NodeList { - mapservice := make(map[string]bool, 1) - for _, s := range v.ServiceList { - mapservice[s] = true - } - nodeData, ok := mapNodeData[v.NodeID] - if ok == false { - return nil, errors.New(fmt.Sprintf("Cannot find node id %d in nodeconfig.json file!", v.NodeID)) - } - - node := CNode{c.SubNetName, v.NodeID, c.SubNetName + "." + v.NodeName, nodeData.NodeAddr, mapservice} - clsCfg.mapIdNode[v.NodeID] = node - - if v.NodeID == nodeid { - clsCfg.currentNode = node - for _, servicename := range c.PublicServiceList { - clsCfg.currentNode.ServiceList[servicename] = true - } - - for _, nodename := range v.ClusterNode { - AddCluster(&clusterNodeNameList, GenNodeName(c.SubNetName, nodename)) - } - AddCluster(&clusterNodeNameList, GenNodeName(c.SubNetName, v.NodeName)) - } - } - } - - if clsCfg.currentNode.NodeID == 0 { - return nil, errors.New(fmt.Sprintf("Cannot find NodeId %d in cluster.json!", nodeid)) - } - - //如果集群是FULL模式 - if strings.ToUpper(clsCfg.GetClusterMode()) == "FULL" { - for _, subnet := range clsCfg.SubNet { - if subnet.SubNetName == clsCfg.currentNode.SubNetName { - for _, nodes := range subnet.NodeList { - AddCluster(&clusterNodeNameList, GenNodeName(subnet.SubNetName, nodes.NodeName)) - } - } - } - } - - for _, clusternodename := range clusterNodeNameList { - for _, c := range clsCfg.SubNet { - for _, nodecfg := range c.NodeList { - if clusternodename != c.SubNetName+"."+nodecfg.NodeName { - continue - } - n, ok := clsCfg.mapIdNode[nodecfg.NodeID] - if ok == false { - return nil, errors.New(fmt.Sprintf("Cannot find NodeId %d in cluster.json!", nodecfg.NodeID)) - } - clsCfg.mapClusterNodeService[nodecfg.NodeName] = append(clsCfg.mapClusterNodeService[nodecfg.NodeName], n) - for _, sname := range nodecfg.ServiceList { - clsCfg.mapClusterServiceNode[sname] = append(clsCfg.mapClusterServiceNode[sname], n) - } - } - } - } - - return clsCfg, nil -} - -func (slf *ClusterConfig) GetIdByService(serviceName, subNetName string) []int { - var nodeidlist = []int{} - - nodeList, ok := slf.mapClusterServiceNode[serviceName] - if ok == true { - nodeidlist = make([]int, 0, len(nodeList)) - for _, v := range nodeList { - if subNetName == "" || subNetName == v.SubNetName { - nodeidlist = append(nodeidlist, v.NodeID) - } - } - } - - return nodeidlist -} - -func (slf *ClusterConfig) GetIdByNodeService(NodeName string, serviceName string) []int { - var nodeidlist []int - nodeidlist = make([]int, 0) - - if NodeName == slf.currentNode.NodeName { - nodeidlist = append(nodeidlist, slf.currentNode.NodeID) - } - - v, ok := slf.mapClusterNodeService[NodeName] - if ok == false { - return nodeidlist - } - - for _, n := range v { - _, ok = n.ServiceList[serviceName] - if ok == true { - nodeidlist = append(nodeidlist, n.NodeID) - } - } - - return nodeidlist -} - -func (slf *ClusterConfig) HasLocalService(serviceName string) bool { - _, ok := slf.currentNode.ServiceList[serviceName] - return ok == true -} - -func IsExistsNode(nodelist []CNode, pNode *CNode) bool { - for _, node := range nodelist { - if node.NodeID == pNode.NodeID { - return true - } - } - - return false -} - -func (slf *ClusterConfig) GetNodeNameByNodeId(nodeid int) string { - node, ok := slf.mapIdNode[nodeid] - if ok == false { - return "" - } - - return node.NodeName -} - -func (slf *ClusterConfig) GetClusterMode() string { - //SubNet []SubNetNodeInfo - for _, subnet := range slf.SubNet { - if subnet.SubNetName == slf.currentNode.SubNetName { - return subnet.SubNetMode - } - } - - return "" -} - -type CACfg struct { - CertFile string - KeyFile string -} - -//NodeConfig ... -type NodeData struct { - NodeID int - LogLevel uint - HttpPort uint16 - WSPort uint16 - ListenPort uint16 - NodeAddr string - CAFile []CACfg - - Environment string - IsListenLog int - IsSendErrorMail int -} - -type NodeConfig struct { - Public NodeData - NodeList []NodeData -} - -//ReadNodeConfig ... -func ReadAllNodeConfig(path string) (map[int]NodeData, error) { - c := &NodeConfig{} - d, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - err = json.Unmarshal(d, c) - if err != nil { - return nil, err - } - - var mapNodeData map[int]NodeData - mapNodeData = map[int]NodeData{} - - //data = c.Public - for _, v := range c.NodeList { - mapNodeData[v.NodeID] = v - } - - return mapNodeData, nil -} -func ReadNodeConfig(path string, nodeid int) (*NodeData, error) { - - c := &NodeConfig{} - d, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - err = json.Unmarshal(d, c) - if err != nil { - return nil, err - } - - var data NodeData - data = c.Public - for _, v := range c.NodeList { - if v.NodeID == nodeid { - data = v - if v.Environment == "" && c.Public.Environment != "" { - data.Environment = c.Public.Environment - } - if len(v.CAFile) == 0 && len(c.Public.CAFile) != 0 { - data.CAFile = c.Public.CAFile - } - - if v.HttpPort == 0 && c.Public.HttpPort != 0 { - data.HttpPort = c.Public.HttpPort - } - if v.WSPort == 0 && c.Public.WSPort != 0 { - data.WSPort = c.Public.WSPort - } - if v.LogLevel == 0 && c.Public.LogLevel != 0 { - data.LogLevel = c.Public.LogLevel - } - if v.IsListenLog == 0 && c.Public.IsListenLog != 0 { - data.IsListenLog = c.Public.IsListenLog - } - break - } - } - - return &data, nil -} diff --git a/cluster/parsecfg.go b/cluster/parsecfg.go new file mode 100644 index 0000000..0c82a2c --- /dev/null +++ b/cluster/parsecfg.go @@ -0,0 +1,192 @@ +package cluster + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" +) + +func (slf *Cluster) ReadClusterConfig(filepath string) (*SubNet,error) { + c := &SubNet{} + d, err := ioutil.ReadFile(filepath) + if err != nil { + return nil, err + } + err = json.Unmarshal(d, c) + if err != nil { + return nil, err + } + + return c,nil +} + + +func (slf *Cluster) ReadServiceConfig(filepath string) (map[string]interface{},error) { + c := map[string]interface{}{} + + d, err := ioutil.ReadFile(filepath) + if err != nil { + return nil, err + } + err = json.Unmarshal(d, &c) + if err != nil { + return nil, err + } + + return c,nil + +} + +func (slf *Cluster) ReadAllSubNetConfig() error { + clusterCfgPath :=strings.TrimRight(configdir,"/") +"/cluster" + fileInfoList,err := ioutil.ReadDir(clusterCfgPath) + if err != nil { + return err + } + slf.mapSubNetInfo =map[string] SubNet{} + for _,f := range fileInfoList{ + if f.IsDir() == true { + subnetinfo,err:=slf.ReadClusterConfig(strings.TrimRight(strings.TrimRight(clusterCfgPath,"/"),"\\")+"/"+f.Name()+"/"+"cluster.json") + if err != nil { + return err + } + slf.mapSubNetInfo[f.Name()] = *subnetinfo + } + } + + + return nil +} + +func (slf *Cluster) ReadLocalSubNetServiceConfig(subnet string) error { + clusterCfgPath :=strings.TrimRight(configdir,"/") +"/cluster" + fileInfoList,err := ioutil.ReadDir(clusterCfgPath) + if err != nil { + return err + } + slf.mapSubNetInfo =map[string] SubNet{} + for _,f := range fileInfoList{ + if f.IsDir() == true && f.Name()==subnet{ //同一子网 + localNodeServiceCfg,err:=slf.ReadServiceConfig(strings.TrimRight(strings.TrimRight(clusterCfgPath,"/"),"\\")+"/"+f.Name()+"/"+"service.json") + if err != nil { + return err + } + slf.localNodeServiceCfg =localNodeServiceCfg + } + } + + return nil +} + + + +func (slf *Cluster) InitCfg(currentNodeId int) error{ + //mapSubNetInfo := map[string] SubNet{} //子网名称,子网信息 + mapSubNetNodeInfo := map[string]map[int]NodeInfo{} //map[子网名称]map[NodeId]NodeInfo + localSubNetMapNode := map[int]NodeInfo{} //本子网内 map[NodeId]NodeInfo + localSubNetMapService := map[string][]NodeInfo{} //本子网内所有ServiceName对应的结点列表 + localNodeMapService := map[string]interface{}{} //本Node支持的服务 + localNodeInfo := NodeInfo{} + + + err := slf.ReadAllSubNetConfig() + + //分析配置 + var localSubnetName string + for subnetName,subnetInfo := range slf.mapSubNetInfo { + for _,nodeinfo := range subnetInfo.NodeList { + //装载slf.mapNodeInfo + _,ok := mapSubNetNodeInfo[subnetName] + if ok == false { + mapnodeInfo := make(map[int]NodeInfo,1) + mapnodeInfo[nodeinfo.NodeId] = nodeinfo + mapSubNetNodeInfo[subnetName] = mapnodeInfo + }else{ + mapSubNetNodeInfo[subnetName][nodeinfo.NodeId] = nodeinfo + } + + //判断本进程的子网 + if nodeinfo.NodeId == currentNodeId { + localSubnetName = subnetName + } + } + } + + + //装载 + subnet,ok := slf.mapSubNetInfo[localSubnetName] + if ok == false { + return fmt.Errorf("NodeId %d not in any subnet",currentNodeId) + } + + for _,nodeinfo := range subnet.NodeList { + localSubNetMapNode[nodeinfo.NodeId] = nodeinfo + + //装载本Node进程所有的服务 + if nodeinfo.NodeId == currentNodeId { + for _,s := range nodeinfo.ServiceList { + servicename := s + if strings.Index(s,"_") == 0 { + servicename = s[1:] + } + localNodeMapService[servicename] = nil + } + localNodeInfo = nodeinfo + } + + for _,s := range nodeinfo.ServiceList { + //以_打头的,表示只在本机进程,不对整个子网开发 + if strings.Index(s,"_") == 0 { + continue + } + + if _,ok := localSubNetMapService[s];ok== true{ + localSubNetMapService[s] = []NodeInfo{} + } + localSubNetMapService[s] = append(localSubNetMapService[s],nodeinfo) + } + } + if localNodeInfo.NodeId == 0 { + return fmt.Errorf("Canoot find NodeId %d not in any config file.",currentNodeId) + } + + + slf.mapSubNetNodeInfo=mapSubNetNodeInfo + slf.localSubNetMapNode=localSubNetMapNode + slf.localSubNetMapService = localSubNetMapService + slf.localNodeMapService = localNodeMapService + slf.localsubnet = subnet + slf.localNodeInfo =localNodeInfo + + //读取服务 + + return err +} + + +func (slf *Cluster) IsConfigService(servicename string) bool { + _,ok := slf.localNodeMapService[servicename] + return ok +} + +func (slf *Cluster) GetNodeIdByService(servicename string) []int{ + var nodelist []int + nodeInfoList,ok := slf.localSubNetMapService[servicename] + if ok == true { + for _,node := range nodeInfoList { + nodelist = append(nodelist,node.NodeId) + } + } + + return nodelist +} + +func (slf *Cluster) GetServiceCfg(servicename string) interface{}{ + v,ok := slf.localNodeServiceCfg[servicename] + if ok == false{ + return nil + } + + return v +} diff --git a/doc/origin分布式框架.pptx b/doc/origin分布式框架.pptx deleted file mode 100644 index 5d7f75446b7e06d9b05a7085cbd273c8ba18e1cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 333086 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

!)+C%l{-tv%K^-YVT+ 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 s%`TTShS{^a$ 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

Z(A8RB!Ijc~ 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[Kl<4|1y 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

Y zsz@mN?eZ*JH`+?Se!ui?Wu6`tpTW?Se8|JZj-#&Q+@baHX582J{Lo@OkF8~_9>4GJ z_<0?jXOfk`9MC>Ee9V$wZ1t$ut?^zfUmdRC2x#Z)y1hE_VXavW`u&v8zp_{>p?m4c zP_Oot>R^WBe7^4vz$qSESBPJucD#qa@$3N(ntZzSVgR_9#EVCDDQBUf9!Pv$h!Ui| zsIi<_BmdTpyQTU4lJH{L<*cSZJB@>}=>R6F25gs>^StvvTGO|$7D92Em5uJ%t|u{U z`#|Yb_^P&%0K2VvQPruf8n6LR7^K?Uqq!5avH|$6i!N+Lh2i|b4ycoCQZvUGZfIx6 z+P|CmHcC<>Imsl=Od@{ys8ya`MFk()Wb=+9?F0czQp91S@UwB$5<%zdc=1==nw^?e zrZIW9>3=-0#Jc-ttc9vrRHxmobwd6&2cy)1Y8}%J%mj&}X));UC!B0D3^||-h@cb0 z$SOxs{EslEVI1l49nJ(ZiEOJpj;X(R{8DfB&CP1Em?QG(oSMqs7AY(LaH;yl-tlXu z0>a7s)74y9W$d=Y z*KTs^*CrZ3PfoQ*2enx&reqtmOECcmO4$G!F8IwZp(W%-2wk!5)VAjWiyNQ+L=hQJ z;>1&>j8hnD&|H&O5IxbrcIV=C37ZdG_yElkSK>Qcz=#foVimzN>2;FUf1w z60_cotOF8>^e_btB+)QMeQv98WO+l-UVcp*55HYvlZLMOXT)$(3Jsenj1L%`Vmfmb zSNG3PZ5_HR2UiWVnUPrSA9lFP0D6Xs3K(I5r*Ua^xX9{&V;JbD>)QqS+@J2|^n|?6 z&H(}V%jHd1dvjL~$;C*df6ntWR{i{8@Q~1l)`wF-PWP;nU{<>)^m5gOwli0?cw4>U zzw?{ryT~+`4rbL(YjvPwm;`ok!S8(C1hk2N`@7xh%ES!z{)Swj5H=(wWO7V|9~T(k z1WCqs98FPorUHbDGUz{(2UIMmESoFAs7Mte#UMo=?map*mY`zY!1C-cnQ^C;MW1b8-3ogGW$CCxJh#MNUSSzamCtAhEdZy1UzvNE*>X>R+ zpAvbH{D5#xFFNcwbI_`p=MkWo4}E@!;j5%lFBy zW^CMRE}d!GPevq=E23{NLXCDj2di=qJNkQf3V)#&ee7K574+m8$1Ozm)FVvG_G1Rt&iEKZuJdbZv2}W zo;gUVpK`b~iBpp(M&}ZXo1gF7$hXaL8Qt>2NV~t?wPj_+2nq1V55=rq9mb-W8M9YP z{-{te=;rv{b!8?)t|N17{?35A-S=f(I>M)gzM*k8yE++xkJc){7!kj$lSk1)T*}@g zb>3#Sx_aPgWw}5=AYstM*Bhvo&+K8O;&ZL7raNH;sO;dh8Px<)5J8wV9u6e*hvo=S z1&D)zo`s79U@_9Zh5wP0!sUJP5xz6$0)nHX;Dy70852;7+J6|Sv*yVtqT;58OGk0_ zgKr&k;sZ{ZX_dqPMV`xNEnvQ4dqxYT?}bYxn^@>{SIm&J!&hG;QBo&nd_*qd@4P0fRXtMQ7IDW@IDc=u z!Sa2Xe_UBV>+W9e=&;q{jgXjjTDIgSeiMCtBm8u`+1DWeSb&6jdbX&t|Jc>B7(KSX zoqP%%n;O28;9)A85dDTZ#>^A#q!&TjW{Zqm^1 z6w=U@B_kDnWCegSy=zz1i@wr*+_=Z*i-f&kgT&Thm`v5ftcMXDTeur`be#qZ%a`%= z83(Ri@AK|Yp%0-k$HM%XvSM>BGsTOQ^L*5Xkb8;hQ1Pf-bbLe^W#n)t5_>?l4;&rC zb--)^$WH{cCS1K4h2Z!~F~dP%#9Fx9=)71rD^CNoDFY)wuC6WN}q%K6o<} z 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 MLm;2$f&N4O3sf@~zW@LL diff --git a/event/event.go b/event/event.go new file mode 100644 index 0000000..2f3a109 --- /dev/null +++ b/event/event.go @@ -0,0 +1,80 @@ +package event + +import ( + "github.com/duanhf2012/originnet/log" + "sync" +) + +const Default_EventChannelLen = 10000 + +//事件接受器 +type EventReciver func(event *Event) error + +type Event struct { + Type EventType + Data interface{} +} + +type IEventProcessor interface { + NotifyEvent(*Event) + OnEventHandler(event *Event) error + SetEventReciver(eventProcessor IEventProcessor) + GetEventReciver() IEventProcessor + SetEventChanNum(num int32) bool +} + +type EventProcessor struct { + //事件管道 + EventChan chan *Event + eventReciver IEventProcessor + + eventChanNumLocker sync.RWMutex + eventChanNum int32 +} + +func (slf *EventProcessor) NotifyEvent(pEvent *Event) { + if len(slf.EventChan) >= int(slf.eventChanNum) { + log.Error("event queue is full!") + } + slf.EventChan <-pEvent +} + +func (slf *EventProcessor) OnEventHandler(event *Event) error{ + return nil +} + +func (slf *EventProcessor) GetEventChan() chan *Event{ + slf.eventChanNumLocker.Lock() + defer slf.eventChanNumLocker.Unlock() + + if slf.eventChanNum == 0 { + slf.eventChanNum = Default_EventChannelLen + } + + if slf.EventChan == nil { + slf.EventChan = make(chan *Event,slf.eventChanNum) + } + + return slf.EventChan +} + +//不允许重复设置 +func (slf *EventProcessor) SetEventChanNum(num int32) bool { + slf.eventChanNumLocker.Lock() + defer slf.eventChanNumLocker.Unlock() + if slf.eventChanNum>0 { + return false + } + + slf.eventChanNum = num + return true +} + +func (slf *EventProcessor) SetEventReciver(eventProcessor IEventProcessor){ + slf.eventReciver = eventProcessor +} + + +func (slf *EventProcessor) GetEventReciver() IEventProcessor{ + return slf.eventReciver +} diff --git a/event/eventtype.go b/event/eventtype.go new file mode 100644 index 0000000..89ee333 --- /dev/null +++ b/event/eventtype.go @@ -0,0 +1,14 @@ +package event + +type EventType int + +//大于Sys_Event_User_Define给用户定义 +const ( + Sys_Event_Tcp_Connected EventType= 1 + Sys_Event_Tcp_DisConnected EventType= 2 + Sys_Event_Tcp_RecvPack EventType = 3 + + + Sys_Event_User_Define EventType = 1000 +) + diff --git a/example/GateService/GateService.go b/example/GateService/GateService.go new file mode 100644 index 0000000..079e1c9 --- /dev/null +++ b/example/GateService/GateService.go @@ -0,0 +1,43 @@ +package GateService + +import ( + "github.com/duanhf2012/originnet/event" + "github.com/duanhf2012/originnet/node" + "github.com/duanhf2012/originnet/service" + "github.com/duanhf2012/originnet/sysservice" +) + +type GateService struct { + processor *PBProcessor + service.Service +} + +func (slf *GateService) OnInit() error{ + tcpervice := node.GetService("TcpService").(*sysservice.TcpService) + tcpervice.SetProcessor(&PBProcessor{}) + return nil +} + + +func (slf *GateService) OnEventHandler(ev *event.Event) error{ + if ev.Type == event.Sys_Event_Tcp_RecvPack { + pPack := ev.Data.(*sysservice.TcpPack) + slf.processor.Route(pPack.Data,pPack.ClientId) + }else if ev.Type == event.Sys_Event_Tcp_Connected { + pPack := ev.Data.(*sysservice.TcpPack) + slf.OnConnected(pPack.ClientId) + }else if ev.Type == event.Sys_Event_Tcp_DisConnected { + pPack := ev.Data.(*sysservice.TcpPack) + slf.OnDisconnected(pPack.ClientId) + } + return nil +} + +func (slf *GateService) OnConnected(clientid uint64){ + +} + + +func (slf *GateService) OnDisconnected(clientid uint64){ + +} diff --git a/example/GateService/pbprocessor.go b/example/GateService/pbprocessor.go new file mode 100644 index 0000000..7396680 --- /dev/null +++ b/example/GateService/pbprocessor.go @@ -0,0 +1,79 @@ +package GateService + +import ( + "encoding/binary" + "fmt" + "github.com/golang/protobuf/proto" + "reflect" +) + +type MessageInfo struct { + msgType reflect.Type + msgHandler MessageHandler +} + + +type PBProcessor struct { + mapMsg map[uint16]MessageInfo + LittleEndian bool +} + +func (slf *PBProcessor) SetLittleEndian(littleEndian bool){ + slf.LittleEndian = littleEndian +} + +type PackInfo struct { + typ uint16 + msg proto.Message +} + +// must goroutine safe +func (slf *PBProcessor ) Route(msg interface{},userdata interface{}) error{ + pPackInfo := msg.(*PackInfo) + v,ok := slf.mapMsg[pPackInfo.typ] + if ok == false { + return fmt.Errorf("cannot find msgtype %d is register!",pPackInfo.typ) + } + + + v.msgHandler(userdata.(uint64),pPackInfo.msg) + return nil +} + +// must goroutine safe +func (slf *PBProcessor ) Unmarshal(data []byte) (interface{}, error) { + var msgType uint16 + if slf.LittleEndian == true { + msgType = binary.LittleEndian.Uint16(data[:2]) + }else{ + msgType = binary.BigEndian.Uint16(data[:2]) + } + + info,ok := slf.mapMsg[msgType] + if ok == false { + return nil,fmt.Errorf("cannot find register %d msgtype!",msgType) + } + msg := reflect.New(info.msgType.Elem()).Interface() + protoMsg := msg.(proto.Message) + err := proto.Unmarshal(data[2:], protoMsg) + if err != nil { + return nil,err + } + + return &PackInfo{typ:msgType,msg:protoMsg},nil +} + +// must goroutine safe +func (slf *PBProcessor ) Marshal(msg interface{}) ([]byte, error){ + return proto.Marshal(msg.(proto.Message)) +} + +type MessageHandler func(clientid uint64,msg proto.Message) + +func (slf *PBProcessor) Register(msgtype uint16,msg proto.Message,handle MessageHandler) { + var info MessageInfo + + info.msgType = reflect.TypeOf(msg.(proto.Message)) + info.msgHandler = handle + slf.mapMsg[msgtype] = info +} diff --git a/example/config/cluster/subnet/cluster.json b/example/config/cluster/subnet/cluster.json new file mode 100644 index 0000000..a97970f --- /dev/null +++ b/example/config/cluster/subnet/cluster.json @@ -0,0 +1,23 @@ +{ + "NodeList":[ + { + "NodeId": 1, + "ListenAddr":"127.0.0.1:8001", + "NodeName": "Node_Test1", + "remark":"//以_打头的,表示只在本机进程,不对整个子网开发", + "ServiceList": ["TestService1","TestService2","TestServiceCall"] + }, + { + "NodeId": 2, + "ListenAddr":"127.0.0.1:8002", + "NodeName": "Node_Gate2", + "ServiceList": ["SubNet1_Service"] + }, + { + "NodeId": 3, + "ListenAddr":"127.0.0.1:8003", + "NodeName": "Node_Room", + "ServiceList": ["SubNet1_Service"] + } + ] +} \ No newline at end of file diff --git a/example/config/cluster/subnet/service.json b/example/config/cluster/subnet/service.json new file mode 100644 index 0000000..1a7666a --- /dev/null +++ b/example/config/cluster/subnet/service.json @@ -0,0 +1,13 @@ +{ + "HttpService":{ + "Port":10001 + }, + "TcpService":{ + "ListenAddr":"0.0.0.0:9030", + "MaxConnNum":3000, + "PendingWriteNum":10000, + "LittleEndian":false, + "MinMsgLen":4, + "MaxMsgLen":65535 + } +} \ No newline at end of file diff --git a/example/main.exe.pid b/example/main.exe.pid new file mode 100644 index 0000000..9057483 --- /dev/null +++ b/example/main.exe.pid @@ -0,0 +1 @@ +49288 \ No newline at end of file diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..7e699be --- /dev/null +++ b/example/main.go @@ -0,0 +1,202 @@ +package main + +import ( + "fmt" + "github.com/duanhf2012/originnet/example/GateService" + "github.com/duanhf2012/originnet/node" + "github.com/duanhf2012/originnet/service" + "github.com/duanhf2012/originnet/sysmodule" + "github.com/duanhf2012/originnet/sysservice" + "time" +) + +type TestService1 struct { + service.Service +} + +type TestService2 struct { + service.Service +} + +type TestServiceCall struct { + service.Service + dbModule sysmodule.DBModule +} + +func init(){ + node.Setup(&TestService1{},&TestService2{},&TestServiceCall{}) +} + +type Module1 struct{ + service.Module +} + +type Module2 struct{ + service.Module +} + +type Module3 struct{ + service.Module +} + +type Module4 struct{ + service.Module +} +var moduleid1 int64 +var moduleid2 int64 +var moduleid3 int64 +var moduleid4 int64 + +func (slf *Module1) OnInit() error { + fmt.Printf("I'm Module1:%d\n",slf.GetModuleId()) + return nil +} + +func (slf *Module2) OnInit() error { + fmt.Printf("I'm Module2:%d\n",slf.GetModuleId()) + moduleid3,_ = slf.AddModule(&Module3{}) + return nil +} +func (slf *Module3) OnInit() error { + fmt.Printf("I'm Module3:%d\n",slf.GetModuleId()) + moduleid4,_ = slf.AddModule(&Module4{}) + + return nil +} + +func (slf *Module4) OnInit() error { + fmt.Printf("I'm Module4:%d\n",slf.GetModuleId()) + //pService := slf.GetService().(*TestServiceCall) + //pService.RPC_Test(nil,nil) + slf.AfterFunc(time.Second*10,slf.TimerTest) + return nil +} + +func (slf *Module4) TimerTest(){ + fmt.Printf("Module4 tigger timer\n") +} + +func (slf *Module1) OnRelease() { + fmt.Printf("Release Module1:%d\n",slf.GetModuleId()) +} +func (slf *Module2) OnRelease() { + fmt.Printf("Release Module2:%d\n",slf.GetModuleId()) +} +func (slf *Module3) OnRelease() { + fmt.Printf("Release Module3:%d\n",slf.GetModuleId()) +} +func (slf *Module4) OnRelease() { + fmt.Printf("Release Module4:%d\n",slf.GetModuleId()) +} + +func (slf *TestServiceCall) OnInit() error { + slf.AfterFunc(time.Second*1,slf.Run) + moduleid1,_ = slf.AddModule(&Module1{}) + moduleid2,_ = slf.AddModule(&Module2{}) + fmt.Print(moduleid1,moduleid2) + + slf.dbModule = sysmodule.DBModule{} + slf.dbModule.Init(10,3, "192.168.0.5:3306", "root", "Root!!2018", "Matrix") + slf.dbModule.SetQuerySlowTime(time.Second * 3) + slf.AddModule(&slf.dbModule) + + slf.AfterFunc(time.Second*5,slf.Release) + slf.AfterFunc(time.Second, slf.TestDB) + return nil +} + +func (slf *TestServiceCall) Release(){ + slf.ReleaseModule(moduleid1) + slf.ReleaseModule(moduleid2) +} + + +type Param struct { + Index int + A int + B string + Pa []string +} + + +func (slf *TestServiceCall) Run(){ + //var ret int + var input int = 10000 + bT := time.Now() // 开始时间 + + //err := slf.Call("TestServiceCall.RPC_Test",&ret,&input) + var param Param + param.A = 2342342341 + param.B = "xxxxxxxxxxxxxxxxxxxxxxx" + param.Pa = []string{"ccccc","asfsdfsdaf","bbadfsdf","ewrwefasdf","safsadfka;fksd"} + + for i:=input;i>=0;i--{ + param.Index = i + slf.AsyncCall("TestService1.RPC_Test",¶m, func(reply *Param, err error) { + if reply.Index == 0 || err != nil{ + eT := time.Since(bT) // 从开始到当前所消耗的时间 + fmt.Print(err,eT.Milliseconds()) + fmt.Print("..................",eT,"\n") + } + //fmt.Print(*reply,"\n",err) + }) + + } + + fmt.Print("finsh....") + + +} + +func (slf *TestService1) RPC_Test(a *Param,b *Param) error { + *a = *b + return nil +} + +func (slf *TestService1) OnInit() error { + return nil +} + +func (slf *TestServiceCall) RPC_Test(a *int,b *int) error { + fmt.Printf("TestService2\n") + *a = *b + return nil +} + +func (slf *TestServiceCall) TestDB() { + assetsInfo := &struct { + Cash int64 `json:"cash"` //美金余额 100 + Gold int64 `json:"gold"` //金币余额 + Heart int64 `json:"heart"` //心数 + }{} + sql := `call sp_select_userAssets(?)` + userID := 100000802 + err := slf.dbModule.AsyncQuery(func(dataList *sysmodule.DataSetList, err error) { + if err != nil { + return + } + err = dataList.UnMarshal(assetsInfo) + if err != nil { + return + } + },-1, sql, &userID) + + fmt.Println(err) +} + +func (slf *TestService2) OnInit() error { + return nil +} + +func main(){ + node.Init() + tcpService := &sysservice.TcpService{} + gateService := &GateService.GateService{} + + tcpService.SetEventReciver(gateService) + node.Setup(tcpService,gateService) + + node.Start() +} + + diff --git a/example/serviceTest/OriginServeOne.go b/example/serviceTest/OriginServeOne.go new file mode 100644 index 0000000..36385e7 --- /dev/null +++ b/example/serviceTest/OriginServeOne.go @@ -0,0 +1,64 @@ +package serviceTest + +import ( + "fmt" + "github.com/duanhf2012/originnet/service" + "time" +) + +type TestAsyn struct { + a int + b string +} + +type OriginServerOne struct { + service.Service +} + +func (slf *OriginServerOne) OnInit() error { + //slf.AfterFunc(time.Second,slf.testCall) + //slf.AfterFunc(time.Second*5,slf.testCall) + //slf.AfterFunc(time.Second*10, slf.testGRCall) + //slf.AfterFunc(time.Second*15, slf.testGRCall) + //slf.AfterFunc(time.Second, slf.testAsyncCall) + slf.AfterFunc(time.Second, slf.testAsyncGRCall) + return nil +} + +func (slf *OriginServerOne) testCall() { + a := 1 + b := 10 + slf.Call("OriginServerTwo.RPC_TestCall", &a, &b) + fmt.Println(b) +} + +func (slf *OriginServerOne) testGRCall() { + a := 1 + b := 10 + slf.GRCall("OriginServerTwo.RPC_TestCall", &a, &b) + fmt.Println(b) +} + +func (slf *OriginServerOne) testAsyncCall() { + for i := 0; i < 100; i++ { + in := i + bT := time.Now() + slf.AsyncCall("OriginServerTwo.RPC_TestAsyncCall", &in, func(reply *TestAsyn, err error) { + eT := time.Since(bT) // 从开始到当前所消耗的时间 + fmt.Println(reply, eT) + }) + fmt.Println(in) + } +} + +func (slf *OriginServerOne) testAsyncGRCall() { + for i := 0; i < 100; i++ { + in := i + bT := time.Now() + slf.GRAsyncCall("OriginServerTwo.RPC_TestAsyncCall", &in, func(reply *TestAsyn, err error) { + eT := time.Since(bT) + fmt.Println(reply, eT) + }) + fmt.Println(in) + } +} \ No newline at end of file diff --git a/example/serviceTest/OriginServerTwo.go b/example/serviceTest/OriginServerTwo.go new file mode 100644 index 0000000..caf32ad --- /dev/null +++ b/example/serviceTest/OriginServerTwo.go @@ -0,0 +1,27 @@ +package serviceTest + +import ( + "fmt" + "github.com/duanhf2012/originnet/service" + "time" +) + +type OriginServerTwo struct { + service.Service +} + +func (slf *OriginServerTwo) RPC_TestCall(a *int,b *int) error { + fmt.Printf("OriginServerTwo\n") + *a = *b*2 + //slf.AfterFunc(time.Second,slf.Test) + return nil +} + +func (slf *OriginServerTwo) RPC_TestAsyncCall(a *int, reply *TestAsyn) error { + fmt.Printf("OriginServerTwo async start sleep %d\n", *a) + time.Sleep(time.Second) + + reply.a = *a + reply.b = "fuck!" + return nil +} diff --git a/log/example_test.go b/log/example_test.go new file mode 100644 index 0000000..55f5c35 --- /dev/null +++ b/log/example_test.go @@ -0,0 +1,29 @@ +package log_test + +import ( + "github.com/name5566/leaf/log" + l "log" +) + +func Example() { + name := "Leaf" + + log.Debug("My name is %v", name) + log.Release("My name is %v", name) + log.Error("My name is %v", name) + // log.Fatal("My name is %v", name) + + logger, err := log.New("release", "", l.LstdFlags) + if err != nil { + return + } + defer logger.Close() + + logger.Debug("will not print") + logger.Release("My name is %v", name) + + log.Export(logger) + + log.Debug("will not print") + log.Release("My name is %v", name) +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..1c7cab9 --- /dev/null +++ b/log/log.go @@ -0,0 +1,153 @@ +package log + +import ( + "errors" + "fmt" + "log" + "os" + "path" + "strings" + "time" +) + +// levels +const ( + debugLevel = 0 + releaseLevel = 1 + errorLevel = 2 + fatalLevel = 3 +) + +const ( + printDebugLevel = "[debug ] " + printReleaseLevel = "[release] " + printErrorLevel = "[error ] " + printFatalLevel = "[fatal ] " +) + +type Logger struct { + level int + baseLogger *log.Logger + baseFile *os.File +} + +func New(strLevel string, pathname string, flag int) (*Logger, error) { + // level + var level int + switch strings.ToLower(strLevel) { + case "debug": + level = debugLevel + case "release": + level = releaseLevel + case "error": + level = errorLevel + case "fatal": + level = fatalLevel + default: + return nil, errors.New("unknown level: " + strLevel) + } + + // logger + var baseLogger *log.Logger + var baseFile *os.File + if pathname != "" { + now := time.Now() + + filename := fmt.Sprintf("%d%02d%02d_%02d_%02d_%02d.log", + now.Year(), + now.Month(), + now.Day(), + now.Hour(), + now.Minute(), + now.Second()) + + file, err := os.Create(path.Join(pathname, filename)) + if err != nil { + return nil, err + } + + baseLogger = log.New(file, "", flag) + baseFile = file + } else { + baseLogger = log.New(os.Stdout, "", flag) + } + + // new + logger := new(Logger) + logger.level = level + logger.baseLogger = baseLogger + logger.baseFile = baseFile + + return logger, nil +} + +// It's dangerous to call the method on logging +func (logger *Logger) Close() { + if logger.baseFile != nil { + logger.baseFile.Close() + } + + logger.baseLogger = nil + logger.baseFile = nil +} + +func (logger *Logger) doPrintf(level int, printLevel string, format string, a ...interface{}) { + if level < logger.level { + return + } + if logger.baseLogger == nil { + panic("logger closed") + } + + format = printLevel + format + logger.baseLogger.Output(3, fmt.Sprintf(format, a...)) + + if level == fatalLevel { + os.Exit(1) + } +} + +func (logger *Logger) Debug(format string, a ...interface{}) { + logger.doPrintf(debugLevel, printDebugLevel, format, a...) +} + +func (logger *Logger) Release(format string, a ...interface{}) { + logger.doPrintf(releaseLevel, printReleaseLevel, format, a...) +} + +func (logger *Logger) Error(format string, a ...interface{}) { + logger.doPrintf(errorLevel, printErrorLevel, format, a...) +} + +func (logger *Logger) Fatal(format string, a ...interface{}) { + logger.doPrintf(fatalLevel, printFatalLevel, format, a...) +} + +var gLogger, _ = New("debug", "", log.LstdFlags) + +// It's dangerous to call the method on logging +func Export(logger *Logger) { + if logger != nil { + gLogger = logger + } +} + +func Debug(format string, a ...interface{}) { + gLogger.doPrintf(debugLevel, printDebugLevel, format, a...) +} + +func Release(format string, a ...interface{}) { + gLogger.doPrintf(releaseLevel, printReleaseLevel, format, a...) +} + +func Error(format string, a ...interface{}) { + gLogger.doPrintf(errorLevel, printErrorLevel, format, a...) +} + +func Fatal(format string, a ...interface{}) { + gLogger.doPrintf(fatalLevel, printFatalLevel, format, a...) +} + +func Close() { + gLogger.Close() +} diff --git a/network/agent.go b/network/agent.go new file mode 100644 index 0000000..7f174b6 --- /dev/null +++ b/network/agent.go @@ -0,0 +1,6 @@ +package network + +type Agent interface { + Run() + OnClose() +} diff --git a/network/conn.go b/network/conn.go new file mode 100644 index 0000000..9f7a2c5 --- /dev/null +++ b/network/conn.go @@ -0,0 +1,14 @@ +package network + +import ( + "net" +) + +type Conn interface { + ReadMsg() ([]byte, error) + WriteMsg(args ...[]byte) error + LocalAddr() net.Addr + RemoteAddr() net.Addr + Close() + Destroy() +} diff --git a/network/httpserver.go b/network/httpserver.go deleted file mode 100644 index 24831ab..0000000 --- a/network/httpserver.go +++ /dev/null @@ -1,98 +0,0 @@ -package network - -import ( - "crypto/tls" - "fmt" - "net/http" - "os" - "time" - - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" -) - -type CA struct { - certfile string - keyfile string -} - -type HttpServer struct { - port uint16 - - handler http.Handler - readtimeout time.Duration - writetimeout time.Duration - - httpserver *http.Server - caList []CA - - ishttps bool -} - -func (slf *HttpServer) Init(port uint16, handler http.Handler, readtimeout time.Duration, writetimeout time.Duration) { - slf.port = port - slf.handler = handler - slf.readtimeout = readtimeout - slf.writetimeout = writetimeout -} - -func (slf *HttpServer) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { - http.HandleFunc(pattern, handler) -} - -func (slf *HttpServer) Start() { - go slf.startListen() -} - -func (slf *HttpServer) startListen() error { - listenPort := fmt.Sprintf(":%d", slf.port) - - var tlscatList []tls.Certificate - var tlsConfig *tls.Config - for _, cadata := range slf.caList { - cer, err := tls.LoadX509KeyPair(cadata.certfile, cadata.keyfile) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "load CA [%s]-[%s] file is error :%s", cadata.certfile, cadata.keyfile, err.Error()) - os.Exit(1) - return nil - } - tlscatList = append(tlscatList, cer) - } - - if len(tlscatList) > 0 { - tlsConfig = &tls.Config{Certificates: tlscatList} - } - - slf.httpserver = &http.Server{ - Addr: listenPort, - Handler: slf.handler, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - TLSConfig: tlsConfig, - } - - var err error - if slf.ishttps == true { - err = slf.httpserver.ListenAndServeTLS("", "") - } else { - err = slf.httpserver.ListenAndServe() - } - - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "http.ListenAndServe(%d, nil) error:%v\n", listenPort, err) - fmt.Printf("http.ListenAndServe(%d, %v) error\n", slf.port, err) - os.Exit(1) - } - - return nil -} - -func (slf *HttpServer) SetHttps(certfile string, keyfile string) bool { - if certfile == "" || keyfile == "" { - return false - } - slf.caList = append(slf.caList, CA{certfile, keyfile}) - slf.ishttps = true - return true -} diff --git a/network/netserver.go b/network/netserver.go new file mode 100644 index 0000000..2ce8a2b --- /dev/null +++ b/network/netserver.go @@ -0,0 +1,7 @@ +package network + +type inetserver interface { + +} + + diff --git a/network/processor.go b/network/processor.go new file mode 100644 index 0000000..7739434 --- /dev/null +++ b/network/processor.go @@ -0,0 +1,10 @@ +package network + +type Processor interface { + // must goroutine safe + Route(msg interface{}, userData interface{}) error + // must goroutine safe + Unmarshal(data []byte) (interface{}, error) + // must goroutine safe + Marshal(msg interface{}) ([]byte, error) +} diff --git a/network/processor/gobprocessor.go b/network/processor/gobprocessor.go new file mode 100644 index 0000000..95af6c9 --- /dev/null +++ b/network/processor/gobprocessor.go @@ -0,0 +1 @@ +package processor diff --git a/network/processor/jsonprocessor.go b/network/processor/jsonprocessor.go new file mode 100644 index 0000000..106b9cb --- /dev/null +++ b/network/processor/jsonprocessor.go @@ -0,0 +1,17 @@ +package processor + +type JsonProcessor struct { + //SetByteOrder(littleEndian bool) + //SetMsgLen(lenMsgLen int, minMsgLen uint32, maxMsgLen uint32) + + +} + +func (slf *JsonProcessor) Unmarshal(data []byte) (interface{}, error) { + return nil,nil +} + + +func (slf *JsonProcessor) Marshal(msg interface{}) ([][]byte, error) { + return nil,nil +} \ No newline at end of file diff --git a/network/processor/msgpackprocessor.go b/network/processor/msgpackprocessor.go new file mode 100644 index 0000000..95af6c9 --- /dev/null +++ b/network/processor/msgpackprocessor.go @@ -0,0 +1 @@ +package processor diff --git a/network/processor/processor.go b/network/processor/processor.go new file mode 100644 index 0000000..e98d88c --- /dev/null +++ b/network/processor/processor.go @@ -0,0 +1,11 @@ +package processor + +type IProcessor interface { + //SetByteOrder(littleEndian bool) + //SetMsgLen(lenMsgLen int, minMsgLen uint32, maxMsgLen uint32) + + Unmarshal(data []byte) (interface{}, error) + // must goroutine safe + Marshal(msg interface{}) ([][]byte, error) +} + diff --git a/network/processor/protobufprocessor.go b/network/processor/protobufprocessor.go new file mode 100644 index 0000000..95af6c9 --- /dev/null +++ b/network/processor/protobufprocessor.go @@ -0,0 +1 @@ +package processor diff --git a/network/tcp_client.go b/network/tcp_client.go new file mode 100644 index 0000000..1db8199 --- /dev/null +++ b/network/tcp_client.go @@ -0,0 +1,130 @@ +package network + +import ( + "github.com/duanhf2012/originnet/log" + "net" + "sync" + "time" +) + +type TCPClient struct { + sync.Mutex + Addr string + ConnNum int + ConnectInterval time.Duration + PendingWriteNum int + AutoReconnect bool + NewAgent func(*TCPConn) Agent + conns ConnSet + wg sync.WaitGroup + closeFlag bool + + // msg parser + LenMsgLen int + MinMsgLen uint32 + MaxMsgLen uint32 + LittleEndian bool + msgParser *MsgParser +} + +func (client *TCPClient) Start() { + client.init() + + for i := 0; i < client.ConnNum; i++ { + client.wg.Add(1) + go client.connect() + } +} + +func (client *TCPClient) init() { + client.Lock() + defer client.Unlock() + + if client.ConnNum <= 0 { + client.ConnNum = 1 + log.Release("invalid ConnNum, reset to %v", client.ConnNum) + } + if client.ConnectInterval <= 0 { + client.ConnectInterval = 3 * time.Second + log.Release("invalid ConnectInterval, reset to %v", client.ConnectInterval) + } + if client.PendingWriteNum <= 0 { + client.PendingWriteNum = 100 + log.Release("invalid PendingWriteNum, reset to %v", client.PendingWriteNum) + } + if client.NewAgent == nil { + log.Fatal("NewAgent must not be nil") + } + if client.conns != nil { + log.Fatal("client is running") + } + + client.conns = make(ConnSet) + client.closeFlag = false + + // msg parser + msgParser := NewMsgParser() + msgParser.SetMsgLen(client.LenMsgLen, client.MinMsgLen, client.MaxMsgLen) + msgParser.SetByteOrder(client.LittleEndian) + client.msgParser = msgParser +} + +func (client *TCPClient) dial() net.Conn { + for { + conn, err := net.Dial("tcp", client.Addr) + if err == nil || client.closeFlag { + return conn + } + + log.Release("connect to %v error: %v", client.Addr, err) + time.Sleep(client.ConnectInterval) + continue + } +} + +func (client *TCPClient) connect() { + defer client.wg.Done() + +reconnect: + conn := client.dial() + if conn == nil { + return + } + + client.Lock() + if client.closeFlag { + client.Unlock() + conn.Close() + return + } + client.conns[conn] = struct{}{} + client.Unlock() + + tcpConn := newTCPConn(conn, client.PendingWriteNum, client.msgParser) + agent := client.NewAgent(tcpConn) + agent.Run() + + // cleanup + tcpConn.Close() + client.Lock() + delete(client.conns, conn) + client.Unlock() + agent.OnClose() + + if client.AutoReconnect { + time.Sleep(client.ConnectInterval) + goto reconnect + } +} + +func (client *TCPClient) Close() { + client.Lock() + client.closeFlag = true + for conn := range client.conns { + conn.Close() + } + client.conns = nil + client.Unlock() + + client.wg.Wait() +} diff --git a/network/tcp_conn.go b/network/tcp_conn.go new file mode 100644 index 0000000..489be77 --- /dev/null +++ b/network/tcp_conn.go @@ -0,0 +1,113 @@ +package network + +import ( + "github.com/duanhf2012/originnet/log" + "net" + "sync" +) + +type ConnSet map[net.Conn]struct{} + +type TCPConn struct { + sync.Mutex + conn net.Conn + writeChan chan []byte + closeFlag bool + msgParser *MsgParser +} + +func newTCPConn(conn net.Conn, pendingWriteNum int, msgParser *MsgParser) *TCPConn { + tcpConn := new(TCPConn) + tcpConn.conn = conn + tcpConn.writeChan = make(chan []byte, pendingWriteNum) + tcpConn.msgParser = msgParser + + go func() { + for b := range tcpConn.writeChan { + if b == nil { + break + } + + _, err := conn.Write(b) + if err != nil { + break + } + } + + conn.Close() + tcpConn.Lock() + tcpConn.closeFlag = true + tcpConn.Unlock() + }() + + return tcpConn +} + +func (tcpConn *TCPConn) doDestroy() { + tcpConn.conn.(*net.TCPConn).SetLinger(0) + tcpConn.conn.Close() + + if !tcpConn.closeFlag { + close(tcpConn.writeChan) + tcpConn.closeFlag = true + } +} + +func (tcpConn *TCPConn) Destroy() { + tcpConn.Lock() + defer tcpConn.Unlock() + + tcpConn.doDestroy() +} + +func (tcpConn *TCPConn) Close() { + tcpConn.Lock() + defer tcpConn.Unlock() + if tcpConn.closeFlag { + return + } + + tcpConn.doWrite(nil) + tcpConn.closeFlag = true +} + +func (tcpConn *TCPConn) doWrite(b []byte) { + if len(tcpConn.writeChan) == cap(tcpConn.writeChan) { + log.Debug("close conn: channel full") + tcpConn.doDestroy() + return + } + + tcpConn.writeChan <- b +} + +// b must not be modified by the others goroutines +func (tcpConn *TCPConn) Write(b []byte) { + tcpConn.Lock() + defer tcpConn.Unlock() + if tcpConn.closeFlag || b == nil { + return + } + + tcpConn.doWrite(b) +} + +func (tcpConn *TCPConn) Read(b []byte) (int, error) { + return tcpConn.conn.Read(b) +} + +func (tcpConn *TCPConn) LocalAddr() net.Addr { + return tcpConn.conn.LocalAddr() +} + +func (tcpConn *TCPConn) RemoteAddr() net.Addr { + return tcpConn.conn.RemoteAddr() +} + +func (tcpConn *TCPConn) ReadMsg() ([]byte, error) { + return tcpConn.msgParser.Read(tcpConn) +} + +func (tcpConn *TCPConn) WriteMsg(args ...[]byte) error { + return tcpConn.msgParser.Write(tcpConn, args...) +} diff --git a/network/tcp_msg.go b/network/tcp_msg.go new file mode 100644 index 0000000..6b309c4 --- /dev/null +++ b/network/tcp_msg.go @@ -0,0 +1,154 @@ +package network + +import ( + "encoding/binary" + "errors" + "io" + "math" +) + +// -------------- +// | len | data | +// -------------- +type MsgParser struct { + lenMsgLen int + minMsgLen uint32 + maxMsgLen uint32 + littleEndian bool +} + +func NewMsgParser() *MsgParser { + p := new(MsgParser) + p.lenMsgLen = 2 + p.minMsgLen = 1 + p.maxMsgLen = 4096 + p.littleEndian = false + + return p +} + +// It's dangerous to call the method on reading or writing +func (p *MsgParser) SetMsgLen(lenMsgLen int, minMsgLen uint32, maxMsgLen uint32) { + if lenMsgLen == 1 || lenMsgLen == 2 || lenMsgLen == 4 { + p.lenMsgLen = lenMsgLen + } + if minMsgLen != 0 { + p.minMsgLen = minMsgLen + } + if maxMsgLen != 0 { + p.maxMsgLen = maxMsgLen + } + + var max uint32 + switch p.lenMsgLen { + case 1: + max = math.MaxUint8 + case 2: + max = math.MaxUint16 + case 4: + max = math.MaxUint32 + } + if p.minMsgLen > max { + p.minMsgLen = max + } + if p.maxMsgLen > max { + p.maxMsgLen = max + } +} + +// It's dangerous to call the method on reading or writing +func (p *MsgParser) SetByteOrder(littleEndian bool) { + p.littleEndian = littleEndian +} + +// goroutine safe +func (p *MsgParser) Read(conn *TCPConn) ([]byte, error) { + var b [4]byte + bufMsgLen := b[:p.lenMsgLen] + + // read len + if _, err := io.ReadFull(conn, bufMsgLen); err != nil { + return nil, err + } + + // parse len + var msgLen uint32 + switch p.lenMsgLen { + case 1: + msgLen = uint32(bufMsgLen[0]) + case 2: + if p.littleEndian { + msgLen = uint32(binary.LittleEndian.Uint16(bufMsgLen)) + } else { + msgLen = uint32(binary.BigEndian.Uint16(bufMsgLen)) + } + case 4: + if p.littleEndian { + msgLen = binary.LittleEndian.Uint32(bufMsgLen) + } else { + msgLen = binary.BigEndian.Uint32(bufMsgLen) + } + } + + // check len + if msgLen > p.maxMsgLen { + return nil, errors.New("message too long") + } else if msgLen < p.minMsgLen { + return nil, errors.New("message too short") + } + + // data + msgData := make([]byte, msgLen) + if _, err := io.ReadFull(conn, msgData); err != nil { + return nil, err + } + + return msgData, nil +} + +// goroutine safe +func (p *MsgParser) Write(conn *TCPConn, args ...[]byte) error { + // get len + var msgLen uint32 + for i := 0; i < len(args); i++ { + msgLen += uint32(len(args[i])) + } + + // check len + if msgLen > p.maxMsgLen { + return errors.New("message too long") + } else if msgLen < p.minMsgLen { + return errors.New("message too short") + } + + msg := make([]byte, uint32(p.lenMsgLen)+msgLen) + + // write len + switch p.lenMsgLen { + case 1: + msg[0] = byte(msgLen) + case 2: + if p.littleEndian { + binary.LittleEndian.PutUint16(msg, uint16(msgLen)) + } else { + binary.BigEndian.PutUint16(msg, uint16(msgLen)) + } + case 4: + if p.littleEndian { + binary.LittleEndian.PutUint32(msg, msgLen) + } else { + binary.BigEndian.PutUint32(msg, msgLen) + } + } + + // write data + l := p.lenMsgLen + for i := 0; i < len(args); i++ { + copy(msg[l:], args[i]) + l += len(args[i]) + } + + conn.Write(msg) + + return nil +} diff --git a/network/tcp_server.go b/network/tcp_server.go new file mode 100644 index 0000000..385fb6f --- /dev/null +++ b/network/tcp_server.go @@ -0,0 +1,127 @@ +package network + +import ( + "github.com/duanhf2012/originnet/log" + "net" + "sync" + "time" +) + +type TCPServer struct { + Addr string + MaxConnNum int + PendingWriteNum int + NewAgent func(*TCPConn) Agent + ln net.Listener + conns ConnSet + mutexConns sync.Mutex + wgLn sync.WaitGroup + wgConns sync.WaitGroup + + // msg parser + LenMsgLen int + MinMsgLen uint32 + MaxMsgLen uint32 + LittleEndian bool + msgParser *MsgParser +} + +func (server *TCPServer) Start() { + server.init() + go server.run() +} + +func (server *TCPServer) init() { + ln, err := net.Listen("tcp", server.Addr) + if err != nil { + log.Fatal("%v", err) + } + + if server.MaxConnNum <= 0 { + server.MaxConnNum = 100 + log.Release("invalid MaxConnNum, reset to %v", server.MaxConnNum) + } + if server.PendingWriteNum <= 0 { + server.PendingWriteNum = 100 + log.Release("invalid PendingWriteNum, reset to %v", server.PendingWriteNum) + } + if server.NewAgent == nil { + log.Fatal("NewAgent must not be nil") + } + + server.ln = ln + server.conns = make(ConnSet) + + // msg parser + msgParser := NewMsgParser() + msgParser.SetMsgLen(server.LenMsgLen, server.MinMsgLen, server.MaxMsgLen) + msgParser.SetByteOrder(server.LittleEndian) + server.msgParser = msgParser +} + +func (server *TCPServer) run() { + server.wgLn.Add(1) + defer server.wgLn.Done() + + var tempDelay time.Duration + for { + conn, err := server.ln.Accept() + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + log.Release("accept error: %v; retrying in %v", err, tempDelay) + time.Sleep(tempDelay) + continue + } + return + } + tempDelay = 0 + + server.mutexConns.Lock() + if len(server.conns) >= server.MaxConnNum { + server.mutexConns.Unlock() + conn.Close() + log.Debug("too many connections") + continue + } + server.conns[conn] = struct{}{} + server.mutexConns.Unlock() + + server.wgConns.Add(1) + + tcpConn := newTCPConn(conn, server.PendingWriteNum, server.msgParser) + agent := server.NewAgent(tcpConn) + go func() { + agent.Run() + + // cleanup + tcpConn.Close() + server.mutexConns.Lock() + delete(server.conns, conn) + server.mutexConns.Unlock() + agent.OnClose() + + server.wgConns.Done() + }() + } +} + +func (server *TCPServer) Close() { + server.ln.Close() + server.wgLn.Wait() + + server.mutexConns.Lock() + for conn := range server.conns { + conn.Close() + } + server.conns = nil + server.mutexConns.Unlock() + server.wgConns.Wait() +} diff --git a/network/tcpsocketclient.go b/network/tcpsocketclient.go deleted file mode 100644 index 3985755..0000000 --- a/network/tcpsocketclient.go +++ /dev/null @@ -1,48 +0,0 @@ -package network - -import ( - "fmt" - "github.com/golang/protobuf/proto" - "net" -) - -type TcpSocketClient struct { - conn net.Conn - addr string -} - -func (slf *TcpSocketClient) Connect(addr string) error{ - tcpAddr,terr := net.ResolveTCPAddr("tcp",addr) - if terr != nil { - return terr - } - - conn,err := net.DialTCP("tcp",nil,tcpAddr) - if err!=nil { - fmt.Println("Client connect error ! " + err.Error()) - return err - } - slf.conn = conn - slf.addr = addr - - // - return nil -} - -func (slf *TcpSocketClient) SendMsg(packtype uint16,message proto.Message) error{ - if slf.conn == nil { - return fmt.Errorf("cannt connect %s",slf.addr) - } - - var msg MsgBasePack - data,err := proto.Marshal(message) - if err != nil { - return err - } - - msg.Make(packtype,data) - - slf.conn.Write(msg.Bytes()) - - return nil -} diff --git a/network/tcpsocketserver.go b/network/tcpsocketserver.go deleted file mode 100644 index 9c34c73..0000000 --- a/network/tcpsocketserver.go +++ /dev/null @@ -1,331 +0,0 @@ -package network - -import ( - "bufio" - "encoding/binary" - "fmt" - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/util" - "github.com/golang/protobuf/proto" - "io" - "net" - "unsafe" - - "os" - "time" -) - -type ITcpSocketServerReciver interface { - OnConnected(pClient *SClient) - OnDisconnect(pClient *SClient) - OnRecvMsg(pClient *SClient, pPack *MsgBasePack) -} - - -type SClient struct { - id uint64 - conn net.Conn - - recvPack *util.SyncQueue - sendPack *util.SyncQueue - tcpserver *TcpSocketServer - remoteip string - starttime int64 - bClose bool -} - -type TcpSocketServer struct { - listenAddr string //ip:port - mapClient util.Map - - MaxRecvPackSize uint16 - MaxSendPackSize uint16 - iReciver ITcpSocketServerReciver - nodelay bool -} - -type MsgBasePack struct { - PackSize uint16 - PackType uint16 - Body []byte - StartTime time.Time -} - - - - - -func (slf *TcpSocketServer) Register(listenAddr string,iReciver ITcpSocketServerReciver){ - slf.listenAddr = listenAddr - slf.iReciver = iReciver -} - - -func (slf *TcpSocketServer) Start(){ - slf.MaxRecvPackSize = 2048 - slf.MaxSendPackSize = 40960 - - util.Go(slf.listenServer) -} - -func (slf *TcpSocketServer) listenServer(){ - slf.nodelay = true - - listener, err := net.Listen("tcp", slf.listenAddr) - if err != nil { - service.GetLogger().Printf(service.LEVER_FATAL, "TcpSocketServer Listen error %+v",err) - os.Exit(1) - } - - var clientId uint64 - for { - conn, aerr := listener.Accept() - if aerr != nil { - service.GetLogger().Printf(service.LEVER_FATAL, "TcpSocketServer accept error %+v",aerr) - continue - } - - if slf.nodelay { - //conn.(ifaceSetNoDelay) - } - for { - clientId += 1 - if slf.mapClient.Get(clientId)!= nil { - continue - } - - sc :=&SClient{id:clientId,conn:conn,tcpserver:slf,remoteip:conn.RemoteAddr().String(),starttime:time.Now().UnixNano(), - recvPack:util.NewSyncQueue(),sendPack:util.NewSyncQueue()} - - slf.mapClient.Set(clientId,sc) - util.Go(sc.listendata) - //收来自客户端数据 - util.Go(sc.onrecv) - //发送数据队列 - util.Go(sc.onsend) - - break - } - } -} - -func (slf *TcpSocketServer) Close(clientid uint64) error { - pClient := slf.mapClient.Get(clientid) - if pClient == nil { - return fmt.Errorf("clientid %d is not in connect pool.",clientid) - } - - pClient.(*SClient).Close() - return nil -} - -func (slf *TcpSocketServer) SendMsg(clientid uint64,packtype uint16,message proto.Message) error{ - pClient := slf.mapClient.Get(clientid) - if pClient == nil { - return fmt.Errorf("clientid %d is not in connect pool.",clientid) - } - - return pClient.(*SClient).SendMsg(packtype,message) -} - -func (slf *TcpSocketServer) Send(clientid uint64,pack *MsgBasePack) error{ - pClient := slf.mapClient.Get(clientid) - if pClient == nil { - return fmt.Errorf("clientid %d is not in connect pool.",clientid) - } - - return pClient.(*SClient).Send(pack) -} - - -func (slf *SClient) listendata(){ - defer func() { - slf.Close() - slf.tcpserver.mapClient.Del(slf.id) - slf.tcpserver.iReciver.OnDisconnect(slf) - service.GetLogger().Printf(service.LEVER_DEBUG, "clent id %d return listendata...",slf.id) - }() - - slf.tcpserver.iReciver.OnConnected(slf) - //获取一个连接的reader读取流 - reader := bufio.NewReader(slf.conn) - - //临时接受数据的buff - var buff []byte //tmprecvbuf - var tmpbuff []byte - var buffDataSize uint16 - tmpbuff = make([]byte,2048) - - //解析包数据 - var pack MsgBasePack - for { - n,err := reader.Read(tmpbuff) - if err != nil || err == io.EOF { - service.GetLogger().Printf(service.LEVER_INFO, "clent id %d is disconnect %+v",slf.id,err) - return - } - buff = append(buff,tmpbuff[:n]...) - buffDataSize += uint16(n) - if buffDataSize> slf.tcpserver.MaxRecvPackSize { - service.GetLogger().Print(service.LEVER_WARN,"recv client id %d data size %d is over %d",slf.id,buffDataSize,slf.tcpserver.MaxRecvPackSize) - return - } - - fillsize,bfillRet,fillhead := pack.FillData(buff,buffDataSize) - //提交校验头 - if fillhead == true { - if pack.PackSize>slf.tcpserver.MaxRecvPackSize { - service.GetLogger().Printf(service.LEVER_WARN, "VerifyPackType error clent id %d is disconnect %d,%d",slf.id,pack.PackType, pack.PackSize) - return - } - } - if bfillRet == true { - slf.recvPack.Push(pack) - pack = MsgBasePack{} - } - if fillsize>0 { - buff = append(buff[fillsize:]) - buffDataSize -= fillsize - } - } -} - - -func (slf *MsgBasePack) Bytes() []byte{ - var bRet []byte - bRet = make([]byte,4) - binary.BigEndian.PutUint16(bRet,slf.PackSize) - binary.BigEndian.PutUint16(bRet[2:],slf.PackType) - bRet = append(bRet,slf.Body...) - - return bRet -} - -//返回值:填充多少字节,是否完成,是否填充头 -func (slf *MsgBasePack) FillData(bdata []byte,datasize uint16) (uint16,bool,bool) { - var fillsize uint16 - fillhead := false - //解包头 - if slf.PackSize ==0 { - if datasize < 4 { - return 0,false,fillhead - } - - slf.PackSize= binary.BigEndian.Uint16(bdata[:2]) - slf.PackType= binary.BigEndian.Uint16(bdata[2:4]) - fillsize += 4 - fillhead = true - } - - //解包体 - if slf.PackSize>0 && datasize+4>=slf.PackSize { - slf.Body = append(slf.Body, bdata[fillsize:slf.PackSize]...) - fillsize += (slf.PackSize - fillsize) - return fillsize,true,fillhead - } - - return fillsize,false,fillhead -} - - -func (slf *MsgBasePack) Clear() { -} - -func (slf *MsgBasePack) Make(packtype uint16,data []byte) { - slf.PackType = packtype - slf.Body = data - slf.PackSize = uint16(unsafe.Sizeof(slf.PackType)*2)+uint16(len(data)) -} - -func (slf *SClient) Send(pack *MsgBasePack) error { - if slf.bClose == true { - return fmt.Errorf("client id %d is close!",slf.id) - } - - slf.sendPack.Push(pack) - - return nil -} - - -func (slf *SClient) SendMsg(packtype uint16,message proto.Message) error{ - if slf.bClose == true { - return fmt.Errorf("client id %d is close!",slf.id) - } - - var msg MsgBasePack - data,err := proto.Marshal(message) - if err != nil { - return err - } - - msg.Make(packtype,data) - slf.sendPack.Push(&msg) - - return nil -} - - - -func (slf *SClient) onsend(){ - defer func() { - slf.Close() - service.GetLogger().Printf(service.LEVER_DEBUG, "clent id %d return onsend...",slf.id) - }() - - for { - pack,ok := slf.sendPack.TryPop() - if slf.bClose == true { - break - } - if ok == false || pack == nil { - time.Sleep(time.Millisecond*1) - continue - } - - pPackData := pack.(*MsgBasePack) - _,e := slf.conn.Write(pPackData.Bytes()) - if e!=nil { - service.GetLogger().Printf(service.LEVER_DEBUG, "clent id %d write error...",slf.id) - return - } - //fmt.Print("xxxxxxxxxxxxxxx:",n,e) - } -} - -func (slf *SClient) onrecv(){ - defer func() { - slf.Close() - service.GetLogger().Printf(service.LEVER_DEBUG, "clent id %d return onrecv...",slf.id) - }() - - for { - pack,ok := slf.recvPack.TryPop() - if slf.bClose == true { - break - } - if ok == false || pack == nil { - time.Sleep(time.Millisecond*1) - continue - } - - pMsg := pack.(MsgBasePack) - slf.tcpserver.iReciver.OnRecvMsg(slf,&pMsg) - } -} - - -func (slf *SClient) Close(){ - if slf.bClose == false { - slf.conn.Close() - slf.bClose = true - - slf.recvPack.Close() - slf.sendPack.Close() - } -} - - -func (slf *SClient) GetId() uint64{ - return slf.id -} diff --git a/network/websocketclient.go b/network/websocketclient.go deleted file mode 100644 index 6d82eb9..0000000 --- a/network/websocketclient.go +++ /dev/null @@ -1,259 +0,0 @@ -package network - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "runtime/debug" - - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" - - "github.com/gorilla/websocket" - - "time" -) - -//IWebsocketClient ... -type IWebsocketClient interface { - Init(slf IWebsocketClient, strurl, strProxyPath string, timeoutsec time.Duration) error - Start() error - WriteMessage(msg []byte) error - OnDisconnect() error - OnConnected() error - OnReadMessage(msg []byte) error - ReConnect() -} - -//WebsocketClient ... -type WebsocketClient struct { - WsDailer *websocket.Dialer - conn *websocket.Conn - url string - state int //0未连接状态 1正在重连 2连接状态 - bwritemsg chan []byte - closer chan bool - slf IWebsocketClient - timeoutsec time.Duration - - bRun bool - ping []byte -} - -const ( - MAX_WRITE_MSG = 10240 -) - -//Init ... -func (ws *WebsocketClient) Init(slf IWebsocketClient, strurl, strProxyPath string, timeoutsec time.Duration) error { - - ws.timeoutsec = timeoutsec - ws.slf = slf - if strProxyPath != "" { - proxy := func(_ *http.Request) (*url.URL, error) { - return url.Parse(strProxyPath) - } - - if timeoutsec > 0 { - tosec := timeoutsec * time.Second - ws.WsDailer = &websocket.Dialer{Proxy: proxy, HandshakeTimeout: tosec} - } else { - ws.WsDailer = &websocket.Dialer{Proxy: proxy} - } - } else { - if timeoutsec > 0 { - tosec := timeoutsec * time.Second - ws.WsDailer = &websocket.Dialer{HandshakeTimeout: tosec} - } else { - ws.WsDailer = &websocket.Dialer{} - } - } - - ws.url = strurl - ws.ping = []byte(`ping`) - return nil -} - -func (ws *WebsocketClient) SetPing(ping string) { - ws.ping = []byte(ping) -} - -//OnRun ... -func (ws *WebsocketClient) OnRun() error { - defer func() { - if r := recover(); r != nil { - coreInfo := string(debug.Stack()) - coreInfo += "\n" + fmt.Sprintf("Core WebsocketClient url is %s. Core information is %v\n", ws.url, r) - service.GetLogger().Printf(service.LEVER_FATAL, coreInfo) - go ws.OnRun() - } - }() - - for { - if ws.bRun == false { - break - } - - if ws.state == 0 { - time.Sleep(2 * time.Second) - ws.StartConnect() - } else if ws.state == 1 { - ws.state = 0 - close(ws.closer) - ws.conn.Close() - ws.slf.OnDisconnect() - } else if ws.state == 2 { - ws.conn.SetReadDeadline(time.Now().Add(ws.timeoutsec * time.Second)) - _, message, err := ws.conn.ReadMessage() - - if err != nil { - service.GetLogger().Printf(service.LEVER_WARN, "websocket client is disconnect [%s],information is %v", ws.url, err) - ws.conn.Close() - ws.state = 0 - close(ws.closer) - ws.slf.OnDisconnect() - continue - } - - ws.slf.OnReadMessage(message) - } - } - - return nil -} - -//StartConnect ... -func (ws *WebsocketClient) StartConnect() error { - - var err error - ws.conn, _, err = ws.WsDailer.Dial(ws.url, nil) - service.GetLogger().Printf(sysmodule.LEVER_INFO, "connecting %s, %+v\n", ws.url, err) - if err != nil { - return err - } - ws.closer = make(chan bool) - ws.bwritemsg = make(chan []byte, MAX_WRITE_MSG) - ws.state = 2 - ws.slf.OnConnected() - - return nil -} - -//Start ... -func (ws *WebsocketClient) Start() error { - - if ws.bRun == false { - ws.bRun = true - ws.state = 0 - go ws.OnRun() - go ws.writeMsg() - } - return nil -} - -//触发 -func (ws *WebsocketClient) writeMsg() error { - //dump处理 - defer func() { - if r := recover(); r != nil { - coreInfo := string(debug.Stack()) - coreInfo += "\n" + fmt.Sprintf("Core WebsocketClient url is %s. Core information is %v\n", ws.url, r) - service.GetLogger().Printf(service.LEVER_FATAL, coreInfo) - go ws.writeMsg() - } - }() - - timerC := time.NewTicker(time.Second * 5).C - for { - if ws.bRun == false { - break - } - - if ws.state == 0 { - time.Sleep(1 * time.Second) - continue - } - select { - case _, ok := <-ws.closer: - if ok == false { - break - } - - case <-timerC: - if ws.state == 2 { - err := ws.WriteMessage(ws.ping) - if err != nil { - service.GetLogger().Printf(service.LEVER_WARN, "websocket client is disconnect [%s],information is %v", ws.url, err) - ws.state = 0 - ws.conn.Close() - ws.slf.OnDisconnect() - } - } - case msg, ok := <-ws.bwritemsg: - if ok == true && ws.state == 2 { - ws.conn.SetWriteDeadline(time.Now().Add(ws.timeoutsec * time.Second)) - err := ws.conn.WriteMessage(websocket.TextMessage, msg) - if err != nil { - service.GetLogger().Printf(service.LEVER_WARN, "websocket client is disconnect [%s],information is %v", ws.url, err) - ws.state = 0 - ws.conn.Close() - ws.slf.OnDisconnect() - } - } - } - } - - return nil -} - -//ReConnect ... -func (ws *WebsocketClient) ReConnect() { - ws.state = 1 -} - -//WriteMessage ... -func (ws *WebsocketClient) WriteMessage(msg []byte) error { - if ws.closer == nil || ws.bwritemsg == nil { - service.GetLogger().Printf(service.LEVER_WARN, "WriteMessage data fail,websocket client is disconnect.") - return errors.New("riteMessage data fail,websocket client is disconnect.") - } - select { - case <-ws.closer: - service.GetLogger().Printf(service.LEVER_WARN, "WriteMessage data fail,websocket client is disconnect.") - return errors.New("riteMessage data fail,websocket client is disconnect.") - default: - if len(ws.bwritemsg) < MAX_WRITE_MSG { - ws.bwritemsg <- msg - } else { - service.GetLogger().Printf(service.LEVER_ERROR, "WriteMessage data fail,bwriteMsg is overload.") - return errors.New("WriteMessage data fail,bwriteMsg is overload.") - } - - } - - return nil -} - -//OnDisconnect ... -func (ws *WebsocketClient) OnDisconnect() error { - - return nil -} - -//OnConnected ... -func (ws *WebsocketClient) OnConnected() error { - - return nil -} - -//OnReadMessage 触发 -func (ws *WebsocketClient) OnReadMessage(msg []byte) error { - - return nil -} - -//Stop ... -func (ws *WebsocketClient) Stop() { - ws.bRun = false -} diff --git a/network/websocketserver.go b/network/websocketserver.go deleted file mode 100644 index f42f952..0000000 --- a/network/websocketserver.go +++ /dev/null @@ -1,328 +0,0 @@ -package network - -import ( - "crypto/tls" - "errors" - "fmt" - "github.com/duanhf2012/origin/util" - "net/http" - "os" - "runtime/debug" - "sync" - "time" - - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" - - "github.com/gorilla/mux" - "github.com/gorilla/websocket" - "github.com/gotoxu/cors" -) - -type IWebsocketServer interface { - SendMsg(clientid uint64, messageType int, msg []byte) bool - CreateClient(conn *websocket.Conn) *WSClient - Disconnect(clientid uint64) - ReleaseClient(pclient *WSClient) - Clients() []uint64 - BroadcastMsg(messageType int, msg []byte) int -} - -type IMessageReceiver interface { - initReciver(messageReciver IMessageReceiver, websocketServer IWebsocketServer) - - OnConnected(clientid uint64) - OnDisconnect(clientid uint64, err error) - OnRecvMsg(clientid uint64, msgtype int, data []byte) - OnHandleHttp(w http.ResponseWriter, r *http.Request) - IsInit() bool -} - -type Reciver struct { - messageReciver IMessageReceiver - bEnableCompression bool -} - -type BaseMessageReciver struct { - messageReciver IMessageReceiver - WsServer IWebsocketServer -} - -type WSClient struct { - clientid uint64 - conn *websocket.Conn - bwritemsg chan WSMessage -} - -type WSMessage struct { - msgtype int - bwritemsg []byte -} - -type WebsocketServer struct { - wsUri string - maxClientid uint64 //记录当前最新clientid - mapClient map[uint64]*WSClient - locker sync.RWMutex - - port uint16 - - httpserver *http.Server - reciver map[string]Reciver - - caList []CA - - iswss bool -} - -const ( - MAX_MSG_COUNT = 20480 -) - -func (slf *WebsocketServer) Init(port uint16) { - slf.port = port - slf.mapClient = make(map[uint64]*WSClient) -} - -func (slf *WebsocketServer) CreateClient(conn *websocket.Conn) *WSClient { - slf.locker.Lock() - slf.maxClientid++ - clientid := slf.maxClientid - pclient := &WSClient{clientid, conn, make(chan WSMessage, MAX_MSG_COUNT+1)} - slf.mapClient[pclient.clientid] = pclient - slf.locker.Unlock() - - service.GetLogger().Printf(sysmodule.LEVER_INFO, "Client id %d is connected.", clientid) - return pclient -} - -func (slf *WebsocketServer) ReleaseClient(pclient *WSClient) { - pclient.conn.Close() - slf.locker.Lock() - delete(slf.mapClient, pclient.clientid) - slf.locker.Unlock() - //关闭写管道 - close(pclient.bwritemsg) - service.GetLogger().Printf(sysmodule.LEVER_INFO, "Client id %d is disconnected.", pclient.clientid) -} - -func (slf *WebsocketServer) SetupReciver(pattern string, messageReciver IMessageReceiver, bEnableCompression bool) { - messageReciver.initReciver(messageReciver, slf) - - if slf.reciver == nil { - slf.reciver = make(map[string]Reciver) - } - slf.reciver[pattern] = Reciver{messageReciver, bEnableCompression} -} - -func (slf *WebsocketServer) startListen() { - listenPort := fmt.Sprintf(":%d", slf.port) - - var tlscatList []tls.Certificate - var tlsConfig *tls.Config - for _, cadata := range slf.caList { - cer, err := tls.LoadX509KeyPair(cadata.certfile, cadata.keyfile) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "load CA %s-%s file is error :%s", cadata.certfile, cadata.keyfile, err.Error()) - os.Exit(1) - return - } - tlscatList = append(tlscatList, cer) - } - - if len(tlscatList) > 0 { - tlsConfig = &tls.Config{Certificates: tlscatList} - } - - slf.httpserver = &http.Server{ - Addr: listenPort, - Handler: slf.initRouterHandler(), - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - TLSConfig: tlsConfig, - } - - var err error - if slf.iswss == true { - err = slf.httpserver.ListenAndServeTLS("", "") - } else { - err = slf.httpserver.ListenAndServe() - } - - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "http.ListenAndServe(%d, nil) error:%v\n", slf.port, err) - os.Exit(1) - } -} - -func (slf *WSClient) startSendMsg() { - for { - msgbuf, ok := <-slf.bwritemsg - if ok == false { - break - } - slf.conn.SetWriteDeadline(time.Now().Add(15 * time.Second)) - err := slf.conn.WriteMessage(msgbuf.msgtype, msgbuf.bwritemsg) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_INFO, "write client id %d is error :%v\n", slf.clientid, err) - break - } - } -} - -func (slf *WebsocketServer) Start() { - - go slf.startListen() -} - -func (slf *WebsocketServer) Clients() []uint64 { - slf.locker.RLock() - defer slf.locker.RUnlock() - r := make([]uint64, 0, len(slf.mapClient)) - for i, _ := range slf.mapClient { - r = append(r, i) - } - return r -} - -func (slf *WebsocketServer) BroadcastMsg(messageType int, msg []byte) int { - slf.locker.RLock() - defer slf.locker.RUnlock() - err := 0 - wsMsg := WSMessage{messageType, msg} - for _, value := range slf.mapClient { - if len(value.bwritemsg) >= MAX_MSG_COUNT { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "message chan is full :%d\n", len(value.bwritemsg)) - err++ - } - value.bwritemsg <- wsMsg - } - return err -} - -func (slf *WebsocketServer) SendMsg(clientid uint64, messageType int, msg []byte) bool { - slf.locker.RLock() - defer slf.locker.RUnlock() - value, ok := slf.mapClient[clientid] - if ok == false { - return false - } - - if len(value.bwritemsg) >= MAX_MSG_COUNT { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "message chan is full :%d\n", len(value.bwritemsg)) - return false - } - - value.bwritemsg <- WSMessage{messageType, msg} - - return true -} - -func (slf *WebsocketServer) Disconnect(clientid uint64) { - slf.locker.Lock() - defer slf.locker.Unlock() - value, ok := slf.mapClient[clientid] - if ok == false { - return - } - - value.conn.Close() -} - -func (slf *WebsocketServer) Stop() { -} - -func (slf *BaseMessageReciver) startReadMsg(pclient *WSClient) { - defer func() { - if r := recover(); r != nil { - var coreInfo string - coreInfo = string(debug.Stack()) - coreInfo += "\n" + fmt.Sprintf("Core information is %v\n", r) - service.GetLogger().Printf(service.LEVER_FATAL, coreInfo) - slf.messageReciver.OnDisconnect(pclient.clientid, errors.New("Core dump")) - slf.WsServer.ReleaseClient(pclient) - } - }() - - var maxTimeStamp int64 - var maxMsgType int - logMinMsgTime :=time.Millisecond*300 - - statisticsIntervalTm := util.Timer{} - statisticsIntervalTm.SetupTimer(1000 * 15)//15秒间隔 - for { - pclient.conn.SetReadDeadline(time.Now().Add(15 * time.Second)) - msgtype, message, err := pclient.conn.ReadMessage() - if err != nil { - slf.messageReciver.OnDisconnect(pclient.clientid, err) - slf.WsServer.ReleaseClient(pclient) - - return - } - - if statisticsIntervalTm.CheckTimeOut() { - service.GetLogger().Printf(service.LEVER_INFO, "MaxMsgtype:%d,diff:%d",maxMsgType,maxTimeStamp) - } - //记录处理时间 - startRecvTm := time.Now().UnixNano() - slf.messageReciver.OnRecvMsg(pclient.clientid, msgtype, message) - diff := time.Now().UnixNano() - startRecvTm - if diff> maxTimeStamp{ - maxTimeStamp = diff - maxMsgType = msgtype - } - if diff >= int64(logMinMsgTime) { - service.GetLogger().Printf(service.LEVER_WARN, "Process slowly MaxMsgtype:%d,diff:%d",maxMsgType,maxTimeStamp) - } - } -} - -func (slf *BaseMessageReciver) initReciver(messageReciver IMessageReceiver, websocketServer IWebsocketServer) { - slf.messageReciver = messageReciver - slf.WsServer = websocketServer -} - -func (slf *BaseMessageReciver) OnConnected(clientid uint64) { -} - -func (slf *BaseMessageReciver) OnDisconnect(clientid uint64, err error) { -} - -func (slf *BaseMessageReciver) OnRecvMsg(clientid uint64, msgtype int, data []byte) { -} - -func (slf *BaseMessageReciver) OnHandleHttp(w http.ResponseWriter, r *http.Request) { - conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024) - if err != nil { - http.Error(w, "Could not open websocket connection", http.StatusBadRequest) - return - } - - pclient := slf.WsServer.CreateClient(conn) - slf.messageReciver.OnConnected(pclient.clientid) - go pclient.startSendMsg() - go slf.startReadMsg(pclient) -} - -func (slf *WebsocketServer) initRouterHandler() http.Handler { - r := mux.NewRouter() - - for pattern, reciver := range slf.reciver { - if reciver.messageReciver.IsInit() == true { - r.HandleFunc(pattern, reciver.messageReciver.OnHandleHttp) - } - } - - cors := cors.AllowAll() - return cors.Handler(r) -} - -func (slf *WebsocketServer) SetWSS(certfile string, keyfile string) bool { - if certfile == "" || keyfile == "" { - return false - } - slf.caList = append(slf.caList, CA{certfile, keyfile}) - slf.iswss = true - return true -} diff --git a/network/wsagentserver.go b/network/wsagentserver.go deleted file mode 100644 index 6734878..0000000 --- a/network/wsagentserver.go +++ /dev/null @@ -1,323 +0,0 @@ -package network - -import ( - "crypto/tls" - "errors" - "fmt" - "net/http" - "os" - "reflect" - "runtime/debug" - "sync" - "time" - - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" - "github.com/gotoxu/cors" - - "github.com/gorilla/mux" - "github.com/gorilla/websocket" -) - -type IWSAgentServer interface { - SendMsg(agentid uint32, messageType int, msg []byte) bool - CreateAgent(urlPattern string, conn *websocket.Conn) IAgent - Disconnect(agentid uint32) - ReleaseAgent(iagent IAgent) -} - -type IAgent interface { - initAgent(conn *websocket.Conn, agentid uint32, iagent IAgent, WSAgentServer IWSAgentServer) - startReadMsg() - startSendMsg() - OnConnected() - OnDisconnect(err error) - OnRecvMsg(msgtype int, data []byte) - //OnHandleHttp(w http.ResponseWriter, r *http.Request) - GetAgentId() uint32 - getConn() *websocket.Conn - getWriteMsgChan() chan WSAgentMessage -} - -type BaseAgent struct { - service.BaseModule - WsServer IWSAgentServer - agent IAgent - agentid uint32 - conn *websocket.Conn - bwritemsg chan WSAgentMessage - iagent IAgent -} - -type WSAgentMessage struct { - msgtype int - bwritemsg []byte -} - -type WSAgentServer struct { - service.BaseModule - wsUri string - maxAgentid uint32 //记录当前最新agentid - //mapAgent map[uint32]IAgent - locker sync.Mutex - - port uint16 - - httpserver *http.Server - regAgent map[string]reflect.Type - - caList []CA - iswss bool -} - -const ( - MAX_AGENT_MSG_COUNT = 20480 -) - -func (slf *WSAgentServer) Init(port uint16) { - slf.port = port -} - -func (slf *WSAgentServer) CreateAgent(urlPattern string, conn *websocket.Conn) IAgent { - slf.locker.Lock() - iAgent, ok := slf.regAgent[urlPattern] - if ok == false { - slf.locker.Unlock() - service.GetLogger().Printf(sysmodule.LEVER_WARN, "Cannot find %s pattern!", urlPattern) - return nil - } - - v := reflect.New(iAgent).Elem().Addr().Interface() - if v == nil { - slf.locker.Unlock() - service.GetLogger().Printf(sysmodule.LEVER_WARN, "new %s pattern agent type is error!", urlPattern) - return nil - } - - pModule := v.(service.IModule) - iagent := v.(IAgent) - slf.maxAgentid++ - agentid := slf.maxAgentid - iagent.initAgent(conn, agentid, iagent, slf) - slf.AddModule(pModule) - - slf.locker.Unlock() - - service.GetLogger().Printf(sysmodule.LEVER_INFO, "Agent id %d is connected.", iagent.GetAgentId()) - return iagent -} - -func (slf *WSAgentServer) ReleaseAgent(iagent IAgent) { - iagent.getConn().Close() - slf.locker.Lock() - slf.ReleaseModule(iagent.GetAgentId()) - //delete(slf.mapAgent, iagent.GetAgentId()) - slf.locker.Unlock() - //关闭写管道 - close(iagent.getWriteMsgChan()) - service.GetLogger().Printf(sysmodule.LEVER_INFO, "Agent id %d is disconnected.", iagent.GetAgentId()) -} - -func (slf *WSAgentServer) SetupAgent(pattern string, agent IAgent, bEnableCompression bool) { - if slf.regAgent == nil { - slf.regAgent = make(map[string]reflect.Type) - } - - slf.regAgent[pattern] = reflect.TypeOf(agent).Elem() //reflect.TypeOf(agent).Elem() -} - -func (slf *WSAgentServer) startListen() { - listenPort := fmt.Sprintf(":%d", slf.port) - - var tlscatList []tls.Certificate - var tlsConfig *tls.Config - for _, cadata := range slf.caList { - cer, err := tls.LoadX509KeyPair(cadata.certfile, cadata.keyfile) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "load CA %s-%s file is error :%s", cadata.certfile, cadata.keyfile, err.Error()) - os.Exit(1) - return - } - tlscatList = append(tlscatList, cer) - } - - if len(tlscatList) > 0 { - tlsConfig = &tls.Config{Certificates: tlscatList} - } - - slf.httpserver = &http.Server{ - Addr: listenPort, - Handler: slf.initRouterHandler(), - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - TLSConfig: tlsConfig, - } - - var err error - if slf.iswss == true { - err = slf.httpserver.ListenAndServeTLS("", "") - } else { - err = slf.httpserver.ListenAndServe() - } - - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_FATAL, "http.ListenAndServe(%d, nil) error:%v\n", slf.port, err) - os.Exit(1) - } -} - -func (slf *BaseAgent) startSendMsg() { - for { - msgbuf, ok := <-slf.bwritemsg - if ok == false { - break - } - slf.conn.SetWriteDeadline(time.Now().Add(15 * time.Second)) - err := slf.conn.WriteMessage(msgbuf.msgtype, msgbuf.bwritemsg) - if err != nil { - service.GetLogger().Printf(sysmodule.LEVER_INFO, "write agent id %d is error :%v\n", slf.GetAgentId(), err) - break - } - } -} - -func (slf *WSAgentServer) Start() { - go slf.startListen() -} - -func (slf *WSAgentServer) GetAgentById(agentid uint32) IAgent { - pModule := slf.GetModuleById(agentid) - if pModule == nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "GetAgentById :%d is fail.\n", agentid) - return nil - } - - return pModule.(IAgent) -} - -func (slf *WSAgentServer) SendMsg(agentid uint32, messageType int, msg []byte) bool { - slf.locker.Lock() - defer slf.locker.Unlock() - - iagent := slf.GetAgentById(agentid) - if iagent == nil { - return false - } - - if len(iagent.getWriteMsgChan()) >= MAX_AGENT_MSG_COUNT { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "message chan is full :%d\n", len(iagent.getWriteMsgChan())) - return false - } - - iagent.getWriteMsgChan() <- WSAgentMessage{messageType, msg} - - return true -} - -func (slf *WSAgentServer) Disconnect(agentid uint32) { - slf.locker.Lock() - defer slf.locker.Unlock() - iagent := slf.GetAgentById(agentid) - if iagent == nil { - return - } - - iagent.getConn().Close() -} - -func (slf *WSAgentServer) Stop() { -} - -func (slf *BaseAgent) startReadMsg() { - defer func() { - if r := recover(); r != nil { - var coreInfo string - coreInfo = string(debug.Stack()) - coreInfo += "\n" + fmt.Sprintf("Core information is %v\n", r) - service.GetLogger().Printf(service.LEVER_FATAL, coreInfo) - slf.agent.OnDisconnect(errors.New("Core dump")) - slf.WsServer.ReleaseAgent(slf.agent) - } - }() - - slf.agent.OnConnected() - for { - slf.conn.SetReadDeadline(time.Now().Add(15 * time.Second)) - msgtype, message, err := slf.conn.ReadMessage() - if err != nil { - slf.agent.OnDisconnect(err) - slf.WsServer.ReleaseAgent(slf.agent) - return - } - - slf.agent.OnRecvMsg(msgtype, message) - } -} - -func (slf *WSAgentServer) initRouterHandler() http.Handler { - r := mux.NewRouter() - - for pattern, _ := range slf.regAgent { - r.HandleFunc(pattern, slf.OnHandleHttp) - } - - cors := cors.AllowAll() - return cors.Handler(r) -} - -func (slf *WSAgentServer) SetWSS(certfile string, keyfile string) bool { - if certfile == "" || keyfile == "" { - return false - } - slf.caList = append(slf.caList, CA{certfile, keyfile}) - slf.iswss = true - return true -} - -func (slf *BaseAgent) GetAgentId() uint32 { - return slf.agentid -} - -func (slf *BaseAgent) initAgent(conn *websocket.Conn, agentid uint32, iagent IAgent, WSAgentServer IWSAgentServer) { - slf.agent = iagent - slf.WsServer = WSAgentServer - slf.bwritemsg = make(chan WSAgentMessage, MAX_AGENT_MSG_COUNT) - slf.agentid = agentid - slf.conn = conn -} - -func (slf *BaseAgent) OnConnected() { -} - -func (slf *BaseAgent) OnDisconnect(err error) { -} - -func (slf *BaseAgent) OnRecvMsg(msgtype int, data []byte) { -} - -func (slf *BaseAgent) getConn() *websocket.Conn { - return slf.conn -} - -func (slf *BaseAgent) getWriteMsgChan() chan WSAgentMessage { - return slf.bwritemsg -} - -func (slf *BaseAgent) SendMsg(agentid uint32, messageType int, msg []byte) bool { - return slf.WsServer.SendMsg(agentid, messageType, msg) -} - -func (slf *WSAgentServer) OnHandleHttp(w http.ResponseWriter, r *http.Request) { - conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024) - if err != nil { - http.Error(w, "Could not open websocket connection!", http.StatusBadRequest) - return - } - - agent := slf.CreateAgent(r.URL.Path, conn) - fmt.Print(agent.GetAgentId()) - slf.AddModule(agent.(service.IModule)) - go agent.startSendMsg() - go agent.startReadMsg() -} diff --git a/node/node.go b/node/node.go new file mode 100644 index 0000000..7fb8517 --- /dev/null +++ b/node/node.go @@ -0,0 +1,115 @@ +package node + +import ( + "fmt" + "github.com/duanhf2012/originnet/service" + "github.com/duanhf2012/originnet/cluster" + "io/ioutil" + "os" + "os/signal" + "strconv" + "syscall" + "time" +) + +var closeSig chan bool +var sigs chan os.Signal + +var preSetupService []service.IService //预安装 + +func init() { + closeSig = make(chan bool,1) + sigs = make(chan os.Signal, 3) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM,syscall.Signal(10)) +} + + +func getRunProcessPid() (int,error) { + f, err := os.OpenFile(os.Args[0]+".pid", os.O_RDONLY, 0600) + defer f.Close() + if err!= nil { + return 0,err + } + + pidbyte,errs := ioutil.ReadAll(f) + if errs!=nil { + return 0,errs + } + + return strconv.Atoi(string(pidbyte)) +} + +func writeProcessPid() { + //pid + f, err := os.OpenFile(os.Args[0]+".pid", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + defer f.Close() + if err != nil { + fmt.Println(err.Error()) + os.Exit(-1) + } else { + _,err=f.Write([]byte(fmt.Sprintf("%d",os.Getpid()))) + if err != nil { + fmt.Println(err.Error()) + os.Exit(-1) + } + } +} + +func GetNodeId() int { + return 1 +} + +func Init(){ + //1.初始化集群 + err := cluster.GetCluster().Init(GetNodeId()) + if err != nil { + panic(err) + } + + //2.service模块初始化 + service.Init(closeSig) + + //3.初始化预安装的服务 + for _,s := range preSetupService { + pServiceCfg := cluster.GetCluster().GetServiceCfg(s.GetName()) + s.Init(s,cluster.GetRpcClient,cluster.GetRpcServer,pServiceCfg) + //是否配置的service + if cluster.GetCluster().IsConfigService(s.GetName()) == false { + continue + } + service.Setup(s) + } +} + +func Start() { + cluster.GetCluster().Start() + service.Start() + writeProcessPid() + for { + select { + case <-sigs: + fmt.Printf("Recv stop sig") + break + default: + time.Sleep(time.Second) + } + } + + close(closeSig) + service.WaitStop() +} + + +func Setup(s ...service.IService) { + for _,sv := range s { + preSetupService = append(preSetupService,sv) + } +} + +func GetService(servicename string) service.IService { + return service.GetService(servicename) +} + +func SetConfigDir(configdir string){ + cluster.SetConfigDir(configdir) +} \ No newline at end of file diff --git a/originnode/node.go b/originnode/node.go deleted file mode 100644 index b4133f4..0000000 --- a/originnode/node.go +++ /dev/null @@ -1,175 +0,0 @@ -package originnode - -import ( - "fmt" - "log" - "strconv" - "strings" - "time" - - "github.com/duanhf2012/origin/util" - - "net/http" - _ "net/http/pprof" - "os" - "os/signal" - "sync" - "syscall" - - "github.com/duanhf2012/origin/cluster" - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" - "github.com/duanhf2012/origin/sysservice" -) - -type CExitCtl struct { - exitChan chan bool - waitGroup *sync.WaitGroup -} - -type COriginNode struct { - CExitCtl - serviceManager service.IServiceManager - sigs chan os.Signal - debugListenAddress string -} - -var initservicelist []service.IService - -func InitService(iservice service.IService) { - initservicelist = append(initservicelist, iservice) -} - -func (s *COriginNode) Init() { - - s.SetupService(initservicelist...) - - //初始化全局模块 - logger := service.InstanceServiceMgr().FindService("syslog").(service.ILogger) - ret := service.InstanceServiceMgr().Init(logger, s.exitChan, s.waitGroup) - if ret == false { - os.Exit(-1) - } - - util.Log = logger.Printf - s.sigs = make(chan os.Signal, 1) - signal.Notify(s.sigs, syscall.SIGINT, syscall.SIGTERM) -} - -// OpenDebugCheck ("localhost:6060")...http://localhost:6060/ -func (s *COriginNode) OpenDebugCheck(listenAddress string) { - s.debugListenAddress = listenAddress -} - -func (s *COriginNode) SetupService(services ...service.IService) { - ppService := &sysservice.PProfService{} - services = append(services, ppService) - cluster.InstanceClusterMgr().AddLocalService(ppService) - for i := 0; i < len(services); i++ { - services[i].Init(services[i]) - - if cluster.InstanceClusterMgr().HasLocalService(services[i].GetServiceName()) == true { - service.InstanceServiceMgr().Setup(services[i]) - } - } -} - -func (s *COriginNode) Start() { - if s.debugListenAddress != "" { - go func() { - log.Println(http.ListenAndServe(s.debugListenAddress, nil)) - }() - } - - //开始运行集群 - cluster.InstanceClusterMgr().Start() - - //开启所有服务 - service.InstanceServiceMgr().Start() - - //监听退出信号 - select { - case <-s.sigs: - service.GetLogger().Printf(sysmodule.LEVER_WARN, "Recv stop sig") - fmt.Printf("Recv stop sig") - } - - //停止运行程序 - s.Stop() - service.GetLogger().Printf(sysmodule.LEVER_INFO, "Node stop run...") -} - -func (s *COriginNode) Stop() { - close(s.exitChan) - s.waitGroup.Wait() -} - -func GetCmdParamNodeId() int { - if len(os.Args) < 2 { - return 0 - //return fmt.Errorf("Param error not find NodeId=number") - } - - parts := strings.Split(os.Args[1], "=") - if len(parts) < 2 { - return 0 - //return fmt.Errorf("Param error not find NodeId=number") - } - - if parts[0] != "NodeId" { - return 0 - //return fmt.Errorf("Param error not find NodeId=number") - } - - //读取配置 - currentNodeid, err := strconv.Atoi(parts[1]) - if err != nil { - return 0 - } - - return currentNodeid -} - -func NewOriginNode() *COriginNode { - CurrentNodeId := GetCmdParamNodeId() - if CurrentNodeId == 0 { - fmt.Print("Param error not find NodeId=number") - os.Exit(-1) - } - - //创建模块 - node := new(COriginNode) - node.exitChan = make(chan bool) - node.waitGroup = &sync.WaitGroup{} - - //安装系统服务 - syslogservice := &sysservice.LogService{} - syslogservice.InitLog("syslog", fmt.Sprintf("syslog_%d", CurrentNodeId), sysmodule.LEVER_DEBUG) - service.InstanceServiceMgr().Setup(syslogservice) - - //初始化集群对象 - err := cluster.InstanceClusterMgr().Init(CurrentNodeId) - if err != nil { - fmt.Print(err) - os.Exit(-1) - return nil - } - - return node -} - -func (s *COriginNode) GetSysLog() *sysservice.LogService { - logService := service.InstanceServiceMgr().FindService("syslog") - if logService == nil { - fmt.Printf("Cannot find syslog service!") - os.Exit(-1) - return nil - } - - return logService.(*sysservice.LogService) -} - -func (s *COriginNode) EnableMonitorModule(checkInterval time.Duration){ - service.EnableDeadForMonitor(checkInterval) -} - diff --git a/rpc/client.go b/rpc/client.go index 4cef3e0..ffa5ddf 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -1,360 +1,208 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package rpc import ( - "bufio" - "encoding/gob" - "errors" "fmt" - "io" - "log" - "net" - "net/http" + "github.com/duanhf2012/originnet/log" + "github.com/duanhf2012/originnet/network" + "math" + "reflect" + "strings" "sync" "time" ) -// ServerError represents an error that has been returned from -// the remote side of the RPC connection. -type ServerError string - -func (e ServerError) Error() string { - return string(e) -} - -var ErrShutdown = errors.New("connection is shut down") - -// Call represents an active RPC. -type Call struct { - ServiceMethod string // The name of the service and method to call. - Args interface{} // The argument to the function (*struct). - Reply interface{} // The reply from the function (*struct). - Error error // After completion, the error status. - Done chan *Call // Strobes when call is complete. -} - -// Client represents an RPC Client. -// There may be multiple outstanding Calls associated -// with a single Client, and a Client may be used by -// multiple goroutines simultaneously. type Client struct { - codec ClientCodec + blocalhost bool + network.TCPClient + conn *network.TCPConn - reqMutex sync.Mutex // protects following - request Request - - mutex sync.Mutex // protects following - seq uint64 - pending map[uint64]*Call - closing bool // user has called Close - shutdown bool // server has told us to stop - bPipe bool + pendingLock sync.RWMutex + startSeq uint64 + pending map[uint64]*Call } -// A ClientCodec implements writing of RPC requests and -// reading of RPC responses for the client side of an RPC session. -// The client calls WriteRequest to write a request to the connection -// and calls ReadResponseHeader and ReadResponseBody in pairs -// to read responses. The client calls Close when finished with the -// connection. ReadResponseBody may be called with a nil -// argument to force the body of the response to be read and then -// discarded. -// See NewClient's comment for information about concurrent access. -type ClientCodec interface { - WriteRequest(*Request, interface{}) error - ReadResponseHeader(*Response) error - ReadResponseBody(interface{}) error - - Close() error +func (slf *Client) NewClientAgent(conn *network.TCPConn) network.Agent { + slf.conn = conn + return slf } -func (client *Client) IsClosed() bool { - client.reqMutex.Lock() - defer client.reqMutex.Unlock() - return client.shutdown || client.closing -} - -func (client *Client) send(call *Call, queueMode bool) { - client.reqMutex.Lock() - defer client.reqMutex.Unlock() - - // Register this call. - client.mutex.Lock() - if client.shutdown || client.closing { - client.mutex.Unlock() - call.Error = ErrShutdown - call.done() - return +func (slf *Client) Connect(addr string) error { + slf.Addr = addr + if strings.Index(addr,"localhost") == 0 { + slf.blocalhost = true + return nil } - seq := client.seq - client.seq++ - client.pending[seq] = call - client.mutex.Unlock() + slf.ConnNum = 1 + slf.ConnectInterval = time.Second*2 + slf.PendingWriteNum = 10000 + slf.AutoReconnect = true + slf.LenMsgLen =2 + slf.MinMsgLen = 2 + slf.MaxMsgLen = math.MaxUint16 + slf.NewAgent =slf.NewClientAgent + slf.LittleEndian = LittleEndian - // Encode and send the request. - client.request.Seq = seq - client.request.ServiceMethod = call.ServiceMethod - client.request.QueueMode = queueMode - err := client.codec.WriteRequest(&client.request, call.Args) - if err != nil { - client.mutex.Lock() - call = client.pending[seq] - delete(client.pending, seq) - client.mutex.Unlock() - if call != nil { - call.Error = err - call.done() - } + slf.pendingLock.Lock() + for _,v := range slf.pending { + v.Err = fmt.Errorf("node is disconnect.") + v.done <- v } + slf.pending = map[uint64]*Call{} + slf.pendingLock.Unlock() + slf.Start() + return nil } -func (client *Client) input() { - var err error - var response Response - for err == nil { - response = Response{} - err = client.codec.ReadResponseHeader(&response) - if err != nil { - break - } - seq := response.Seq - client.mutex.Lock() - call := client.pending[seq] - delete(client.pending, seq) - client.mutex.Unlock() - - switch { - case call == nil: - // We've got no pending call. That usually means that - // WriteRequest partially failed, and call was already - // removed; response is a server telling us about an - // error reading request body. We should still attempt - // to read error body, but there's no one to give it to. - err = client.codec.ReadResponseBody(nil) - if err != nil { - err = errors.New("reading error body: " + err.Error()) - } - case response.Error != "": - // We've got an error response. Give this to the request; - // any subsequent requests will get the ReadResponseBody - // error if there is one. - call.Error = ServerError(response.Error) - err = client.codec.ReadResponseBody(nil) - if err != nil { - err = errors.New("reading error body: " + err.Error()) - } - call.done() - default: - err = client.codec.ReadResponseBody(call.Reply) - if err != nil { - call.Error = errors.New("reading body " + err.Error()) - } - if client.bPipe { - err = nil - } - call.done() - } - } - // Terminate pending calls. - client.reqMutex.Lock() - client.mutex.Lock() - client.shutdown = true - closing := client.closing - if err == io.EOF { - if closing { - err = ErrShutdown - } else { - err = io.ErrUnexpectedEOF - } - } - for _, call := range client.pending { - call.Error = err - call.done() - } - client.mutex.Unlock() - client.reqMutex.Unlock() - if debugLog && err != io.EOF && !closing { - log.Println("rpc: client protocol error:", err) - } -} - -func (call *Call) done() { - select { - case call.Done <- call: - // ok - default: - // We don't want to block here. It is the caller's responsibility to make - // sure the channel has enough buffer space. See comment in Go(). - if debugLog { - log.Println("rpc: discarding Call reply due to insufficient Done chan capacity") - } - } -} - -// NewClient returns a new Client to handle requests to the -// set of services at the other end of the connection. -// It adds a buffer to the write side of the connection so -// the header and payload are sent as a unit. -// -// The read and write halves of the connection are serialized independently, -// so no interlocking is required. However each half may be accessed -// concurrently so the implementation of conn should protect against -// concurrent reads or concurrent writes. -func NewClient(conn io.ReadWriteCloser, isPipe bool) *Client { - encBuf := bufio.NewWriter(conn) - client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf} - return NewClientWithCodec(client, isPipe) -} - -// NewClientWithCodec is like NewClient but uses the specified -// codec to encode requests and decode responses. -func NewClientWithCodec(codec ClientCodec, isPipe bool) *Client { - client := &Client{ - codec: codec, - pending: make(map[uint64]*Call), - bPipe: isPipe, - } - go client.input() - return client -} - -type gobClientCodec struct { - rwc io.ReadWriteCloser - dec *gob.Decoder - enc *gob.Encoder - encBuf *bufio.Writer -} - -func (c *gobClientCodec) WriteRequest(r *Request, body interface{}) (err error) { - if err = c.enc.Encode(r); err != nil { - return - } - if err = c.enc.Encode(body); err != nil { - return - } - return c.encBuf.Flush() -} - -func (c *gobClientCodec) ReadResponseHeader(r *Response) error { - return c.dec.Decode(r) -} - -func (c *gobClientCodec) ReadResponseBody(body interface{}) error { - return c.dec.Decode(body) -} - -func (c *gobClientCodec) Close() error { - return c.rwc.Close() -} - -// DialHTTP connects to an HTTP RPC server at the specified network address -// listening on the default HTTP RPC path. -func DialHTTP(network, address string) (*Client, error) { - return DialHTTPPath(network, address, DefaultRPCPath) -} - -// DialHTTPPath connects to an HTTP RPC server -// at the specified network address and path. -func DialHTTPPath(network, address, path string) (*Client, error) { - var err error - conn, err := net.Dial(network, address) - - if err != nil { - return nil, err - } - tcpconn, _ := conn.(*net.TCPConn) - tcpconn.SetNoDelay(true) - - io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n") - - // Require successful HTTP response - // before switching to RPC protocol. - resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"}) - if err == nil && resp.Status == connected { - return NewClient(conn, false), nil - } - if err == nil { - err = errors.New("unexpected HTTP response: " + resp.Status) - } - conn.Close() - return nil, &net.OpError{ - Op: "dial-http", - Net: network + " " + address, - Addr: nil, - Err: err, - } -} - -// Dial connects to an RPC server at the specified network address. -func Dial(network, address string) (*Client, error) { - conn, err := net.Dial(network, address) - if err != nil { - return nil, err - } - tcpconn, _ := conn.(*net.TCPConn) - tcpconn.SetNoDelay(true) - return NewClient(conn, false), nil -} - -func DialTimeOut(network, address string, timeout time.Duration) (*Client, error) { - conn, err := net.DialTimeout(network, address, timeout) - if err != nil { - return nil, err - } - tcpconn, _ := conn.(*net.TCPConn) - tcpconn.SetNoDelay(true) - return NewClient(conn, false), nil -} - -// Close calls the underlying codec's Close method. If the connection is already -// shutting down, ErrShutdown is returned. -func (client *Client) Close() error { - client.mutex.Lock() - if client.closing { - client.mutex.Unlock() - return ErrShutdown - } - client.closing = true - client.mutex.Unlock() - return client.codec.Close() -} - -// Go invokes the function asynchronously. It returns the Call structure representing -// the invocation. The done channel will signal when the call is complete by returning -// the same Call object. If done is nil, Go will allocate a new channel. -// If non-nil, done must be buffered or Go will deliberately crash. -func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call, queueMode bool) *Call { +func (slf *Client) AsycGo(rpcHandler IRpcHandler,mutiCoroutine bool,serviceMethod string,callback reflect.Value, args interface{},replyParam interface{}) error { call := new(Call) - call.ServiceMethod = serviceMethod - call.Args = args - call.Reply = reply - if done == nil { - done = make(chan *Call, 10) // buffered. - } else { - // If caller passes done != nil, it must arrange that - // done has enough buffer for the number of simultaneous - // RPCs that will be using that channel. If the channel - // is totally unbuffered, it's best not to run at all. - if cap(done) == 0 { - log.Panic("rpc: done channel is unbuffered") - } + call.Reply = replyParam + call.callback = &callback + call.rpcHandler = rpcHandler + + request := &RpcRequest{} + request.NoReply = false + request.MutiCoroutine = mutiCoroutine + call.Arg = args + slf.pendingLock.Lock() + slf.startSeq += 1 + call.Seq = slf.startSeq + request.Seq = slf.startSeq + slf.pending[call.Seq] = call + slf.pendingLock.Unlock() + + request.ServiceMethod = serviceMethod + var herr error + request.InParam,herr = processor.Marshal(args) + if herr != nil { + return herr } - call.Done = done - client.send(call, queueMode) + + bytes,err := processor.Marshal(request) + if err != nil { + return err + } + if slf.conn == nil { + return fmt.Errorf("Rpc server is disconnect,call %s is fail!",serviceMethod) + } + err = slf.conn.WriteMsg(bytes) + if err != nil { + call.Err = err + } + + return call.Err +} + +func (slf *Client) Go(noReply bool,mutiCoroutine bool,serviceMethod string, args interface{},reply interface{}) *Call { + call := new(Call) + call.done = make(chan *Call,1) + call.Reply = reply + + request := &RpcRequest{} + request.MutiCoroutine = mutiCoroutine + request.NoReply = noReply + call.Arg = args + slf.pendingLock.Lock() + slf.startSeq += 1 + call.Seq = slf.startSeq + request.Seq = slf.startSeq + slf.pending[call.Seq] = call + slf.pendingLock.Unlock() + + request.ServiceMethod = serviceMethod + var herr error + request.InParam,herr = processor.Marshal(args) + if herr != nil { + call.Err = herr + return call + } + + bytes,err := processor.Marshal(request) + if err != nil { + call.Err = err + return call + } + + err = slf.conn.WriteMsg(bytes) + if err != nil { + call.Err = err + } + return call } -// Call invokes the named function, waits for it to complete, and returns its error status. -func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error { - select { - case call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1), false).Done: - return call.Error - case <-time.After(30 * time.Second): +type RequestHandler func(Returns interface{},Err error) + +type RpcRequest struct { + //packhead + Seq uint64 // sequence number chosen by client + ServiceMethod string // format: "Service.Method" + NoReply bool //是否需要返回 + MutiCoroutine bool // 是否多协程模式 + + //packbody + InParam []byte + localReply interface{} + localParam interface{} //本地调用的参数列表 + requestHandle RequestHandler + + callback *reflect.Value +} + +type RpcResponse struct { + //head + Seq uint64 // sequence number chosen by client + Err error + + //returns + Returns []byte +} + + +func (slf *Client) Run(){ + for { + bytes,err := slf.conn.ReadMsg() + if err != nil { + slf.Close() + slf.Start() + } + //1.解析head + respone := &RpcResponse{} + err = processor.Unmarshal(bytes,respone) + if err != nil { + log.Error("rpcClient Unmarshal head error,error:%+v",err) + continue + } + + slf.pendingLock.Lock() + v,ok := slf.pending[respone.Seq] + if ok == false { + log.Error("rpcClient cannot find seq %d in pending",respone.Seq) + slf.pendingLock.Unlock() + }else { + delete(slf.pending,respone.Seq) + slf.pendingLock.Unlock() + + err = processor.Unmarshal(respone.Returns,v.Reply) + if err != nil { + log.Error("rpcClient Unmarshal body error,error:%+v",err) + continue + } + + if v.callback.IsValid() { + v.rpcHandler.(*RpcHandler).callResponeCallBack<-v + }else{ + //发送至接受者 + v.done <- v + } + + } } - //call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done - return errors.New(fmt.Sprintf("Call RPC %s is time out 30s", serviceMethod)) } + +func (slf *Client) OnClose(){ + if slf.blocalhost== false{ + //关闭时,重新连接 + slf.Start() + } +} \ No newline at end of file diff --git a/rpc/debug.go b/rpc/debug.go deleted file mode 100644 index a1d799f..0000000 --- a/rpc/debug.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rpc - -/* - Some HTML presented at http://machine:port/debug/rpc - Lists services, their methods, and some statistics, still rudimentary. -*/ - -import ( - "fmt" - "html/template" - "net/http" - "sort" -) - -const debugText = ` - - Services - {{range .}} -


- Service {{.Name}} -
- - - {{range .Method}} - - - - - {{end}} -
MethodCalls
{{.Name}}({{.Type.ArgType}}, {{.Type.ReplyType}}) error{{.Type.NumCalls}}
- {{end}} - - ` - -var debug = template.Must(template.New("RPC debug").Parse(debugText)) - -// If set, print log statements for internal and I/O errors. -var debugLog = false - -type debugMethod struct { - Type *methodType - Name string -} - -type methodArray []debugMethod - -type debugService struct { - Service *service - Name string - Method methodArray -} - -type serviceArray []debugService - -func (s serviceArray) Len() int { return len(s) } -func (s serviceArray) Less(i, j int) bool { return s[i].Name < s[j].Name } -func (s serviceArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func (m methodArray) Len() int { return len(m) } -func (m methodArray) Less(i, j int) bool { return m[i].Name < m[j].Name } -func (m methodArray) Swap(i, j int) { m[i], m[j] = m[j], m[i] } - -type debugHTTP struct { - *Server -} - -// Runs at /debug/rpc -func (server debugHTTP) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Build a sorted version of the data. - var services serviceArray - server.serviceMap.Range(func(snamei, svci interface{}) bool { - svc := svci.(*service) - ds := debugService{svc, snamei.(string), make(methodArray, 0, len(svc.method))} - for mname, method := range svc.method { - ds.Method = append(ds.Method, debugMethod{method, mname}) - } - sort.Sort(ds.Method) - services = append(services, ds) - return true - }) - sort.Sort(services) - err := debug.Execute(w, services) - if err != nil { - fmt.Fprintln(w, "rpc: error executing template:", err.Error()) - } -} diff --git a/rpc/gobrpc/processor.go b/rpc/gobrpc/processor.go new file mode 100644 index 0000000..0272095 --- /dev/null +++ b/rpc/gobrpc/processor.go @@ -0,0 +1 @@ +package gobrpc diff --git a/rpc/jsonprocessor.go b/rpc/jsonprocessor.go new file mode 100644 index 0000000..2539171 --- /dev/null +++ b/rpc/jsonprocessor.go @@ -0,0 +1,22 @@ +package rpc + +import ( + + "encoding/json" + +) + +type JsonProcessor struct { +} + + + +func (slf *JsonProcessor) Marshal(v interface{}) ([]byte, error){ + return json.Marshal(v) +} + +func (slf *JsonProcessor) Unmarshal(data []byte, v interface{}) error{ + + return json.Unmarshal(data,v) +} + diff --git a/rpc/netrpc.go b/rpc/netrpc.go new file mode 100644 index 0000000..9ab1e3e --- /dev/null +++ b/rpc/netrpc.go @@ -0,0 +1 @@ +package rpc diff --git a/rpc/rpchandler.go b/rpc/rpchandler.go new file mode 100644 index 0000000..f9f7a29 --- /dev/null +++ b/rpc/rpchandler.go @@ -0,0 +1,377 @@ +package rpc + +import ( + "fmt" + "github.com/duanhf2012/originnet/log" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +type FuncRpcClient func(serviceMethod string) ([]*Client,error) +type FuncRpcServer func() (*Server) +var NilError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()) + +type RpcMethodInfo struct { + method reflect.Method + iparam interface{} + oParam reflect.Value +} + +type RpcHandler struct { + callRequest chan *RpcRequest + rpcHandler IRpcHandler + mapfunctons map[string]RpcMethodInfo + funcRpcClient FuncRpcClient + funcRpcServer FuncRpcServer + + callResponeCallBack chan *Call //异步返回的回调 +} + +type IRpcHandler interface { + GetName() string + InitRpcHandler(rpcHandler IRpcHandler,getClientFun FuncRpcClient,getServerFun FuncRpcServer) + GetRpcHandler() IRpcHandler + PushRequest(callinfo *RpcRequest) + HandlerRpcRequest(request *RpcRequest) + HandlerRpcResponeCB(call *Call) + + GetRpcRequestChan() chan *RpcRequest + GetRpcResponeChan() chan *Call + CallMethod(ServiceMethod string,param interface{},reply interface{}) error +} + +func (slf *RpcHandler) GetRpcHandler() IRpcHandler{ + return slf.rpcHandler +} + +func (slf *RpcHandler) InitRpcHandler(rpcHandler IRpcHandler,getClientFun FuncRpcClient,getServerFun FuncRpcServer) { + slf.callRequest = make(chan *RpcRequest,100000) + slf.callResponeCallBack = make(chan *Call,100000) + + slf.rpcHandler = rpcHandler + slf.mapfunctons = map[string]RpcMethodInfo{} + slf.funcRpcClient = getClientFun + slf.funcRpcServer = getServerFun + + slf.RegisterRpc(rpcHandler) +} + +// Is this an exported - upper case - name? +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// Is this type exported or a builtin? +func (slf *RpcHandler) isExportedOrBuiltinType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +} + + +func (slf *RpcHandler) suitableMethods(method reflect.Method) error { + //只有RPC_开头的才能被调用 + if strings.Index(method.Name,"RPC_")!=0 { + return nil + } + + //取出输入参数类型 + var rpcMethodInfo RpcMethodInfo + typ := method.Type + if typ.NumOut() != 1 { + return fmt.Errorf("%s The number of returned arguments must be 1!",method.Name) + } + + if typ.Out(0).String() != "error" { + return fmt.Errorf("%s The return parameter must be of type error!",method.Name) + } + + if typ.NumIn() != 3 { + return fmt.Errorf("%s The number of input arguments must be 1!",method.Name) + } + + if slf.isExportedOrBuiltinType(typ.In(1)) == false || slf.isExportedOrBuiltinType(typ.In(2)) == false { + return fmt.Errorf("%s Unsupported parameter types!",method.Name) + } + + rpcMethodInfo.iparam = reflect.New(typ.In(1).Elem()).Interface() //append(rpcMethodInfo.iparam,) + rpcMethodInfo.oParam = reflect.New(typ.In(2).Elem()) + + rpcMethodInfo.method = method + slf.mapfunctons[slf.rpcHandler.GetName()+"."+method.Name] = rpcMethodInfo + return nil +} + +func (slf *RpcHandler) RegisterRpc(rpcHandler IRpcHandler) error { + typ := reflect.TypeOf(rpcHandler) + for m:=0;m 1 { + log.Error("Cannot call more then 1 node!") + return fmt.Errorf("Cannot call more then 1 node!") + } + + //2.rpcclient调用 + //如果调用本结点服务 + pClient := pClientList[0] + if pClient.blocalhost == true { + pLocalRpcServer:=slf.funcRpcServer() + //判断是否是同一服务 + sMethod := strings.Split(serviceMethod,".") + if len(sMethod)!=2 { + err := fmt.Errorf("Call serviceMethod %s is error!",serviceMethod) + log.Error("%+v",err) + return err + } + //调用自己rpcHandler处理器 + if sMethod[0] == slf.rpcHandler.GetName() { //自己服务调用 + // + return pLocalRpcServer.myselfRpcHandlerGo(sMethod[0],sMethod[1],args,nil) + } + //其他的rpcHandler的处理器 + pCall := pLocalRpcServer.rpcHandlerGo(true,mutiCoroutine,sMethod[0],sMethod[1],args,nil) + return pCall.Err + } + + //跨node调用 + pCall := pClient.Go(true,mutiCoroutine,serviceMethod,args,nil) + return pCall.Err +} + +func (slf *RpcHandler) callRpc(serviceMethod string,mutiCoroutine bool,args interface{},reply interface{}) error { + pClientList,err := slf.funcRpcClient(serviceMethod) + if err != nil { + log.Error("Call serviceMethod is error:%+v!",err) + return err + } + if len(pClientList) > 1 { + log.Error("Cannot call more then 1 node!") + return fmt.Errorf("Cannot call more then 1 node!") + } + + //2.rpcclient调用 + //如果调用本结点服务 + pClient := pClientList[0] + if pClient.blocalhost == true { + pLocalRpcServer:=slf.funcRpcServer() + //判断是否是同一服务 + sMethod := strings.Split(serviceMethod,".") + if len(sMethod)!=2 { + err := fmt.Errorf("Call serviceMethod %s is error!",serviceMethod) + log.Error("%+v",err) + return err + } + //调用自己rpcHandler处理器 + if sMethod[0] == slf.rpcHandler.GetName() { //自己服务调用 + // + return pLocalRpcServer.myselfRpcHandlerGo(sMethod[0],sMethod[1],args,reply) + } + //其他的rpcHandler的处理器 + pCall := pLocalRpcServer.rpcHandlerGo(false,mutiCoroutine,sMethod[0],sMethod[1],args,reply) + pResult := pCall.Done() + return pResult.Err + } + + //跨node调用 + pCall := pClient.Go(false,mutiCoroutine,serviceMethod,args,reply) + pResult := pCall.Done() + return pResult.Err +} + +func (slf *RpcHandler) asyncCallRpc(serviceMethod string,mutiCoroutine bool,args interface{},callback interface{}) error { + fVal := reflect.ValueOf(callback) + if fVal.Kind()!=reflect.Func{ + return fmt.Errorf("input function is error!") + } + + reply := reflect.New(fVal.Type().In(0).Elem()).Interface() + pClientList,err := slf.funcRpcClient(serviceMethod) + if err != nil { + log.Error("Call serviceMethod is error:%+v!",err) + return err + } + if len(pClientList) > 1 { + log.Error("Cannot call more then 1 node!") + return fmt.Errorf("Cannot call more then 1 node!") + } + + //2.rpcclient调用 + //如果调用本结点服务 + pClient := pClientList[0] + if pClient.blocalhost == true { + pLocalRpcServer:=slf.funcRpcServer() + //判断是否是同一服务 + sMethod := strings.Split(serviceMethod,".") + if len(sMethod)!=2 { + err := fmt.Errorf("Call serviceMethod %s is error!",serviceMethod) + log.Error("%+v",err) + return err + } + //调用自己rpcHandler处理器 + if sMethod[0] == slf.rpcHandler.GetName() { //自己服务调用 + err := pLocalRpcServer.myselfRpcHandlerGo(sMethod[0],sMethod[1],args,reply) + if err == nil { + fVal.Call([]reflect.Value{reflect.ValueOf(reply),NilError}) + }else{ + fVal.Call([]reflect.Value{reflect.ValueOf(reply),reflect.ValueOf(err)}) + } + + } + + //其他的rpcHandler的处理器 + if callback!=nil { + return pLocalRpcServer.rpcHandlerAsyncGo(slf,false,mutiCoroutine,sMethod[0],sMethod[1],args,reply,fVal) + } + pCall := pLocalRpcServer.rpcHandlerGo(false,mutiCoroutine,sMethod[0],sMethod[1],args,reply) + pResult := pCall.Done() + return pResult.Err + } + + //跨node调用 + return pClient.AsycGo(slf,mutiCoroutine,serviceMethod,fVal,args,reply) +} + +func (slf *RpcHandler) GetName() string{ + return slf.rpcHandler.GetName() +} + + +//func (slf *RpcHandler) asyncCallRpc(serviceMethod string,mutiCoroutine bool,callback interface{},args ...interface{}) error { +//func (slf *RpcHandler) callRpc(serviceMethod string,reply interface{},mutiCoroutine bool,args ...interface{}) error { +//func (slf *RpcHandler) goRpc(serviceMethod string,mutiCoroutine bool,args ...interface{}) error { +//(reply *int,err error) {} +func (slf *RpcHandler) AsyncCall(serviceMethod string,args interface{},callback interface{}) error { + return slf.asyncCallRpc(serviceMethod,false,args,callback) +} + +func (slf *RpcHandler) GRAsyncCall(serviceMethod string,args interface{},callback interface{}) error { + return slf.asyncCallRpc(serviceMethod,true,args,callback) +} + +func (slf *RpcHandler) Call(serviceMethod string,args interface{},reply interface{}) error { + return slf.callRpc(serviceMethod,false,args,reply) +} + +func (slf *RpcHandler) GRCall(serviceMethod string,args interface{},reply interface{}) error { + return slf.callRpc(serviceMethod,true,args,reply) +} + +func (slf *RpcHandler) Go(serviceMethod string,args interface{}) error { + return slf.goRpc(serviceMethod,false,args) +} + +func (slf *RpcHandler) GRGo(serviceMethod string,args interface{}) error { + return slf.goRpc(serviceMethod,true,args) +} \ No newline at end of file diff --git a/rpc/server.go b/rpc/server.go index 4f8e4f3..afe34e4 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -1,849 +1,260 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* - Package rpc provides access to the exported methods of an object across a - network or other I/O connection. A server registers an object, making it visible - as a service with the name of the type of the object. After registration, exported - methods of the object will be accessible remotely. A server may register multiple - objects (services) of different types but it is an error to register multiple - objects of the same type. - - Only methods that satisfy these criteria will be made available for remote access; - other methods will be ignored: - - - the method's type is exported. - - the method is exported. - - the method has two arguments, both exported (or builtin) types. - - the method's second argument is a pointer. - - the method has return type error. - - In effect, the method must look schematically like - - func (t *T) MethodName(argType T1, replyType *T2) error - - where T1 and T2 can be marshaled by encoding/gob. - These requirements apply even if a different codec is used. - (In the future, these requirements may soften for custom codecs.) - - The method's first argument represents the arguments provided by the caller; the - second argument represents the result parameters to be returned to the caller. - The method's return value, if non-nil, is passed back as a string that the client - sees as if created by errors.New. If an error is returned, the reply parameter - will not be sent back to the client. - - The server may handle requests on a single connection by calling ServeConn. More - typically it will create a network listener and call Accept or, for an HTTP - listener, HandleHTTP and http.Serve. - - A client wishing to use the service establishes a connection and then invokes - NewClient on the connection. The convenience function Dial (DialHTTP) performs - both steps for a raw network connection (an HTTP connection). The resulting - Client object has two methods, Call and Go, that specify the service and method to - call, a pointer containing the arguments, and a pointer to receive the result - parameters. - - The Call method waits for the remote call to complete while the Go method - launches the call asynchronously and signals completion using the Call - structure's Done channel. - - Unless an explicit codec is set up, package encoding/gob is used to - transport the data. - - Here is a simple example. A server wishes to export an object of type Arith: - - package server - - import "errors" - - type Args struct { - A, B int - } - - type Quotient struct { - Quo, Rem int - } - - type Arith int - - func (t *Arith) Multiply(args *Args, reply *int) error { - *reply = args.A * args.B - return nil - } - - func (t *Arith) Divide(args *Args, quo *Quotient) error { - if args.B == 0 { - return errors.New("divide by zero") - } - quo.Quo = args.A / args.B - quo.Rem = args.A % args.B - return nil - } - - The server calls (for HTTP service): - - arith := new(Arith) - rpc.Register(arith) - rpc.HandleHTTP() - l, e := net.Listen("tcp", ":1234") - if e != nil { - log.Fatal("listen error:", e) - } - go http.Serve(l, nil) - - At this point, clients can see a service "Arith" with methods "Arith.Multiply" and - "Arith.Divide". To invoke one, a client first dials the server: - - client, err := rpc.DialHTTP("tcp", serverAddress + ":1234") - if err != nil { - log.Fatal("dialing:", err) - } - - Then it can make a remote call: - - // Synchronous call - args := &server.Args{7,8} - var reply int - err = client.Call("Arith.Multiply", args, &reply) - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d*%d=%d", args.A, args.B, reply) - - or - - // Asynchronous call - quotient := new(Quotient) - divCall := client.Go("Arith.Divide", args, quotient, nil) - replyCall := <-divCall.Done // will be equal to divCall - // check errors, print, etc. - - A server implementation will often provide a simple, type-safe wrapper for the - client. - - The net/rpc package is frozen and is not accepting new features. -*/ package rpc import ( - "bufio" - "encoding/gob" - "errors" "fmt" - "io" - "log" + "github.com/duanhf2012/originnet/log" + "github.com/duanhf2012/originnet/network" + "math" "net" - "net/http" "reflect" "strings" - "sync" - "time" - "unicode" - "unicode/utf8" - - runtimedebug "runtime/debug" - - orginservice "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/util" - "github.com/duanhf2012/origin/util/uuid" ) -const ( - // Defaults used by HandleHTTP - DefaultRPCPath = "/_goRPC_" - DefaultDebugPath = "/debug/rpc" -) +var processor iprocessor = &JsonProcessor{} +var LittleEndian bool -// Precompute the reflect type for error. Can't use error directly -// because Typeof takes an empty interface value. This is annoying. -var typeOfError = reflect.TypeOf((*error)(nil)).Elem() - -type methodType struct { - sync.Mutex // protects counters - method reflect.Method - ArgType reflect.Type - ReplyType reflect.Type - numCalls uint +type Call struct { + Seq uint64 + //ServiceMethod string + Arg interface{} + Reply interface{} + Respone *RpcResponse + Err error + done chan *Call // Strobes when call is complete. + connid int + callback *reflect.Value + rpcHandler IRpcHandler } -type service struct { - name string // name of service - rcvr reflect.Value // receiver of methods for the service - typ reflect.Type // type of the receiver - method map[string]*methodType // registered methods +func (slf *Call) Done() *Call{ + return <-slf.done } -// Request is a header written before every RPC call. It is used internally -// but documented here as an aid to debugging, such as when analyzing -// network traffic. -type Request struct { - ServiceMethod string // format: "Service.Method" - Seq uint64 // sequence number chosen by client - next *Request // for free list in Server - QueueMode bool +type iprocessor interface { + Marshal(v interface{}) ([]byte, error) + Unmarshal(data []byte, v interface{}) error } -// Response is a header written before every RPC return. It is used internally -// but documented here as an aid to debugging, such as when analyzing -// network traffic. -type Response struct { - ServiceMethod string // echoes that of the Request - Seq uint64 // echoes that of the request - Error string // error, if any. - next *Response // for free list in Server -} - -const ( - MAX_RPCDATA_QUEUE_COUNT = 10240 -) - -// Server represents an RPC Server. type Server struct { - serviceMap sync.Map // map[string]*service - reqLock sync.Mutex // protects freeReq - freeReq *Request - respLock sync.Mutex // protects freeResp - freeResp *Response + functions map[interface{}]interface{} + listenAddr string //ip:port - mapCallQueue map[string]chan *CQueueRpcData + cmdchannel chan *Call + + rpcHandleFinder RpcHandleFinder + rpcserver *network.TCPServer } -type CQueueRpcData struct { - server *Server - sending *sync.Mutex - wg *sync.WaitGroup - mtype *methodType - req *Request - argv reflect.Value - replyv reflect.Value - codec ServerCodec - service *service +type RpcHandleFinder interface { + FindRpcHandler(serviceMethod string) IRpcHandler } -// NewServer returns a new Server. -func NewServer() *Server { - server := &Server{} - server.mapCallQueue = make(map[string]chan *CQueueRpcData) - - return server +func (slf *Server) Init(rpcHandleFinder RpcHandleFinder) { + slf.cmdchannel = make(chan *Call,10000) + slf.rpcHandleFinder = rpcHandleFinder + slf.rpcserver = &network.TCPServer{} } -// DefaultServer is the default instance of *Server. -var DefaultServer = NewServer() - -// Is this an exported - upper case - name? -func isExported(name string) bool { - rune, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(rune) +func (slf *Server) Start(listenAddr string) { + slf.listenAddr = listenAddr + slf.rpcserver.Addr = listenAddr + slf.rpcserver.LenMsgLen = 2 //uint16 + slf.rpcserver.MinMsgLen = 2 + slf.rpcserver.MaxMsgLen = math.MaxUint16 + slf.rpcserver.MaxConnNum = 10000 + slf.rpcserver.PendingWriteNum = 10000 + slf.rpcserver.NewAgent =slf.NewAgent + slf.rpcserver.LittleEndian = LittleEndian + slf.rpcserver.Start() } -// Is this type exported or a builtin? -func isExportedOrBuiltinType(t reflect.Type) bool { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - // PkgPath will be non-empty even for an exported type, - // so we need to check the type name as well. - return isExported(t.Name()) || t.PkgPath() == "" + +func (gate *RpcAgent) OnDestroy() {} + +type RpcAgent struct { + conn network.Conn + rpcserver *Server + userData interface{} } -// Register publishes in the server the set of methods of the -// receiver value that satisfy the following conditions: -// - exported method of exported type -// - two arguments, both of exported type -// - the second argument is a pointer -// - one return value, of type error -// It returns an error if the receiver is not an exported type or has -// no suitable methods. It also logs the error using package log. -// The client accesses each method using a string of the form "Type.Method", -// where Type is the receiver's concrete type. -func (server *Server) Register(rcvr interface{}) error { - return server.register(rcvr, "", "", false) +type RpcRequestRw struct { + // + ServiceMethod string // format: "Service.Method" + //Seq uint64 // sequence number chosen by client + InputParam []byte + + requestHandle RequestHandler } -// RegisterName is like Register but uses the provided name for the type -// instead of the receiver's concrete type. -func (server *Server) RegisterName(name string, prefix string, rcvr interface{}) error { - return server.register(rcvr, name, prefix, true) -} - -func (server *Server) ProcessQueue(name string) { - chanRpc, ok := server.mapCallQueue[name] - if ok == false { - orginservice.GetLogger().Printf(orginservice.LEVER_FATAL, "cannot find queue") - return - } - - //定时报告队列超负荷运行 - var checktm util.Timer - checktm.SetupTimerEx(time.Minute*1) - - maxSize := 0 - uuidkey := uuid.Rand().HexEx() +func (agent *RpcAgent) Run() { for { - if checktm.CheckTimeOut() { - orginservice.GetLogger().Printf(orginservice.LEVER_WARN, "RpcServer.ProcessQueue(%s) max %d",name,maxSize) - maxSize = 0 - }else { - curSize := len(chanRpc) - if curSize > maxSize { - maxSize = curSize - } - } - - rpcData := <-chanRpc - - - orginservice.MonitorEnter(uuidkey,name) - rpcData.service.call(rpcData.server, rpcData.sending, rpcData.wg, rpcData.mtype, rpcData.req, rpcData.argv, rpcData.replyv, rpcData.codec) - orginservice.MonitorLeave(uuidkey) - } -} - -func (server *Server) register(rcvr interface{}, name string, prefix string, useName bool) error { - s := new(service) - s.typ = reflect.TypeOf(rcvr) - s.rcvr = reflect.ValueOf(rcvr) - sname := reflect.Indirect(s.rcvr).Type().Name() - - _, ok := server.mapCallQueue[sname] - if ok == false { - server.mapCallQueue[sname] = make(chan *CQueueRpcData, 10240) - util.GoRecover(server.ProcessQueue,-1, sname) - } - if useName { - sname = name - } - if sname == "" { - s := "rpc.Register: no service name for type " + s.typ.String() - log.Print(s) - return errors.New(s) - } - - if !isExported(sname) && !useName { - s := "rpc.Register: type " + sname + " is not exported" - log.Print(s) - return errors.New(s) - } - s.name = sname - - // Install the methods - s.method = suitableMethods(prefix, s.typ, true) - - if len(s.method) == 0 { - str := "" - - // To help the user, see if a pointer receiver would work. - method := suitableMethods(prefix, reflect.PtrTo(s.typ), false) - if len(method) != 0 { - str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)" - } else { - str = "rpc.Register: type " + sname + " has no exported methods of suitable type" - } - //log.Print(str) - - return errors.New(str) - } - - //将已经注册的消息加载 - svci, ok := server.serviceMap.Load(sname) - if ok { - svc := svci.(*service) - for key, value := range svc.method { - s.method[key] = &methodType{method: value.method, ArgType: value.ArgType, ReplyType: value.ReplyType} - } - } - - server.serviceMap.Store(sname, s) - return nil -} - -// suitableMethods returns suitable Rpc methods of typ, it will report -// error using log if reportErr is true. -func suitableMethods(prefix string, typ reflect.Type, reportErr bool) map[string]*methodType { - methods := make(map[string]*methodType) - for m := 0; m < typ.NumMethod(); m++ { - method := typ.Method(m) - mtype := method.Type - mname := method.Name - - if prefix != "" { - if strings.Contains(mname, prefix) == false { - continue - } - } - - // Method must be exported. - if method.PkgPath != "" { - continue - } - // Method needs three ins: receiver, *args, *reply. - if mtype.NumIn() != 3 { - if reportErr { - log.Printf("rpc.Register: method %q has %d input parameters; needs exactly three\n", mname, mtype.NumIn()) - } - continue - } - // First arg need not be a pointer. - argType := mtype.In(1) - if !isExportedOrBuiltinType(argType) { - if reportErr { - log.Printf("rpc.Register: argument type of method %q is not exported: %q\n", mname, argType) - } - continue - } - // Second arg must be a pointer. - replyType := mtype.In(2) - if replyType.Kind() != reflect.Ptr { - if reportErr { - log.Printf("rpc.Register: reply type of method %q is not a pointer: %q\n", mname, replyType) - } - continue - } - // Reply type must be exported. - if !isExportedOrBuiltinType(replyType) { - if reportErr { - log.Printf("rpc.Register: reply type of method %q is not exported: %q\n", mname, replyType) - } - continue - } - // Method needs one out. - if mtype.NumOut() != 1 { - if reportErr { - log.Printf("rpc.Register: method %q has %d output parameters; needs exactly one\n", mname, mtype.NumOut()) - } - continue - } - // The return type of the method must be error. - if returnType := mtype.Out(0); returnType != typeOfError { - if reportErr { - log.Printf("rpc.Register: return type of method %q is %q, must be error\n", mname, returnType) - } - continue - } - methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType} - } - return methods -} - -// A value sent as a placeholder for the server's response value when the server -// receives an invalid request. It is never decoded by the client since the Response -// contains an error when it is used. -var invalidRequest = struct{}{} - -func (server *Server) sendResponse(sending *sync.Mutex, req *Request, reply interface{}, codec ServerCodec, errmsg string) { - resp := server.getResponse() - // Encode the response header - resp.ServiceMethod = req.ServiceMethod - if errmsg != "" { - resp.Error = errmsg - reply = invalidRequest - } - resp.Seq = req.Seq - sending.Lock() - err := codec.WriteResponse(resp, reply) - if debugLog && err != nil { - log.Println("rpc: writing response:", err) - } - sending.Unlock() - server.freeResponse(resp) -} - -func (m *methodType) NumCalls() (n uint) { - m.Lock() - n = m.numCalls - m.Unlock() - return n -} - -func (s *service) call(server *Server, sending *sync.Mutex, wg *sync.WaitGroup, mtype *methodType, req *Request, argv, replyv reflect.Value, codec ServerCodec) { - defer func() { - if r := recover(); r != nil { - var coreInfo string - - coreInfo = string(runtimedebug.Stack()) - coreInfo += "\nCore Request RPC Name:" + req.ServiceMethod - coreInfo += "\n" + fmt.Sprintf("Core information is %v\n", r) - orginservice.GetLogger().Printf(orginservice.LEVER_FATAL, coreInfo) - } - }() - - if wg != nil { - defer wg.Done() - } - - mtype.Lock() - mtype.numCalls++ - mtype.Unlock() - function := mtype.method.Func - // Invoke the method, providing a new value for the reply. - returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv}) - // The return value for the method is an error. - errInter := returnValues[0].Interface() - errmsg := "" - if errInter != nil { - errmsg = errInter.(error).Error() - } - server.sendResponse(sending, req, replyv.Interface(), codec, errmsg) - server.freeRequest(req) -} - -type gobServerCodec struct { - rwc io.ReadWriteCloser - dec *gob.Decoder - enc *gob.Encoder - encBuf *bufio.Writer - closed bool -} - -func (c *gobServerCodec) ReadRequestHeader(r *Request) error { - return c.dec.Decode(r) -} - -func (c *gobServerCodec) ReadRequestBody(body interface{}) error { - return c.dec.Decode(body) -} - -func (c *gobServerCodec) WriteResponse(r *Response, body interface{}) (err error) { - if err = c.enc.Encode(r); err != nil { - if c.encBuf.Flush() == nil { - // Gob couldn't encode the header. Should not happen, so if it does, - // shut down the connection to signal that the connection is broken. - log.Println("rpc: gob error encoding response:", err) - c.Close() - } - return - } - if err = c.enc.Encode(body); err != nil { - if c.encBuf.Flush() == nil { - // Was a gob problem encoding the body but the header has been written. - // Shut down the connection to signal that the connection is broken. - log.Println("rpc: gob error encoding body:", err) - c.Close() - } - return - } - return c.encBuf.Flush() -} - -func (c *gobServerCodec) Close() error { - if c.closed { - // Only call c.rwc.Close once; otherwise the semantics are undefined. - return nil - } - c.closed = true - return c.rwc.Close() -} - -// ServeConn runs the server on a single connection. -// ServeConn blocks, serving the connection until the client hangs up. -// The caller typically invokes ServeConn in a go statement. -// ServeConn uses the gob wire format (see package gob) on the -// connection. To use an alternate codec, use ServeCodec. -// See NewClient's comment for information about concurrent access. -func (server *Server) ServeConn(conn io.ReadWriteCloser) { - buf := bufio.NewWriter(conn) - srv := &gobServerCodec{ - rwc: conn, - dec: gob.NewDecoder(conn), - enc: gob.NewEncoder(buf), - encBuf: buf, - } - server.ServeCodec(srv) -} - -// ServeCodec is like ServeConn but uses the specified codec to -// decode requests and encode responses. -func (server *Server) ServeCodec(codec ServerCodec) { - sending := new(sync.Mutex) - wg := new(sync.WaitGroup) - for { - service, mtype, req, argv, replyv, keepReading, queueMode, err := server.readRequest(codec) + data,err := agent.conn.ReadMsg() if err != nil { - if debugLog && err != io.EOF { - orginservice.GetLogger().Printf(orginservice.LEVER_FATAL, "rpc: %v", err) - log.Println("rpc:", err) - } - if !keepReading { - break - } - // send a response if we actually managed to read a header. - if req != nil { - server.sendResponse(sending, req, invalidRequest, codec, err.Error()) - server.freeRequest(req) - } + log.Debug("read message: %v", err) + break + } + + if processor==nil{ + log.Error("Rpc Processor not set!") continue } - rpcData := &CQueueRpcData{server, sending, wg, mtype, req, argv, replyv, codec, service} + var req RpcRequest + //解析head + err = processor.Unmarshal(data,&req) + if err != nil { + log.Debug("processor message: %v", err) + agent.Close() + break + } - if queueMode == true { - rpcChan, ok := server.mapCallQueue[service.name] - if ok == true { - if len(rpcChan) >= MAX_RPCDATA_QUEUE_COUNT { - //不在这里写日志了 否则RPC繁忙 这里会刷日志把磁盘刷爆 ProcessQueue会记录channel繁忙的日志 - //orginservice.GetLogger().Printf(orginservice.LEVER_FATAL, "Rpc Service Name %s chan overload %d", service.name, MAX_RPCDATA_QUEUE_COUNT) - - continue + //交给程序处理 + serviceMethod := strings.Split(req.ServiceMethod,".") + if len(serviceMethod)!=2 { + log.Debug("rpc request req.ServiceMethod is error") + continue + } + rpcHandler := agent.rpcserver.rpcHandleFinder.FindRpcHandler(serviceMethod[0]) + if rpcHandler== nil { + log.Error("service method %s not config!", req.ServiceMethod) + continue + } + if req.NoReply == false { + req.requestHandle = func(Returns interface{},Err error){ + var rpcRespone RpcResponse + rpcRespone.Seq = req.Seq + rpcRespone.Err = Err + if Err==nil { + rpcRespone.Returns,rpcRespone.Err = processor.Marshal(Returns) + } + + bytes,err := processor.Marshal(rpcRespone) + if err != nil { + log.Error("service method %s Marshal error:%+v!", req.ServiceMethod,err) + return + } + + err = agent.conn.WriteMsg(bytes) + if err != nil { + log.Error("Rpc %s return is error:%+v",req.ServiceMethod,err) } - wg.Add(1) - rpcChan <- rpcData - continue - } else { - orginservice.GetLogger().Printf(orginservice.LEVER_FATAL, "Rpc Service Name %s call not find coroutines", service.name) } } - wg.Add(1) - //queueMode - //fmt.Print(queueMode) - util.Go(service.call,server, sending, wg, mtype, req, argv, replyv, codec) - //go service.call(server, sending, wg, mtype, req, argv, replyv, codec) + if req.MutiCoroutine == true { + go rpcHandler.HandlerRpcRequest(&req) + }else{ + rpcHandler.PushRequest(&req) + } + } - // We've seen that there are no more requests. - // Wait for responses to be sent before closing codec. - wg.Wait() - codec.Close() } -// ServeRequest is like ServeCodec but synchronously serves a single request. -// It does not close the codec upon completion. -func (server *Server) ServeRequest(codec ServerCodec) error { - sending := new(sync.Mutex) - service, mtype, req, argv, replyv, keepReading, _, err := server.readRequest(codec) - if err != nil { - if !keepReading { - return err - } - // send a response if we actually managed to read a header. - if req != nil { - server.sendResponse(sending, req, invalidRequest, codec, err.Error()) - server.freeRequest(req) - } +func (agent *RpcAgent) OnClose() { +} + +func (agent *RpcAgent) WriteMsg(msg interface{}) { +} + +func (agent *RpcAgent) LocalAddr() net.Addr { + return agent.conn.LocalAddr() +} + +func (agent *RpcAgent) RemoteAddr() net.Addr { + return agent.conn.RemoteAddr() +} + +func (agent *RpcAgent) Close() { + agent.conn.Close() +} + +func (agent *RpcAgent) Destroy() { + agent.conn.Destroy() +} + + +func (slf *Server) NewAgent(conn *network.TCPConn) network.Agent { + agent := &RpcAgent{conn: conn, rpcserver: slf} + + return agent +} + +func (slf *Server) myselfRpcHandlerGo(handlerName string,methodName string, args interface{},reply interface{}) error { + rpcHandler := slf.rpcHandleFinder.FindRpcHandler(handlerName) + if rpcHandler== nil { + err := fmt.Errorf("service method %s.%s not config!", handlerName,methodName) + log.Error("%s",err.Error()) return err } - service.call(server, sending, nil, mtype, req, argv, replyv, codec) + + return rpcHandler.CallMethod(fmt.Sprintf("%s.%s",handlerName,methodName),args,reply) +} + + +func (slf *Server) rpcHandlerGo(noReply bool,mutiCoroutine bool,handlerName string,methodName string, args interface{},reply interface{}) *Call { + pCall := &Call{} + pCall.done = make( chan *Call,1) + rpcHandler := slf.rpcHandleFinder.FindRpcHandler(handlerName) + if rpcHandler== nil { + pCall.Err = fmt.Errorf("service method %s.%s not config!", handlerName,methodName) + log.Error("%s",pCall.Err.Error()) + pCall.done <- pCall + return pCall + } + var req RpcRequest + req.ServiceMethod = fmt.Sprintf("%s.%s",handlerName,methodName) + req.localParam = args + req.localReply = reply + req.NoReply = noReply + + if noReply == false { + req.requestHandle = func(Returns interface{},Err error){ + pCall.Err = Err + pCall.done <- pCall + } + } + + if mutiCoroutine == true { + go rpcHandler.HandlerRpcRequest(&req) + }else{ + rpcHandler.PushRequest(&req) + } + + return pCall +} + +func (slf *Server) rpcHandlerAsyncGo(callerRpcHandler IRpcHandler,noReply bool,mutiCoroutine bool,handlerName string,methodName string,args interface{},reply interface{},callback reflect.Value) error { + pCall := &Call{} + //pCall.done = make( chan *Call,1) + pCall.rpcHandler = callerRpcHandler + pCall.callback = &callback + rpcHandler := slf.rpcHandleFinder.FindRpcHandler(handlerName) + if rpcHandler== nil { + err := fmt.Errorf("service method %s.%s not config!", handlerName,methodName) + log.Error("%+v",err) + return err + } + + var req RpcRequest + req.ServiceMethod = fmt.Sprintf("%s.%s",handlerName,methodName) + req.localParam = args + req.localReply = reply + req.NoReply = noReply + req.MutiCoroutine = mutiCoroutine + + if noReply == false { + req.requestHandle = func(Returns interface{},Err error){ + pCall.Err = Err + pCall.Reply = Returns + pCall.rpcHandler.(*RpcHandler).callResponeCallBack<-pCall + } + } + + if mutiCoroutine == true { + go rpcHandler.HandlerRpcRequest(&req) + }else{ + rpcHandler.PushRequest(&req) + } + return nil } - -func (server *Server) getRequest() *Request { - server.reqLock.Lock() - req := server.freeReq - if req == nil { - req = new(Request) - } else { - server.freeReq = req.next - *req = Request{} - } - server.reqLock.Unlock() - return req -} - -func (server *Server) freeRequest(req *Request) { - server.reqLock.Lock() - req.next = server.freeReq - server.freeReq = req - server.reqLock.Unlock() -} - -func (server *Server) getResponse() *Response { - server.respLock.Lock() - resp := server.freeResp - if resp == nil { - resp = new(Response) - } else { - server.freeResp = resp.next - *resp = Response{} - } - server.respLock.Unlock() - return resp -} - -func (server *Server) freeResponse(resp *Response) { - server.respLock.Lock() - resp.next = server.freeResp - server.freeResp = resp - server.respLock.Unlock() -} - -func (server *Server) readRequest(codec ServerCodec) (service *service, mtype *methodType, req *Request, argv, replyv reflect.Value, keepReading bool, queueMode bool, err error) { - service, mtype, req, keepReading, queueMode, err = server.readRequestHeader(codec) - if err != nil { - if !keepReading { - return - } // discard body - - codec.ReadRequestBody(nil) - return - } - - // Decode the argument value. - argIsValue := false // if true, need to indirect before calling. - if mtype.ArgType.Kind() == reflect.Ptr { - argv = reflect.New(mtype.ArgType.Elem()) - } else { - argv = reflect.New(mtype.ArgType) - argIsValue = true - } - // argv guaranteed to be a pointer now. - if err = codec.ReadRequestBody(argv.Interface()); err != nil { - return - } - if argIsValue { - argv = argv.Elem() - } - - replyv = reflect.New(mtype.ReplyType.Elem()) - - switch mtype.ReplyType.Elem().Kind() { - case reflect.Map: - replyv.Elem().Set(reflect.MakeMap(mtype.ReplyType.Elem())) - case reflect.Slice: - replyv.Elem().Set(reflect.MakeSlice(mtype.ReplyType.Elem(), 0, 0)) - } - return -} - -func (server *Server) readRequestHeader(codec ServerCodec) (svc *service, mtype *methodType, req *Request, keepReading bool, queueMode bool, err error) { - // Grab the request header. - req = server.getRequest() - err = codec.ReadRequestHeader(req) - if err != nil { - req = nil - if err == io.EOF || err == io.ErrUnexpectedEOF { - return - } - err = errors.New("rpc: server cannot decode request: " + err.Error()) - return - } - - // We read the header successfully. If we see an error now, - // we can still recover and move on to the next request. - keepReading = true - - dot := strings.LastIndex(req.ServiceMethod, ".") - if dot < 0 { - err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod) - return - } - serviceName := req.ServiceMethod[:dot] - methodName := req.ServiceMethod[dot+1:] - - // Look up the request. - svci, ok := server.serviceMap.Load(serviceName) - if !ok { - err = errors.New("rpc: can't find service " + req.ServiceMethod) - return - } - svc = svci.(*service) - mtype = svc.method[methodName] - if mtype == nil { - err = errors.New("rpc: can't find method " + req.ServiceMethod) - } - queueMode = req.QueueMode - return -} - -// Accept accepts connections on the listener and serves requests -// for each incoming connection. Accept blocks until the listener -// returns a non-nil error. The caller typically invokes Accept in a -// go statement. -func (server *Server) Accept(lis net.Listener) { - for { - conn, err := lis.Accept() - if err != nil { - log.Print("rpc.Serve: accept:", err.Error()) - return - } - go server.ServeConn(conn) - } -} - -// Register publishes the receiver's methods in the DefaultServer. -func Register(rcvr interface{}) error { return DefaultServer.Register(rcvr) } - -// RegisterName is like Register but uses the provided name for the type -// instead of the receiver's concrete type. -func RegisterName(name string, prefix string, rcvr interface{}) error { - return DefaultServer.RegisterName(name, prefix, rcvr) -} - -// A ServerCodec implements reading of RPC requests and writing of -// RPC responses for the server side of an RPC session. -// The server calls ReadRequestHeader and ReadRequestBody in pairs -// to read requests from the connection, and it calls WriteResponse to -// write a response back. The server calls Close when finished with the -// connection. ReadRequestBody may be called with a nil -// argument to force the body of the request to be read and discarded. -// See NewClient's comment for information about concurrent access. -type ServerCodec interface { - ReadRequestHeader(*Request) error - ReadRequestBody(interface{}) error - WriteResponse(*Response, interface{}) error - - // Close can be called multiple times and must be idempotent. - Close() error -} - -// ServeConn runs the DefaultServer on a single connection. -// ServeConn blocks, serving the connection until the client hangs up. -// The caller typically invokes ServeConn in a go statement. -// ServeConn uses the gob wire format (see package gob) on the -// connection. To use an alternate codec, use ServeCodec. -// See NewClient's comment for information about concurrent access. -func ServeConn(conn io.ReadWriteCloser) { - DefaultServer.ServeConn(conn) -} - -// ServeCodec is like ServeConn but uses the specified codec to -// decode requests and encode responses. -func ServeCodec(codec ServerCodec) { - DefaultServer.ServeCodec(codec) -} - -// ServeRequest is like ServeCodec but synchronously serves a single request. -// It does not close the codec upon completion. -func ServeRequest(codec ServerCodec) error { - return DefaultServer.ServeRequest(codec) -} - -// Accept accepts connections on the listener and serves requests -// to DefaultServer for each incoming connection. -// Accept blocks; the caller typically invokes it in a go statement. -func Accept(lis net.Listener) { DefaultServer.Accept(lis) } - -// Can connect to RPC service using HTTP CONNECT to rpcPath. -var connected = "200 Connected to Go RPC" - -// ServeHTTP implements an http.Handler that answers RPC requests. -func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if req.Method != "CONNECT" { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusMethodNotAllowed) - io.WriteString(w, "405 must CONNECT\n") - return - } - conn, _, err := w.(http.Hijacker).Hijack() - if err != nil { - log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error()) - return - } - io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n") - server.ServeConn(conn) -} - -// HandleHTTP registers an HTTP handler for RPC messages on rpcPath, -// and a debugging handler on debugPath. -// It is still necessary to invoke http.Serve(), typically in a go statement. -func (server *Server) HandleHTTP(rpcPath, debugPath string) { - http.Handle(rpcPath, server) - http.Handle(debugPath, debugHTTP{server}) -} - -// HandleHTTP registers an HTTP handler for RPC messages to DefaultServer -// on DefaultRPCPath and a debugging handler on DefaultDebugPath. -// It is still necessary to invoke http.Serve(), typically in a go statement. -func HandleHTTP() { - DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath) -} diff --git a/service/DeadForMonitor.go b/service/DeadForMonitor.go deleted file mode 100644 index 4e0a0eb..0000000 --- a/service/DeadForMonitor.go +++ /dev/null @@ -1,78 +0,0 @@ -package service - -import ( - "fmt" - "github.com/duanhf2012/origin/util" - "runtime/pprof" - "time" -) - -type ModuleMontior struct { - mapModule *util.MapEx -} - -type ModuleInfo struct { - enterStartTm int64 - mNameInfo string -} - -var moduleMontior ModuleMontior - - - -func MonitorEnter(uuid string,strMonitorInfo string){ - if moduleMontior.mapModule == nil { - return - } - moduleMontior.mapModule.Set(uuid, &ModuleInfo{enterStartTm:time.Now().Unix(),mNameInfo:strMonitorInfo}) -} - -func MonitorLeave(uuid string){ - if moduleMontior.mapModule == nil { - return - } - - moduleMontior.mapModule.Del(uuid) -} - - - -func ReportDeadFor(){ - if moduleMontior.mapModule == nil { - return - } - moduleMontior.mapModule.RLockRange(func(key interface{}, value interface{}) { - if value != nil { - pModuleInfo := value.(*ModuleInfo) - //超过5分钟认为dead for - if time.Now().Unix() - pModuleInfo.enterStartTm > 300 { - GetLogger().Printf(LEVER_FATAL, "module is %s, Dead cycle\n", pModuleInfo.mNameInfo) - } - } - }) -} - -func EnableDeadForMonitor(checkInterval time.Duration){ - moduleMontior.mapModule = util.NewMapEx() - var tmInval util.Timer - tmInval.SetupTimer(int32(checkInterval.Milliseconds())) - go func(){ - for { - time.Sleep(time.Second*5) - if tmInval.CheckTimeOut(){ - ReportDeadFor() - ReportPprof() - } - } - }() -} - - -func ReportPprof(){ - strReport := "" - for _, p := range pprof.Profiles() { - strReport += fmt.Sprintf("Name %s,count %d\n",p.Name(),p.Count()) - } - - GetLogger().Printf(LEVER_INFO, "PProf %s\n", strReport) -} \ No newline at end of file diff --git a/service/Logger.go b/service/Logger.go deleted file mode 100644 index 76bec99..0000000 --- a/service/Logger.go +++ /dev/null @@ -1,37 +0,0 @@ -package service - -import ( - "fmt" -) - -const ( - LEVER_UNKNOW = 0 - LEVER_DEBUG = 1 - LEVER_INFO = 2 - LEVER_WARN = 3 - LEVER_ERROR = 4 - LEVER_FATAL = 5 - LEVEL_MAX = 6 -) - -var defaultLogger = &LoggerFmt{} - -type ILogger interface { - Printf(level uint, format string, v ...interface{}) - Print(level uint, v ...interface{}) - SetLogLevel(level uint) -} - -type LoggerFmt struct { -} - -func (slf *LoggerFmt) Printf(level uint, format string, v ...interface{}) { - fmt.Printf(format, v...) - fmt.Println("") -} -func (slf *LoggerFmt) Print(level uint, v ...interface{}) { - fmt.Println(v...) -} -func (slf *LoggerFmt) SetLogLevel(level uint) { - //do nothing -} diff --git a/service/Module.go b/service/Module.go index fc5e458..c2b6466 100644 --- a/service/Module.go +++ b/service/Module.go @@ -2,361 +2,191 @@ package service import ( "fmt" - "github.com/duanhf2012/origin/util" - "github.com/duanhf2012/origin/util/uuid" - "runtime/debug" - "sync" - "sync/atomic" + "github.com/duanhf2012/originnet/event" + "github.com/duanhf2012/originnet/util/timer" + "time" ) -const ( - //ModuleNone ... - INIT_AUTO_INCREMENT = 0 -) +const InitModuleId = 1e18 + type IModule interface { - OnInit() error //Module初始化时调用 - OnRun() bool //Module运行时调用 - OnEndRun() - SetModuleId(moduleId uint32) //手动设置ModuleId - GetModuleId() uint32 //获取ModuleId - GetModuleById(moduleId uint32) IModule //通过ModuleId获取Module - AddModule(module IModule) uint32 //添加Module - ReleaseModule(moduleId uint32) bool //释放Module - - SetSelf(module IModule) //设置保存自己interface - GetSelf() IModule //获取自己interface - SetOwner(module IModule) //设置父Module - GetOwner() IModule //获取父Module - SetOwnerService(iservice IService) //设置拥有者服务 - GetOwnerService() IService //获取拥有者服务 - GetRoot() IModule //获取Root根Module - - RunModule(module IModule) //手动运行Module - InitModule(exit chan bool, pwaitGroup *sync.WaitGroup) error //手动初始化Module - getBaseModule() *BaseModule //获取BaseModule指针 - IsInit() bool - SetUnOnRun() - getUnOnRun() bool + SetModuleId(moduleId int64) bool + GetModuleId() int64 + AddModule(module IModule) (int64,error) + GetModule(moduleId int64) IModule + GetAncestor()IModule + ReleaseModule(moduleId int64) + NewModuleId() int64 + GetParent()IModule + OnInit() error + OnRelease() + getBaseModule() IModule + GetService() IService + GetEventChan() chan *event.Event } -type BaseModule struct { - moduleId uint32 - ownerService IService - mapModule map[uint32]IModule - ownerModule IModule - selfModule IModule - CurrMaxModuleId uint32 - corouterstatus int32 //0表示运行状态 //1释放消亡状态 +//1.管理各模块树层关系 +//2.提供定时器常用工具 +type Module struct { + moduleId int64 + parent IModule //父亲 + self IModule //父亲 + child map[int64]IModule //孩子们 + mapActiveTimer map[*timer.Timer]interface{} + mapActiveCron map[*timer.Cron]interface{} - moduleLocker sync.RWMutex - ExitChan chan bool - WaitGroup *sync.WaitGroup - bInit bool + dispatcher *timer.Dispatcher //timer - recoverCount int8 - bUnOnRun bool + //根结点 + ancestor IModule //始祖 + seedModuleId int64 //模块id种子 + descendants map[int64]IModule//始祖的后裔们 + + //事件管道 + event.EventProcessor + //eventChan chan *SEvent } -func (slf *BaseModule) GetRoot() IModule { - currentOwner := slf.GetSelf() - for { - owner := currentOwner.GetOwner() - if owner == currentOwner { - return owner - } - currentOwner = owner - } -} -func (slf *BaseModule) SetModuleId(moduleId uint32) { - slf.moduleId = moduleId -} - -func (slf *BaseModule) GetModuleId() uint32 { - return slf.moduleId -} - -func (slf *BaseModule) GetModuleById(moduleId uint32) IModule { - slf.moduleLocker.RLock() - ret, ok := slf.mapModule[moduleId] - slf.moduleLocker.RUnlock() - if ok == false { - return nil - } - - return ret -} - -func (slf *BaseModule) GetModuleCount() int { - slf.moduleLocker.RLock() - moduleCount := len(slf.mapModule) - slf.moduleLocker.RUnlock() - return moduleCount -} - -func (slf *BaseModule) genModuleId() uint32 { - slf.CurrMaxModuleId++ - - return slf.CurrMaxModuleId -} - -func (slf *BaseModule) deleteModule(baseModule *BaseModule) bool { - for _, subModule := range baseModule.mapModule { - baseModule.deleteModule(subModule.getBaseModule()) - } - - atomic.AddInt32(&baseModule.corouterstatus, 1) - //fmt.Printf("Delete %T->%T\n", slf.GetSelf(), baseModule.GetSelf()) - baseModule.ownerService = nil - baseModule.mapModule = nil - baseModule.ownerModule = nil - baseModule.selfModule = nil - - delete(slf.mapModule, baseModule.GetModuleId()) - baseModule.moduleLocker.Unlock() - - return true -} - -func (slf *BaseModule) LockTree(rootModule *BaseModule) { - rootModule.moduleLocker.Lock() - //fmt.Printf("Lock %T\n", rootModule.GetSelf()) - - for _, pModule := range rootModule.mapModule { - slf.LockTree(pModule.getBaseModule()) - //pModule.getBaseModule().moduleLocker.Lock() - } -} - -func (slf *BaseModule) UnLockTree(rootModule *BaseModule) { - for _, pModule := range rootModule.mapModule { - pModule.getBaseModule().moduleLocker.Unlock() - } - rootModule.moduleLocker.Unlock() -} - -func (slf *BaseModule) ReleaseModule(moduleId uint32) bool { - slf.moduleLocker.Lock() - - module, ok := slf.mapModule[moduleId] - if ok == false { - slf.moduleLocker.Unlock() - GetLogger().Printf(LEVER_FATAL, "RemoveModule fail %d...", moduleId) +func (slf *Module) SetModuleId(moduleId int64) bool{ + if moduleId > 0 { return false } - //锁住被结点树 - slf.LockTree(module.getBaseModule()) - slf.deleteModule(module.getBaseModule()) - - delete(slf.mapModule, moduleId) - - //fmt.Printf("++Release %T -- %+v\n", slf.GetSelf(), slf.mapModule) - slf.moduleLocker.Unlock() + slf.moduleId = moduleId return true } -func (slf *BaseModule) IsRoot() bool { - return slf.GetOwner() == slf.GetSelf() +func (slf *Module) GetModuleId() int64{ + return slf.moduleId } -func (slf *BaseModule) GetSelf() IModule { - if slf.selfModule == nil { - return slf - } - - return slf.selfModule +func (slf *Module) OnInit() error{ + return nil } -func (slf *BaseModule) SetUnOnRun() { - slf.bUnOnRun = true -} - -func (slf *BaseModule) getUnOnRun() bool { - return slf.bUnOnRun -} - -func (slf *BaseModule) AddModule(module IModule) uint32 { - //消亡状态不允许加入模块 - if atomic.LoadInt32(&slf.corouterstatus) != 0 { - GetLogger().Printf(LEVER_ERROR, "%T Cannot AddModule %T", slf.GetSelf(), module.GetSelf()) - return 0 +func (slf *Module) AddModule(module IModule) (int64,error){ + pAddModule := module.getBaseModule().(*Module) + if pAddModule.GetModuleId()==0 { + pAddModule.moduleId = slf.NewModuleId() } - pModule := slf.GetModuleById(module.GetModuleId()) - if pModule != nil { - GetLogger().Printf(LEVER_ERROR, "%T Cannot AddModule %T,moduleid %d is repeat!", slf.GetSelf(), module.GetSelf(), module.GetModuleId()) - return 0 + if slf.child == nil { + slf.child = map[int64]IModule{} } - - //如果没有设置,自动生成ModuleId - slf.moduleLocker.Lock() - var genid uint32 - if module.GetModuleId() == 0 { - genid = slf.genModuleId() - module.SetModuleId(genid) - } - - module.getBaseModule().selfModule = module - if slf.GetOwner() != nil { - if slf.IsRoot() { - //root owner为自己 - module.SetOwner(slf.GetOwner()) - } else { - module.SetOwner(slf.GetSelf()) - } - } - - //设置模块退出信号捕获 - module.InitModule(slf.ExitChan, slf.WaitGroup) - - //存入父模块中 - if slf.mapModule == nil { - slf.mapModule = make(map[uint32]IModule) - } - _, ok := slf.mapModule[module.GetModuleId()] + _,ok := slf.child[module.GetModuleId()] if ok == true { - slf.moduleLocker.Unlock() - GetLogger().Printf(LEVER_ERROR, "check mapModule %#v id is %d ,%d is fail...", module, module.GetModuleId(), genid) - return 0 + return 0,fmt.Errorf("Exists module id %d",module.GetModuleId()) } - slf.mapModule[module.GetModuleId()] = module + pAddModule.self = module + pAddModule.parent = slf.self + pAddModule.dispatcher = slf.GetAncestor().getBaseModule().(*Module).dispatcher + pAddModule.ancestor = slf.ancestor - slf.moduleLocker.Unlock() - - //运行模块 - GetLogger().Printf(LEVER_INFO, "Start Init module %T.", module) err := module.OnInit() if err != nil { - delete(slf.mapModule, module.GetModuleId()) - GetLogger().Printf(LEVER_ERROR, "End Init module %T id is %d is fail,reason:%v...", module, module.GetModuleId(), err) - return 0 - } - initErr := module.getBaseModule().OnInit() - if initErr != nil { - delete(slf.mapModule, module.GetModuleId()) - GetLogger().Printf(LEVER_ERROR, "OnInit module %T id is %d is fail,reason:%v...", module, module.GetModuleId(), initErr) - return 0 + return 0,err } - GetLogger().Printf(LEVER_INFO, "End Init module %T.", module) - if module.getUnOnRun() == false { - go module.RunModule(module) + slf.child[module.GetModuleId()] = module + slf.ancestor.getBaseModule().(*Module).descendants[module.GetModuleId()] = module + + return module.GetModuleId(),nil +} + +func (slf *Module) ReleaseModule(moduleId int64){ + //pBaseModule := slf.GetModule(moduleId).getBaseModule().(*Module) + pModule := slf.GetModule(moduleId).getBaseModule().(*Module) + + //释放子孙 + for id,_ := range pModule.child { + slf.ReleaseModule(id) + } + pModule.self.OnRelease() + for pTimer,_ := range pModule.mapActiveTimer { + pTimer.Stop() } - return module.GetModuleId() -} - -func (slf *BaseModule) OnInit() error { - slf.bInit = true - return nil -} - -func (slf *BaseModule) OnRun() bool { - return false -} - -func (slf *BaseModule) OnEndRun() { -} - -func (slf *BaseModule) SetOwner(ownerModule IModule) { - slf.ownerModule = ownerModule -} - -func (slf *BaseModule) SetSelf(module IModule) { - slf.selfModule = module -} - -func (slf *BaseModule) GetOwner() IModule { - - if slf.ownerModule == nil { - return slf + for pCron,_ := range pModule.mapActiveCron { + pCron.Stop() } - return slf.ownerModule + + delete(slf.child,moduleId) + delete (slf.ancestor.getBaseModule().(*Module).descendants,moduleId) + + //清理被删除的Module + pModule.self = nil + pModule.parent = nil + pModule.child = nil + pModule.mapActiveTimer = nil + pModule.mapActiveCron = nil + pModule.dispatcher = nil + pModule.ancestor = nil + pModule.descendants = nil } -func (slf *BaseModule) GetOwnerService() IService { - return slf.ownerService +func (slf *Module) NewModuleId() int64{ + slf.ancestor.getBaseModule().(*Module).seedModuleId+=1 + return slf.ancestor.getBaseModule().(*Module).seedModuleId } -func (slf *BaseModule) SetOwnerService(iservice IService) { - slf.ownerService = iservice +func (slf *Module) GetAncestor()IModule{ + return slf.ancestor } -func (slf *BaseModule) InitModule(exit chan bool, pwaitGroup *sync.WaitGroup) error { - slf.CurrMaxModuleId = INIT_AUTO_INCREMENT - slf.WaitGroup = pwaitGroup - slf.ExitChan = exit - return nil +func (slf *Module) GetModule(moduleId int64) IModule{ + iModule,ok := slf.GetAncestor().getBaseModule().(*Module).descendants[moduleId] + if ok == false{ + return nil + } + return iModule } -func (slf *BaseModule) getBaseModule() *BaseModule { +func (slf *Module) getBaseModule() IModule{ return slf } -func (slf *BaseModule) IsInit() bool { - return slf.bInit + +func (slf *Module) GetParent()IModule{ + return slf.parent } -func (slf *BaseModule) RunModule(module IModule) { - GetLogger().Printf(LEVER_INFO, "Start Run module %T ...", module) - - defer func() { - if r := recover(); r != nil { - var coreInfo string - coreInfo = string(debug.Stack()) - - coreInfo += "\n" + fmt.Sprintf("Core module is %T, try count %d. core information is %v\n", module, slf.recoverCount, r) - GetLogger().Printf(LEVER_FATAL, coreInfo) - slf.recoverCount += 1 - - //重试3次 - if slf.recoverCount < 10 { - go slf.RunModule(slf.GetSelf()) - } else { - GetLogger().Printf(LEVER_FATAL, "Routine %T.OnRun has exited!", module) - } - } - }() - - //运行所有子模块 - timer := util.Timer{} - timer.SetupTimer(1000) - slf.WaitGroup.Add(1) - defer slf.WaitGroup.Done() - - uuidkey := uuid.Rand().HexEx() - moduleTypeName := fmt.Sprintf("%T",module) - for { - if atomic.LoadInt32(&slf.corouterstatus) != 0 { - module.OnEndRun() - GetLogger().Printf(LEVER_INFO, "OnEndRun module %T ...", module) - break - } - - //每500ms检查退出 - if timer.CheckTimeOut() { - select { - case <-slf.ExitChan: - module.OnEndRun() - GetLogger().Printf(LEVER_INFO, "OnEndRun module %T...", module) - return - default: - } - } - - MonitorEnter(uuidkey,moduleTypeName) - if module.OnRun() == false { - module.OnEndRun() - MonitorLeave(uuidkey) - GetLogger().Printf(LEVER_INFO, "OnEndRun module %T...", module) - return - } - - MonitorLeave(uuidkey) +func (slf *Module) AfterFunc(d time.Duration, cb func()) *timer.Timer { + if slf.mapActiveTimer == nil { + slf.mapActiveTimer =map[*timer.Timer]interface{}{} } + + tm := slf.dispatcher.AfterFuncEx(d,func(t *timer.Timer){ + cb() + delete(slf.mapActiveTimer,t) + }) + + slf.mapActiveTimer[tm] = nil + return tm } + +func (slf *Module) CronFunc(cronExpr *timer.CronExpr, cb func()) *timer.Cron { + if slf.mapActiveCron == nil { + slf.mapActiveCron =map[*timer.Cron]interface{}{} + } + + cron := slf.dispatcher.CronFuncEx(cronExpr, func(cron *timer.Cron) { + cb() + delete(slf.mapActiveCron,cron) + }) + + slf.mapActiveCron[cron] = nil + return cron +} + +func (slf *Module) OnRelease(){ +} + +func (slf *Module) GetService() IService { + return slf.GetAncestor().(IService) +} + diff --git a/service/Service.go b/service/Service.go index 30a3adb..a9d6614 100644 --- a/service/Service.go +++ b/service/Service.go @@ -1,85 +1,126 @@ package service import ( - "fmt" - + "github.com/duanhf2012/originnet/rpc" + "github.com/duanhf2012/originnet/util/timer" "reflect" - "strings" + "sync" + "sync/atomic" ) -type MethodInfo struct { - Fun reflect.Value - ParamList []reflect.Value - types reflect.Type -} + +var closeSig chan bool +var timerDispatcherLen = 10 type IService interface { - Init(Iservice IService) error + Init(iservice IService,getClientFun rpc.FuncRpcClient,getServerFun rpc.FuncRpcServer,serviceCfg interface{}) + GetName() string + OnInit() error - OnRun() bool - OnFetchService(iservice IService) error - OnSetupService(iservice IService) //其他服务被安装 - OnRemoveService(iservice IService) //其他服务被安装 - - GetServiceName() string - SetServiceName(serviceName string) bool - GetServiceId() int - - GetStatus() int - IsInit() bool + OnRelease() + Wait() + Start() + GetRpcHandler() rpc.IRpcHandler + GetServiceCfg()interface{} } -type BaseService struct { - BaseModule - serviceid int - servicename string - Status int + +type Service struct { + Module + rpc.RpcHandler //rpc + name string //service name + closeSig chan bool + wg sync.WaitGroup + this IService + serviceCfg interface{} + gorouterNum int32 + startStatus bool } -func (slf *BaseService) GetServiceId() int { - return slf.serviceid +func (slf *Service) Init(iservice IService,getClientFun rpc.FuncRpcClient,getServerFun rpc.FuncRpcServer,serviceCfg interface{}) { + slf.name = reflect.Indirect(reflect.ValueOf(iservice)).Type().Name() + slf.dispatcher =timer.NewDispatcher(timerDispatcherLen) + slf.this = iservice + slf.InitRpcHandler(iservice.(rpc.IRpcHandler),getClientFun,getServerFun) + + //初始化祖先 + slf.ancestor = iservice.(IModule) + slf.seedModuleId =InitModuleId + slf.descendants = map[int64]IModule{} + slf.serviceCfg = serviceCfg + slf.gorouterNum = 1 + slf.this.OnInit() } -func (slf *BaseService) GetServiceName() string { - return slf.servicename -} +func (slf *Service) SetGoRouterNum(gorouterNum int32) bool { + //已经开始状态不允许修改协程数量 + if slf.startStatus == true { + return false + } -func (slf *BaseService) SetServiceName(serviceName string) bool { - slf.servicename = serviceName + slf.gorouterNum = gorouterNum return true } -func (slf *BaseService) GetStatus() int { - return slf.Status +func (slf *Service) Start() { + slf.startStatus = true + for i:=int32(0);i= MAX_EXECUTE_FUN { + chanIndex := queryHas % len(slf.executeList) + if chanIndex < 0 { + chanIndex = rand.Intn(len(slf.executeList)) + } + + if len(slf.executeList[chanIndex].syncExecuteFun) >= MAX_EXECUTE_FUN { dbret := DataSetList{} ret.err <- fmt.Errorf("chan is full,sql:%s", query) ret.sres <- &dbret @@ -507,7 +568,7 @@ func (slf *DBModule) SyncQuery(query string, args ...interface{}) SyncQueryDBRes return ret } - slf.syncExecuteFun <- func() { + slf.executeList[chanIndex].syncExecuteFun <- func() { rsp, err := slf.QueryEx(query, args...) ret.err <- err ret.sres <- rsp @@ -516,16 +577,35 @@ func (slf *DBModule) SyncQuery(query string, args ...interface{}) SyncQueryDBRes return ret } +func (slf *DBModule) AsyncQuery(call SyncDBResultExCallBack, queryHas int, query string, args ...interface{}) error { + chanIndex := queryHas % len(slf.executeList) + if chanIndex < 0 { + chanIndex = rand.Intn(len(slf.executeList)) + } + + if len(slf.executeList[chanIndex].syncExecuteFun) >= MAX_EXECUTE_FUN { + return fmt.Errorf("chan is full,sql:%s", query) + } + + slf.executeList[chanIndex].syncExecuteFun <- func() { + rsp, err := slf.QueryEx(query, args...) + call(rsp, err) + return + } + + return nil +} + // Exec ... func (slf *DBModule) Exec(query string, args ...interface{}) (*DBResultEx, error) { ret := &DBResultEx{} if slf.db == nil { - service.GetLogger().Printf(service.LEVER_ERROR, "cannot connect database:%s", query) + log.Error("cannot connect database:%s", query) return ret, fmt.Errorf("cannot connect database!") } if slf.CheckArgs(args) != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "CheckArgs is error :%s", query) + log.Error("CheckArgs is error :%s", query) //return ret, fmt.Errorf("cannot connect database!") return ret, fmt.Errorf("CheckArgs is error!") } @@ -534,10 +614,10 @@ func (slf *DBModule) Exec(query string, args ...interface{}) (*DBResultEx, error res, err := slf.db.Exec(query, args...) TimeFuncPass := time.Since(TimeFuncStart) if slf.IsPrintTimeLog(TimeFuncPass) { - service.GetLogger().Printf(service.LEVER_INFO, "DBModule QueryEx Time %s , Query :%s , args :%+v", TimeFuncPass, query, args) + log.Error("DBModule QueryEx Time %s , Query :%s , args :%+v", TimeFuncPass, query, args) } if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Exec:%s(%v)", query, err) + log.Error("Exec:%s(%v)", query, err) return nil, err } @@ -548,18 +628,23 @@ func (slf *DBModule) Exec(query string, args ...interface{}) (*DBResultEx, error } // SyncExec ... -func (slf *DBModule) SyncExec(query string, args ...interface{}) *SyncExecuteDBResult { +func (slf *DBModule) SyncExec(queryHas int, query string, args ...interface{}) *SyncExecuteDBResult { ret := &SyncExecuteDBResult{ sres: make(chan *DBResultEx, 1), err: make(chan error, 1), } - if len(slf.syncExecuteFun) >= MAX_EXECUTE_FUN { + chanIndex := queryHas % len(slf.executeList) + if chanIndex < 0 { + chanIndex = rand.Intn(len(slf.executeList)) + } + + if len(slf.executeList[chanIndex].syncExecuteFun) >= MAX_EXECUTE_FUN { ret.err <- fmt.Errorf("chan is full,sql:%s", query) return ret } - slf.syncExecuteFun <- func() { + slf.executeList[chanIndex].syncExecuteFun <- func() { rsp, err := slf.Exec(query, args...) if err != nil { ret.err <- err @@ -573,19 +658,41 @@ func (slf *DBModule) SyncExec(query string, args ...interface{}) *SyncExecuteDBR return ret } -func (slf *DBModule) RunExecuteDBCoroutine() { - slf.WaitGroup.Add(1) - defer slf.WaitGroup.Done() +func (slf *DBModule) AsyncExec(call SyncDBResultExCallBack, queryHas int, query string, args ...interface{}) error { + chanIndex := queryHas % len(slf.executeList) + if chanIndex < 0 { + chanIndex = rand.Intn(len(slf.executeList)) + } + + if len(slf.executeList[chanIndex].syncExecuteFun) >= MAX_EXECUTE_FUN { + return fmt.Errorf("chan is full,sql:%s", query) + } + + slf.executeList[chanIndex].syncExecuteFun <- func() { + rsp, err := slf.Exec(query, args...) + + data := DataSetList{tag:"json", blur:true, dataSetList:[]DBResultEx{}} + data.dataSetList = append(data.dataSetList, *rsp) + call(&data, err) + + return + } + + return nil +} + +func (slf *DBModule) RunExecuteDBCoroutine(has int) { + slf.waitGroup.Add(1) + defer slf.waitGroup.Done() for { select { - case <-slf.ExitChan: - service.GetLogger().Printf(LEVER_WARN, "stopping module %s...", fmt.Sprintf("%T", slf)) + case <-slf.executeList[has].syncExecuteExit: + log.Error("stopping module %s...", fmt.Sprintf("%T", slf)) return - case fun := <-slf.syncExecuteFun: + case fun := <-slf.executeList[has].syncExecuteFun: fun() } } - } func (slf *DataSetList) UnMarshal(args ...interface{}) error { @@ -740,7 +847,7 @@ func (slf *DBModule) Begin() (*Tx, error) { var txDBMoudule Tx txdb, err := slf.db.Begin() if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Begin error:%s", err.Error()) + log.Error("Begin error:%s", err.Error()) return &txDBMoudule, err } txDBMoudule.tx = txdb @@ -805,21 +912,21 @@ func (slf *Tx) CheckArgs(args ...interface{}) error { func (slf *Tx) Query(query string, args ...interface{}) DBResult { if slf.CheckArgs(args) != nil { ret := DBResult{} - service.GetLogger().Printf(service.LEVER_ERROR, "CheckArgs is error :%s", query) + log.Error("CheckArgs is error :%s", query) ret.Err = fmt.Errorf("CheckArgs is error!") return ret } if slf.tx == nil { ret := DBResult{} - service.GetLogger().Printf(service.LEVER_ERROR, "cannot connect database:%s", query) + log.Error("cannot connect database:%s", query) ret.Err = fmt.Errorf("cannot connect database!") return ret } rows, err := slf.tx.Query(query, args...) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Tx Query:%s(%v)", query, err) + log.Error("Tx Query:%s(%v)", query, err) } return DBResult{ @@ -845,12 +952,12 @@ func (slf *Tx) QueryEx(query string, args ...interface{}) (*DataSetList, error) datasetList.blur = true if slf.CheckArgs(args) != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "CheckArgs is error :%s", query) + log.Error("CheckArgs is error :%s", query) return &datasetList, fmt.Errorf("CheckArgs is error!") } if slf.tx == nil { - service.GetLogger().Printf(service.LEVER_ERROR, "cannot connect database:%s", query) + log.Error("cannot connect database:%s", query) return &datasetList, fmt.Errorf("cannot connect database!") } @@ -859,10 +966,10 @@ func (slf *Tx) QueryEx(query string, args ...interface{}) (*DataSetList, error) TimeFuncPass := time.Since(TimeFuncStart) if slf.IsPrintTimeLog(TimeFuncPass) { - service.GetLogger().Printf(service.LEVER_INFO, "Tx QueryEx Time %s , Query :%s , args :%+v", TimeFuncPass, query, args) + log.Error("Tx QueryEx Time %s , Query :%s , args :%+v", TimeFuncPass, query, args) } if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Tx Query:%s(%v)", query, err) + log.Error("Tx Query:%s(%v)", query, err) if rows != nil { rows.Close() } @@ -903,7 +1010,7 @@ func (slf *Tx) QueryEx(query string, args ...interface{}) (*DataSetList, error) if hasRet == false { if rows.Err() != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Tx Query:%s(%+v)", query, rows) + log.Error("Tx Query:%s(%+v)", query, rows) } break } @@ -916,12 +1023,12 @@ func (slf *Tx) QueryEx(query string, args ...interface{}) (*DataSetList, error) func (slf *Tx) Exec(query string, args ...interface{}) (*DBResultEx, error) { ret := &DBResultEx{} if slf.tx == nil { - service.GetLogger().Printf(service.LEVER_ERROR, "cannot connect database:%s", query) + log.Error("cannot connect database:%s", query) return ret, fmt.Errorf("cannot connect database!") } if slf.CheckArgs(args) != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "CheckArgs is error :%s", query) + log.Error("CheckArgs is error :%s", query) //return ret, fmt.Errorf("cannot connect database!") return ret, fmt.Errorf("CheckArgs is error!") } @@ -930,10 +1037,10 @@ func (slf *Tx) Exec(query string, args ...interface{}) (*DBResultEx, error) { res, err := slf.tx.Exec(query, args...) TimeFuncPass := time.Since(TimeFuncStart) if slf.IsPrintTimeLog(TimeFuncPass) { - service.GetLogger().Printf(service.LEVER_INFO, "Tx QueryEx Time %s , Query :%s , args :%+v", TimeFuncPass, query, args) + log.Error("Tx QueryEx Time %s , Query :%s , args :%+v", TimeFuncPass, query, args) } if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Tx Exec:%s(%v)", query, err) + log.Error("Tx Exec:%s(%v)", query, err) return nil, err } diff --git a/sysmodule/DBModule_test.go b/sysmodule/DBModule_test.go deleted file mode 100644 index 1eba7f1..0000000 --- a/sysmodule/DBModule_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package sysmodule_test - -import ( - "fmt" - "sync" - "testing" - - "github.com/duanhf2012/origin/sysmodule" - _ "github.com/go-sql-driver/mysql" -) - -func TestDBModule(t *testing.T) { - db := sysmodule.DBModule{} - db.ExitChan = make(chan bool) - db.WaitGroup = new(sync.WaitGroup) - - // db.Init(100, "192.168.0.5:3306", "root", "Root!!2018", "QuantFundsDB") - db.Init(100, "127.0.0.1:3306", "root", "zgh50221", "rebort_message") - db.OnInit() - tx, err := db.GetTx() - if err != nil { - fmt.Println("err 1", err) - return - } - res, err := tx.QueryEx("select id as Id, info_type as InfoType, info_type_Name as InfoTypeName from tbl_info_type where id >= 1") - if err != nil { - fmt.Println("err 2", err) - tx.Rollback() - return - } - out := []struct { - Id int64 - InfoType string - InfoTypeName string - }{} - err = res.UnMarshal(&out) - if err != nil { - fmt.Println("err 3", err) - tx.Rollback() - return - } - fmt.Println(out) - _, err = tx.Exec("insert into tbl_info_type(info_type, info_type_name) VALUES (?, ?)", "4", "weibo") - if err != nil { - fmt.Println("err 4", err) - tx.Rollback() - return - } - _, err = tx.Exec("update tbl_info_type set info_types = ? Where id = ?", "5", 0) - if err != nil { - fmt.Println("err 4", err) - tx.Rollback() - return - } - - tx.Commit() - // res, err := db.QueryEx("select * from tbl_fun_heelthrow where id >= 1") - // if err != nil { - // t.Error(err) - // } - // out := []struct { - // Addtime int64 `json:"addtime"` - // Tname string `json:"tname"` - // Uuid string `json:"uuid,omitempty"` - // AAAA string `json:"xxx"` - // }{} - // err = res.UnMarshal(&out) - // if err != nil { - // t.Error(err) - // } - - // sres := db.SyncQuery("select * from tbl_fun_heelthrow where id >= 1") - // res, err = sres.Get(2000) - // if err != nil { - // t.Error(err) - // } - - // out2 := []struct { - // Addtime int64 `json:"addtime"` - // Tname string `json:"tname"` - // Uuid string `json:"uuid,omitempty"` - // AAAA string `json:"xxx"` - // }{} - - // err = res.UnMarshal(&out2) - // if err != nil { - // t.Error(err) - // } -} diff --git a/sysmodule/HttpClientPoolModule.go b/sysmodule/HttpClientPoolModule.go index f970546..97c0723 100644 --- a/sysmodule/HttpClientPoolModule.go +++ b/sysmodule/HttpClientPoolModule.go @@ -10,11 +10,11 @@ import ( "net/url" "time" - "github.com/duanhf2012/origin/service" + "github.com/duanhf2012/originnet/service" ) type HttpClientPoolModule struct { - service.BaseModule + service.Module client *http.Client } diff --git a/sysmodule/HttpClientPoolModule_test.go b/sysmodule/HttpClientPoolModule_test.go deleted file mode 100644 index c2f674a..0000000 --- a/sysmodule/HttpClientPoolModule_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package sysmodule_test - -import ( - "fmt" - "net/http" - "testing" - - "github.com/duanhf2012/origin/sysmodule" -) - -func TestHttpClientPoolModule(t *testing.T) { - c := sysmodule.HttpClientPoolModule{} - c.Init(10, "") - - rsp := c.Request(http.MethodGet, "https://www.baidu.com/", nil, nil) - fmt.Println(rsp.Err) - fmt.Println(rsp.Header) - fmt.Println(rsp.StatusCode) - fmt.Println(rsp.Status) - fmt.Println(string(rsp.Body)) - - srsp := c.SyncRequest(http.MethodGet, "https://www.baidu.com/", nil, nil) - rsp1 := srsp.Get(1) - fmt.Println(rsp1.Err) - fmt.Println(rsp1.Header) - fmt.Println(rsp1.StatusCode) - fmt.Println(rsp1.Status) - fmt.Println(string(rsp1.Body)) -} diff --git a/sysmodule/LogModule.go b/sysmodule/LogModule.go deleted file mode 100644 index ff6cc14..0000000 --- a/sysmodule/LogModule.go +++ /dev/null @@ -1,190 +0,0 @@ -package sysmodule - -import ( - "fmt" - "log" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "time" - - "github.com/duanhf2012/origin/service" -) - -const ( - maxLinesInLog = 100000 //一个日志文件最多只写这么多行 -) - -//等级从低到高 -const ( - LEVER_UNKNOW = 0 - LEVER_DEBUG = 1 - LEVER_INFO = 2 - LEVER_WARN = 3 - LEVER_ERROR = 4 - LEVER_FATAL = 5 - LEVEL_MAX = 6 -) - -var LogPrefix = [LEVEL_MAX]string{"[UNKNOW]", "[DEBUG]", "[INFO ]", "[WARN ]", "[ERROR]", "[FATAL]"} - -type ILogger interface { - Printf(level uint, format string, v ...interface{}) - Print(level uint, v ...interface{}) - SetLogLevel(level uint) -} - -type FunListenLog func(uint, string) - -type LogModule struct { - service.BaseModule - currentDay int64 - lines int64 - logfilename string - logger [LEVEL_MAX]*log.Logger - logFile *os.File - openLevel uint - locker sync.Mutex - calldepth int - listenFun FunListenLog -} - -func (slf *LogModule) GetCurrentFileName() string { - now := time.Now() - fpath := filepath.Join("logs") - os.MkdirAll(fpath, os.ModePerm) - y, m, d := now.Date() - h := now.Hour() - mm := now.Minute() - mm -= mm % 15 //15分钟内使用同一个日志文件 - dt := y*10000 + int(m)*100 + d - tm := h*100 + mm - fname := fmt.Sprintf("%s-%d-%d.log", slf.logfilename, dt, tm) - ret := filepath.Join(fpath, fname) - return ret -} - -//检查是否需要切换新的日志文件 -func (slf *LogModule) CheckAndGenFile(fileline string) (newFile bool) { - now := time.Now() - nowDate := int64(now.Day()) - - slf.locker.Lock() - - isNewDay := nowDate != slf.currentDay - slf.lines++ - if isNewDay || slf.lines > maxLinesInLog { - // if time.Now().Day() == slf.currentDay { - // slf.locker.Unlock() - // return - // } - //fmt.Println("new log file", slf.currentDay, nowDate, isNewDay, slf.lines, maxLinesInLog) - - slf.currentDay = nowDate - slf.lines = 1 - newFile = true - if slf.logFile != nil { - slf.logFile.Close() - } - - var err error - filename := slf.GetCurrentFileName() - slf.logFile, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm) - if err != nil { - fmt.Printf("create log file %+v error!", filename) - slf.locker.Unlock() - return false - } - - for level := 0; level < LEVEL_MAX; level++ { - slf.logger[level] = log.New(slf.logFile, LogPrefix[level], log.Lshortfile|log.LstdFlags) - } - } - - slf.locker.Unlock() - return newFile -} - -func (slf *LogModule) Init(logFilePrefixName string, openLevel uint) { - slf.currentDay = 0 - slf.logfilename = logFilePrefixName - slf.openLevel = openLevel - slf.calldepth = 3 -} - -func (slf *LogModule) SetListenLogFunc(listenFun FunListenLog) { - slf.listenFun = listenFun -} - -func (slf *LogModule) GetLoggerByLevel(level uint) *log.Logger { - if level >= LEVEL_MAX { - level = 0 - } - return slf.logger[level] -} - -func (slf *LogModule) Printf(level uint, format string, v ...interface{}) { - if level < slf.openLevel { - return - } - - _, file, line, ok := runtime.Caller(slf.calldepth - 1) - if !ok { - file = "???" - line = 0 - } - fileLine := fmt.Sprintf(" %s:%d: ", file, line) - slf.CheckAndGenFile(fileLine) - - logContents := fmt.Sprintf(format, v...) - slf.doPutLog(level, fileLine, logContents) -} - -func (slf *LogModule) Print(level uint, v ...interface{}) { - if level < slf.openLevel { - return - } - - _, file, line, ok := runtime.Caller(slf.calldepth - 1) - if !ok { - file = "???" - line = 0 - } - fileLine := fmt.Sprintf(" %s:%d: ", file, line) - slf.CheckAndGenFile(fileLine) - - logContents := fmt.Sprint(v...) - slf.doPutLog(level, fileLine, logContents) -} - -//最终写日志的接口 -func (slf *LogModule) doPutLog(level uint, fileLine, logContents string) { - if slf.openLevel == LEVER_DEBUG || slf.listenFun != nil { - strlog := fmt.Sprintf("%s %s %s", LogPrefix[level], time.Now().Format("2006-01-02 15:04:05"), logContents) - if slf.openLevel == LEVER_DEBUG { - fmt.Println(strlog) - } - - if slf.listenFun != nil { - fline := fileLine - if idx := strings.LastIndex(fileLine, "/"); idx >= 0 { - fline = fileLine[idx+1:] - } - - ft := fline + " " + strlog - slf.listenFun(level, fmt.Sprintf(ft)) - } - } - - slf.GetLoggerByLevel(level).Output(slf.calldepth+1, logContents) -} - -func (slf *LogModule) AppendCallDepth(calldepth int) { - slf.calldepth += calldepth -} - -func (slf *LogModule) SetLogLevel(level uint) { - slf.openLevel = level -} diff --git a/sysmodule/RedisModule.go b/sysmodule/RedisModule.go index cbf30c6..4d443a4 100644 --- a/sysmodule/RedisModule.go +++ b/sysmodule/RedisModule.go @@ -5,9 +5,10 @@ import ( "encoding/json" "errors" "fmt" + "github.com/duanhf2012/originnet/log" "time" - "github.com/duanhf2012/origin/service" + "github.com/duanhf2012/originnet/service" "github.com/gomodule/redigo/redis" ) @@ -39,7 +40,7 @@ func (slf *RetMapString) Get() (error, map[string]bool) { type Func func() type RedisModule struct { - service.BaseModule + service.Module redispool *redis.Pool redisTask chan Func } @@ -71,7 +72,7 @@ func (slf *RedisModule) Init(redisCfg *ConfigRedis) { } c, err := redis.Dial("tcp", redisServer, opt...) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Connect redis fail reason:%v", err) + log.Error("Connect redis fail reason:%v", err) return nil, err } @@ -84,7 +85,7 @@ func (slf *RedisModule) Init(redisCfg *ConfigRedis) { } _, err := c.Do("PING") if err != nil { - service.GetLogger().Printf(service.LEVER_WARN, "Do PING fail reason:%v", err) + log.Error("Do PING fail reason:%v", err) return err } return err @@ -107,7 +108,7 @@ func (slf *RedisModule) RunAnsyTask() { func (slf *RedisModule) GoTask(fc Func) error { if len(slf.redisTask) >= MAX_TASK_CHANNEL { - service.GetLogger().Printf(service.LEVER_ERROR, "Redis task channel recover max.") + log.Error("Redis task channel recover max.") return fmt.Errorf("Redis task channel recover max.") } @@ -118,19 +119,19 @@ func (slf *RedisModule) GoTask(fc Func) error { // GetConn ... func (slf *RedisModule) getConn() (redis.Conn, error) { if slf.redispool == nil { - service.GetLogger().Printf(service.LEVER_FATAL, "Not Init RedisModule") + log.Error("Not Init RedisModule") return nil, fmt.Errorf("Not Init RedisModule") } conn := slf.redispool.Get() if conn == nil { - service.GetLogger().Printf(service.LEVER_ERROR, "Cannot get connection") + log.Error("Cannot get connection") return nil, fmt.Errorf("Cannot get connection") } if conn.Err() != nil { err := conn.Err() if err != nil { - service.GetLogger().Printf(service.LEVER_WARN, "Get Conn have error,reason:%v", err) + log.Error("Get Conn have error,reason:%v", err) } conn.Close() return nil, err @@ -148,7 +149,7 @@ func (slf *RedisModule) TestPingRedis() error { err = slf.redispool.TestOnBorrow(conn, time.Now()) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "TestOnBorrow fail,reason:%v", err) + log.Error("TestOnBorrow fail,reason:%v", err) return err } @@ -222,7 +223,7 @@ func (slf *RedisModule) GoSetStringJSONExpire(key string, val interface{}, expir slf.GoSetStringExpire(key, string(temp), expire, retErr) return nil } else { - service.GetLogger().Printf(service.LEVER_ERROR, "GoSetStringJSONExpire fail,reason:%v", err) + log.Error("GoSetStringJSONExpire fail,reason:%v", err) retErr.resultChan <- err } return err @@ -248,14 +249,14 @@ func (slf *RedisModule) setStringByExpire(key, value, expire string) error { } if retErr != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "setStringByExpire fail,reason:%v", retErr) + log.Error("setStringByExpire fail,reason:%v", retErr) return retErr } _, ok := ret.(string) if !ok { retErr = errors.New("setStringByExpire redis data is error") - service.GetLogger().Printf(service.LEVER_ERROR, "setStringByExpire redis data is error") + log.Error("setStringByExpire redis data is error") return retErr } @@ -301,7 +302,7 @@ func (slf *RedisModule) GoSetMuchStringExpire(mapInfo map[string]string, expire func (slf *RedisModule) setMuchStringByExpire(mapInfo map[string]string, expire string) error { if len(mapInfo) <= 0 { - service.GetLogger().Printf(service.LEVER_ERROR, "setMuchStringByExpire Info Is Empty") + log.Error("setMuchStringByExpire Info Is Empty") return errors.New("setMuchStringByExpire Info Is Empty") } @@ -324,7 +325,7 @@ func (slf *RedisModule) setMuchStringByExpire(mapInfo map[string]string, expire _, err = conn.Do("EXEC") if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "setMuchStringByExpire fail,reason:%v", err) + log.Error("setMuchStringByExpire fail,reason:%v", err) } return err @@ -341,7 +342,7 @@ func (slf *RedisModule) GetString(key string) (string, error) { ret, err := conn.Do("GET", key) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetString fail,reason:%v", err) + log.Error("GetString fail,reason:%v", err) return "", err } @@ -353,7 +354,7 @@ func (slf *RedisModule) GetString(key string) (string, error) { str, ok := ret.([]byte) if !ok { err = errors.New("GetString redis data is error") - service.GetLogger().Printf(service.LEVER_ERROR, "GetString redis data is error") + log.Error("GetString redis data is error") return "", err } @@ -371,7 +372,7 @@ func (slf *RedisModule) GetStringJSON(key string, st interface{}) error { ret, err := conn.Do("GET", key) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetStringJSON fail,reason:%v", err) + log.Error("GetStringJSON fail,reason:%v", err) return err } @@ -383,12 +384,12 @@ func (slf *RedisModule) GetStringJSON(key string, st interface{}) error { str, ok := ret.([]byte) if !ok { err = errors.New("GetStringJSON redis data is error!") - service.GetLogger().Printf(service.LEVER_ERROR, "GetStringJSON redis data is error!") + log.Error("GetStringJSON redis data is error!") return err } if err = json.Unmarshal(str, st); err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetStringJSON fail json.Unmarshal is error:%s,%s,reason:%v", key, string(str), err) + log.Error("GetStringJSON fail json.Unmarshal is error:%s,%s,reason:%v", key, string(str), err) return err } @@ -413,7 +414,7 @@ func (slf *RedisModule) GetMuchString(keys []string) (retMap map[string]string, // 开始Send数据 err = conn.Send("MULTI") if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetMuchString fail %v", err) + log.Error("GetMuchString fail %v", err) return nil, err } for _, val := range keys { @@ -423,7 +424,7 @@ func (slf *RedisModule) GetMuchString(keys []string) (retMap map[string]string, ret, err := conn.Do("EXEC") if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetMuchString fail %v", err) + log.Error("GetMuchString fail %v", err) return } @@ -455,7 +456,7 @@ func (slf *RedisModule) GetMuchString(keys []string) (retMap map[string]string, func (slf *RedisModule) GetMuchStringJSON(keys map[string]interface{}) error { if len(keys) <= 0 { err := errors.New("GetMuchStringJSON fail key is empty") - service.GetLogger().Printf(service.LEVER_ERROR, "GetMuchStringJSON fail key is empty") + log.Error("GetMuchStringJSON fail key is empty") return err } conn, err := slf.getConn() @@ -476,7 +477,7 @@ func (slf *RedisModule) GetMuchStringJSON(keys map[string]interface{}) error { ret, err := conn.Do("EXEC") if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetMuchStringJSON fail, reason:%v", err) + log.Error("GetMuchStringJSON fail, reason:%v", err) return err } @@ -496,7 +497,7 @@ func (slf *RedisModule) GetMuchStringJSON(keys map[string]interface{}) error { err = json.Unmarshal(strVal, keys[tempKeys[index]]) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetMuchStringJSON Unmarshal fail, reason:%v", err) + log.Error("GetMuchStringJSON Unmarshal fail, reason:%v", err) return err } } @@ -514,7 +515,7 @@ func (slf *RedisModule) ExistsKey(key string) (bool, error) { ret, err := conn.Do("EXISTS", key) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "ExistsKey fail, reason:%v", err) + log.Error("ExistsKey fail, reason:%v", err) return false, err } retValue, ok := ret.(int64) @@ -537,7 +538,7 @@ func (slf *RedisModule) DelString(key string) error { ret, err := conn.Do("DEL", key) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "DelString fail, reason:%v", err) + log.Error("DelString fail, reason:%v", err) return err } @@ -613,7 +614,7 @@ func (slf *RedisModule) DelMuchString(keys []string) (map[string]bool, error) { ret, err := conn.Do("EXEC") if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "DelMuchString fail,reason:%v", err) + log.Error("DelMuchString fail,reason:%v", err) return nil, err } @@ -652,7 +653,7 @@ func (slf *RedisModule) SetHash(redisKey, hashKey, value string) error { _, retErr := conn.Do("HSET", redisKey, hashKey, value) if retErr != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "SetHash fail,reason:%v", retErr) + log.Error("SetHash fail,reason:%v", retErr) } return retErr @@ -686,7 +687,7 @@ func (slf *RedisModule) GetAllHashJSON(redisKey string) (map[string]string, erro value, err := conn.Do("HGETALL", redisKey) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetAllHashJSON fail,reason:%v", err) + log.Error("GetAllHashJSON fail,reason:%v", err) return nil, err } @@ -697,7 +698,7 @@ func (slf *RedisModule) GetAllHashJSON(redisKey string) (map[string]string, erro func (slf *RedisModule) GetHashValueByKey(redisKey string, fieldKey string) (string, error) { if redisKey == "" || fieldKey == "" { - service.GetLogger().Printf(service.LEVER_ERROR, "GetHashValueByKey key is empty!") + log.Error("GetHashValueByKey key is empty!") return "", errors.New("Key Is Empty") } conn, err := slf.getConn() @@ -708,7 +709,7 @@ func (slf *RedisModule) GetHashValueByKey(redisKey string, fieldKey string) (str value, err := conn.Do("HGET", redisKey, fieldKey) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetHashValueByKey fail,reason:%v", err) + log.Error("GetHashValueByKey fail,reason:%v", err) return "", err } if value == nil { @@ -770,7 +771,7 @@ func (slf *RedisModule) SetMuchHashJSON(redisKey string, value map[string][]inte // 执行命令 _, err = conn.Do("EXEC") if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "SetMuchHashJSON fail,reason:%v", err) + log.Error("SetMuchHashJSON fail,reason:%v", err) } return err } @@ -841,7 +842,7 @@ func (slf *RedisModule) DelMuchHash(redisKey string, hsahKey []string) error { _, retErr := conn.Do("HDEL", arg...) if retErr != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "DelMuchHash fail,reason:%v", retErr) + log.Error("DelMuchHash fail,reason:%v", retErr) } return retErr } @@ -883,7 +884,7 @@ func (slf *RedisModule) setList(key string, value []string, setType string) erro } _, retErr := conn.Do(setType, arg...) if retErr != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "setList fail,reason:%v", retErr) + log.Error("setList fail,reason:%v", retErr) } return retErr } @@ -918,7 +919,7 @@ func (slf *RedisModule) SetListJSONLpush(key string, value interface{}) error { tempVal := []string{string(temp)} err = slf.setList(key, tempVal, "LPUSH") } else { - service.GetLogger().Printf(service.LEVER_ERROR, "SetListJSONLpush fail,reason:%v", err) + log.Error("SetListJSONLpush fail,reason:%v", err) } return err } @@ -955,7 +956,7 @@ func (slf *RedisModule) SetMuchListJSONLpush(key string, value []interface{}) er for _, val := range value { temp, err := json.Marshal(val) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "SetMuchListJSONLpush fail,reason:%v", err) + log.Error("SetMuchListJSONLpush fail,reason:%v", err) return err } @@ -998,7 +999,7 @@ func (slf *RedisModule) SetListJSONRpush(key string, value interface{}) error { tempVal := []string{string(temp)} err = slf.setList(key, tempVal, "RPUSH") } else { - service.GetLogger().Printf(service.LEVER_ERROR, "SetListJSONRpush fail,reason:%v", err) + log.Error("SetListJSONRpush fail,reason:%v", err) } return err @@ -1052,7 +1053,7 @@ func (slf *RedisModule) Lrange(key string, start, end int) ([]string, error) { reply, err := conn.Do("lrange", key, start, end) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "SetListJSONRpush fail,reason:%v", err) + log.Error("SetListJSONRpush fail,reason:%v", err) return nil, err } @@ -1069,7 +1070,7 @@ func (slf *RedisModule) GetListLen(key string) (int, error) { reply, err := conn.Do("LLEN", key) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "GetListLen fail,reason:%v", err) + log.Error("GetListLen fail,reason:%v", err) return -1, err } return redis.Int(reply, err) @@ -1086,7 +1087,7 @@ func (slf *RedisModule) RPOPListValue(key string) error { _, err = conn.Do("RPOP", key, 100) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "RPOPListValue fail,reason:%v", err) + log.Error("RPOPListValue fail,reason:%v", err) return err } return nil @@ -1103,7 +1104,7 @@ func (slf *RedisModule) LtrimList(key string, start, end int) error { _, err = conn.Do("LTRIM", key, start, end) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "LtrimListValue fail,reason:%v", err) + log.Error("LtrimListValue fail,reason:%v", err) return err } return nil @@ -1124,7 +1125,7 @@ func (slf *RedisModule) ZADDInsertJson(key string, score float64, value interfac } _, err = conn.Do("ZADD", key, score, JsonValue) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "ZADDInsertJson fail,reason:%v", err) + log.Error("ZADDInsertJson fail,reason:%v", err) return err } return nil @@ -1140,7 +1141,7 @@ func (slf *RedisModule) ZADDInsert(key string, score float64, Data interface{}) _, err = conn.Do("ZADD", key, score, Data) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "ZADDInsert fail,reason:%v", err) + log.Error("ZADDInsert fail,reason:%v", err) return err } return nil @@ -1444,13 +1445,13 @@ func (slf *RedisModule) HincrbyHashInt(redisKey, hashKey string, value int) erro _, retErr := conn.Do("HINCRBY", redisKey, hashKey, value) if retErr != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "HincrbyHashInt fail,reason:%v", retErr) + log.Error("HincrbyHashInt fail,reason:%v", retErr) } return retErr } - func (slf *RedisModule) EXPlREInsert(key string, TTl int) error { +func (slf *RedisModule) EXPlREInsert(key string, TTl int) error { conn, err := slf.getConn() if err != nil { return err @@ -1459,7 +1460,7 @@ func (slf *RedisModule) HincrbyHashInt(redisKey, hashKey string, value int) erro _, err = conn.Do("expire", key, TTl) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "expire fail,reason:%v", err) + log.Error("expire fail,reason:%v", err) return err } return nil @@ -1486,7 +1487,7 @@ func (slf *RedisModule) Keys(key string) ([]string, error) { ret, err := conn.Do("KEYS", key) if err != nil { - service.GetLogger().Printf(service.LEVER_ERROR, "KEYS fail, reason:%v", err) + log.Error("KEYS fail, reason:%v", err) return nil, err } retList, ok := ret.([]interface{}) diff --git a/sysmodule/RedisModule_test.go b/sysmodule/RedisModule_test.go deleted file mode 100644 index f80d2f3..0000000 --- a/sysmodule/RedisModule_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package sysmodule - -import ( - "fmt" - "testing" - "time" -) - -type CTestJson struct { - A string - B string -} - -func TestRedisModule(t *testing.T) { - - var cfg ConfigRedis - var redsmodule RedisModule - - cfg.IP = "127.0.0.1" - cfg.Password = "" - cfg.Port = "6379" - cfg.IdleTimeout = 2 - cfg.MaxActive = 3 - cfg.MaxIdle = 3 - cfg.DbIndex = 15 - redsmodule.Init(&cfg) - - //GoSetString - var retErr RetError - redsmodule.GoSetString("testkey", "testvalue", &retErr) - ret1, err1 := redsmodule.GetString("testkey") - - //GoSetStringExpire - redsmodule.GoSetStringExpire("setstring_key", "value11", "1", &retErr) - time.Sleep(time.Second * 2) - ret2, err2 := redsmodule.GetString("setstring_key") - fmt.Print(ret1, err1, retErr.Get(), "\n") - fmt.Print(ret2, err2, "\n") - - //GoSetStringJSON - var testjson CTestJson - testjson.A = "A value" - testjson.B = "B value" - - var retErr2 RetError - redsmodule.GoSetStringJSON("setstring_key_json", &testjson, &retErr) - - //GoSetStringJSONExpire - redsmodule.GoSetStringJSONExpire("setstring_key_json_expire", &testjson, "10", &retErr2) - fmt.Print(retErr.Get(), "\n") - fmt.Print(retErr2.Get(), "\n") - var testjson2 CTestJson - err := redsmodule.GetStringJSON("setstring_key_json", &testjson2) - fmt.Print(err, "\n", testjson2, "\n") - - //GoSetMuchString/GoSetMuchStringExpire - mapInfo := make(map[string]string) - mapInfo["A"] = "A_value" - mapInfo["B"] = "B_value" - mapInfo["C"] = "C_value" - redsmodule.GoSetMuchString(mapInfo, &retErr) - redsmodule.GoSetMuchStringExpire(mapInfo, "100", &retErr) - var keylist []string - keylist = append(keylist, "A", "C") - mapInfo, _ = redsmodule.GetMuchString(keylist) - fmt.Print(retErr.Get(), "\n", mapInfo, "\n") - - //GoDelString - redsmodule.GoDelString("setstring_key_json", &retErr) - fmt.Print(retErr.Get(), "\n") - - var retMapStr RetMapString - redsmodule.GoDelMuchString(keylist, &retMapStr) - err2, mapstr := retMapStr.Get() - fmt.Print(err2, "\n", mapstr, "\n") - - //GoSetHash - redsmodule.GoSetHash("rediskey", "hashkey", "1111", &retErr) - ret, err := redsmodule.GetHashValueByKey("rediskey", "hashkey") - fmt.Print(retErr.Get(), ret, err) - - //GoSetHashJSON - redsmodule.GoSetHashJSON("rediskey", "hashkey2", &testjson, &retErr) - fmt.Print(retErr.Get(), "\n") - - //SetMuchHashJSON - var mapHashJson map[string][]interface{} - var listInterface []interface{} - mapHashJson = make(map[string][]interface{}) - listInterface = append(listInterface, testjson, testjson) - - mapHashJson["key3"] = listInterface - mapHashJson["key4"] = listInterface - - redsmodule.GoSetMuchHashJSON("rediskey", mapHashJson, &retErr) - fmt.Print(retErr.Get(), "\n") - - //GoDelHash - redsmodule.GoDelHash("rediskey", "hashkey", &retErr) - fmt.Print(retErr.Get(), "\n") - - //GoDelMuchHash - var keys []string - keys = append(keys, "hashkey", "hashkey2") - - redsmodule.GoDelMuchHash("rediskey", keys, &retErr) - fmt.Print(retErr.Get(), "\n") - - //GoSetListLpush - redsmodule.GoSetListLpush("setlistKey", "list1", &retErr) - fmt.Print(retErr.Get(), "\n") - redsmodule.GoSetListLpush("setlistKey", "list2", &retErr) - fmt.Print(retErr.Get(), "\n") - redsmodule.GoSetListLpush("setlistKey", "list3", &retErr) - fmt.Print(retErr.Get(), "\n") -} diff --git a/sysmodule/SystemModuleDef.go b/sysmodule/SystemModuleDef.go deleted file mode 100644 index f4380f1..0000000 --- a/sysmodule/SystemModuleDef.go +++ /dev/null @@ -1,7 +0,0 @@ -package sysmodule - -//等级从低到高 -const ( - SYS_LOG = 1 - SYS_TEST = 2 -) diff --git a/sysservice/logservice.go b/sysservice/logservice.go deleted file mode 100644 index af50b75..0000000 --- a/sysservice/logservice.go +++ /dev/null @@ -1,37 +0,0 @@ -package sysservice - -import ( - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysmodule" -) - -type LogService struct { - service.BaseService - logmodule *sysmodule.LogModule -} - -func (slf *LogService) InitLog(logservicename string, logFilePrefix string, openLevel uint) { - slf.SetServiceName(logservicename) - slf.logmodule = &sysmodule.LogModule{} - slf.logmodule.Init(logFilePrefix, openLevel) -} - -func (slf *LogService) Printf(level uint, format string, v ...interface{}) { - slf.logmodule.Printf(level, format, v...) -} - -func (slf *LogService) Print(level uint, v ...interface{}) { - slf.logmodule.Print(level, v...) -} - -func (slf *LogService) AppendCallDepth(calldepth int) { - slf.logmodule.AppendCallDepth(calldepth) -} - -func (slf *LogService) SetLogLevel(level uint) { - slf.logmodule.SetLogLevel(level) -} - -func (slf *LogService) SetListenLogFunc(listenFun sysmodule.FunListenLog) { - slf.logmodule.SetListenLogFunc(listenFun) -} diff --git a/sysservice/originhttp/httpserverervice.go b/sysservice/originhttp/httpserverervice.go deleted file mode 100644 index d9e126a..0000000 --- a/sysservice/originhttp/httpserverervice.go +++ /dev/null @@ -1,511 +0,0 @@ -package originhttp - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - - "os" - "reflect" - "runtime" - "strings" - "time" - - "github.com/duanhf2012/origin/sysmodule" - - "github.com/duanhf2012/origin/rpc" - - "github.com/duanhf2012/origin/cluster" - "github.com/duanhf2012/origin/network" - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/util/uuid" -) - -type HttpRedirectData struct { - Url string - //Cookies map[string]string - - CookieList []*http.Cookie -} - -type HttpRequest struct { - Header http.Header - Body string - - ParamStr string //http://127.0.0.1:7001/aaa/bbb?aa=1中的aa=1部分 - mapParam map[string]string - URL string - //Req http.Request -} - -type HttpRespone struct { - Respone []byte - RedirectData HttpRedirectData - //Resp http.ResponseWriter -} - -type ServeHTTPRouterMux struct { - httpfiltrateList []HttpFiltrate - allowOrigin bool -} -type ControllerMapsType map[string]reflect.Value - -type HttpServerService struct { - service.BaseService - httpserver network.HttpServer - port uint16 - - controllerMaps ControllerMapsType - certfile string - keyfile string - ishttps bool - serverHTTPMux ServeHTTPRouterMux -} - -type RouterMatchData struct { - callpath string - matchURL string - routerType int8 //0表示函数调用 1表示静态资源 -} - -type RouterStaticResoutceData struct { - localpath string - method string -} - -type HTTP_METHOD int - -const ( - METHOD_GET HTTP_METHOD = iota - METHOD_POST HTTP_METHOD = 1 - //METHOD_PUT HTTP_METHOD = 2 -) - -var bPrintRequestTime bool - -var postAliasUrl map[string]map[string]RouterMatchData //url地址,对应本service地址 -var staticRouterResource map[string]RouterStaticResoutceData - -func init() { - postAliasUrl = make(map[string]map[string]RouterMatchData) - postAliasUrl["GET"] = make(map[string]RouterMatchData) - postAliasUrl["POST"] = make(map[string]RouterMatchData) - - staticRouterResource = make(map[string]RouterStaticResoutceData) -} - -type HttpHandle func(request *HttpRequest, resp *HttpRespone) error - -func AnalysisRouterUrl(url string) (string, error) { - - //替换所有空格 - url = strings.ReplaceAll(url, " ", "") - if len(url) <= 1 || url[0] != '/' { - return "", fmt.Errorf("url %s format is error!", url) - } - - //去掉尾部的/ - return strings.Trim(url, "/"), nil -} - -func (slf *HttpRequest) Query(key string) (string, bool) { - if slf.mapParam == nil { - slf.mapParam = make(map[string]string) - //分析字符串 - slf.ParamStr = strings.Trim(slf.ParamStr, "/") - paramStrList := strings.Split(slf.ParamStr, "&") - for _, val := range paramStrList { - Index := strings.Index(val, "=") - if Index >= 0 { - slf.mapParam[val[0:Index]] = val[Index+1:] - } - } - } - - ret, ok := slf.mapParam[key] - return ret, ok -} - -func Request(method HTTP_METHOD, url string, handle HttpHandle) error { - fnpath := runtime.FuncForPC(reflect.ValueOf(handle).Pointer()).Name() - - sidx := strings.LastIndex(fnpath, "*") - if sidx == -1 { - return errors.New(fmt.Sprintf("http post func path is error, %s\n", fnpath)) - } - - eidx := strings.LastIndex(fnpath, "-fm") - if sidx == -1 { - return errors.New(fmt.Sprintf("http post func path is error, %s\n", fnpath)) - } - callpath := fnpath[sidx+1 : eidx] - ridx := strings.LastIndex(callpath, ")") - if ridx == -1 { - return errors.New(fmt.Sprintf("http post func path is error, %s\n", fnpath)) - } - - hidx := strings.LastIndex(callpath, "HTTP_") - if hidx == -1 { - return errors.New(fmt.Sprintf("http post func not contain HTTP_, %s\n", fnpath)) - } - - callpath = strings.ReplaceAll(callpath, ")", "") - - var r RouterMatchData - var matchURL string - var err error - r.routerType = 0 - r.callpath = "_" + callpath - matchURL, err = AnalysisRouterUrl(url) - if err != nil { - return err - } - - var strMethod string - if method == METHOD_GET { - strMethod = "GET" - } else if method == METHOD_POST { - strMethod = "POST" - } else { - return nil - } - - postAliasUrl[strMethod][matchURL] = r - - return nil -} - -func Post(url string, handle HttpHandle) error { - return Request(METHOD_POST, url, handle) -} - -func Get(url string, handle HttpHandle) error { - return Request(METHOD_GET, url, handle) -} - -func (slf *HttpServerService) OnInit() error { - slf.serverHTTPMux = ServeHTTPRouterMux{} - slf.httpserver.Init(slf.port, &slf.serverHTTPMux, 10*time.Second, 10*time.Second) - if slf.ishttps == true { - slf.httpserver.SetHttps(slf.certfile, slf.keyfile) - } - return nil -} - -func (slf *ServeHTTPRouterMux) SetAlowOrigin(allowOrigin bool) { - slf.allowOrigin = allowOrigin -} - -func (slf *ServeHTTPRouterMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if slf.allowOrigin == true { - if origin := r.Header.Get("Origin"); origin != "" { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") - w.Header().Set("Access-Control-Allow-Headers", - "Action, Module") //有使用自定义头 需要这个,Action, Module是例子 - } - } - - if r.Method == "OPTIONS" { - return - } - - methodRouter, bok := postAliasUrl[r.Method] - if bok == false { - writeRespone(w, http.StatusNotFound, fmt.Sprint("Can not support method.")) - return - } - - //权限验证 - var errRet error - for _, filter := range slf.httpfiltrateList { - ret := filter(r.URL.Path, w, r) - if ret == nil { - errRet = nil - break - } else { - errRet = ret - } - } - if errRet != nil { - writeRespone(w, http.StatusOK, errRet.Error()) - return - } - - url := strings.Trim(r.URL.Path, "/") - var strCallPath string - matchData, ok := methodRouter[url] - if ok == true { - strCallPath = matchData.callpath - } else { - //如果是资源处理 - for k, v := range staticRouterResource { - idx := strings.Index(url, k) - if idx != -1 { - staticServer(k, v, w, r) - return - } - } - - // 拼接得到rpc服务的名称 - vstr := strings.Split(url, "/") - if len(vstr) < 2 { - writeRespone(w, http.StatusNotFound, "Cannot find path.") - return - } - strCallPath = "_" + vstr[0] + ".HTTP_" + vstr[1] - } - - defer r.Body.Close() - msg, err := ioutil.ReadAll(r.Body) - if err != nil { - writeRespone(w, http.StatusBadRequest, "") - return - } - - request := HttpRequest{r.Header, string(msg), r.URL.RawQuery, nil, r.URL.Path} - var resp HttpRespone - //resp.Resp = w - timeFuncStart := time.Now() - err = cluster.InstanceClusterMgr().Call(strCallPath, &request, &resp) - - timeFuncPass := time.Since(timeFuncStart) - if bPrintRequestTime { - service.GetLogger().Printf(service.LEVER_INFO, "HttpServer Time : %s url : %s\n", timeFuncPass, strCallPath) - } - if err != nil { - writeRespone(w, http.StatusBadRequest, fmt.Sprint(err)) - } else { - if resp.RedirectData.Url != "" { - resp.redirects(&w, r) - } else { - writeRespone(w, http.StatusOK, string(resp.Respone)) - } - - } -} - -// CkResourceDir 检查静态资源文件夹路径 -func SetStaticResource(method HTTP_METHOD, urlpath string, dirname string) error { - _, err := os.Stat(dirname) - if err != nil { - return err - } - matchURL, berr := AnalysisRouterUrl(urlpath) - if berr != nil { - return berr - } - - var routerData RouterStaticResoutceData - if method == METHOD_GET { - routerData.method = "GET" - } else if method == METHOD_POST { - routerData.method = "POST" - } else { - return nil - } - routerData.localpath = dirname - - staticRouterResource[matchURL] = routerData - return nil -} - -func writeRespone(w http.ResponseWriter, status int, msg string) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(status) - w.Write([]byte(msg)) -} - -type HttpFiltrate func(path string, w http.ResponseWriter, r *http.Request) error - -func (slf *HttpServerService) AppendHttpFiltrate(fun HttpFiltrate) bool { - slf.serverHTTPMux.httpfiltrateList = append(slf.serverHTTPMux.httpfiltrateList, fun) - - return false -} - -func (slf *HttpServerService) OnRun() bool { - - slf.httpserver.Start() - return false -} - -func NewHttpServerService(port uint16) *HttpServerService { - http := new(HttpServerService) - - http.port = port - return http -} - -func (slf *HttpServerService) OnDestory() error { - return nil -} - -func (slf *HttpServerService) OnSetupService(iservice service.IService) { - rpc.RegisterName(iservice.GetServiceName(), "HTTP_", iservice) -} - -func (slf *HttpServerService) OnRemoveService(iservice service.IService) { - return -} - -func (slf *HttpServerService) SetPrintRequestTime(isPrint bool) { - bPrintRequestTime = isPrint -} - -func staticServer(routerUrl string, routerData RouterStaticResoutceData, w http.ResponseWriter, r *http.Request) { - upath := r.URL.Path - idx := strings.Index(upath, routerUrl) - subPath := strings.Trim(upath[idx+len(routerUrl):], "/") - - destLocalPath := routerData.localpath + subPath - - writeResp := func(status int, msg string) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(status) - w.Write([]byte(msg)) - } - - switch r.Method { - //获取资源 - case "GET": - //判断文件夹是否存在 - _, err := os.Stat(destLocalPath) - if err == nil { - http.ServeFile(w, r, destLocalPath) - } else { - writeResp(http.StatusNotFound, "") - return - } - //上传资源 - case "POST": - // 在这儿处理例外路由接口 - /* - var errRet error - for _, filter := range slf.httpfiltrateList { - ret := filter(r.URL.Path, w, r) - if ret == nil { - errRet = nil - break - } else { - errRet = ret - } - } - if errRet != nil { - w.Write([]byte(errRet.Error())) - return - }*/ - r.ParseMultipartForm(32 << 20) // max memory is set to 32MB - resourceFile, resourceFileHeader, err := r.FormFile("file") - if err != nil { - fmt.Println(err) - writeResp(http.StatusNotFound, err.Error()) - return - } - defer resourceFile.Close() - //重新拼接文件名 - imgFormat := strings.Split(resourceFileHeader.Filename, ".") - if len(imgFormat) < 2 { - writeResp(http.StatusNotFound, "not a file") - return - } - filePrefixName := uuid.Rand().HexEx() - fileName := filePrefixName + "." + imgFormat[len(imgFormat)-1] - //创建文件 - localpath := fmt.Sprintf("%s%s", destLocalPath, fileName) - localfd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE, 0666) - if err != nil { - fmt.Println(err) - writeResp(http.StatusNotFound, "upload fail") - return - } - defer localfd.Close() - io.Copy(localfd, resourceFile) - writeResp(http.StatusOK, upath+"/"+fileName) - } - -} - -func (slf *HttpServerService) GetMethod(strCallPath string) (*reflect.Value, error) { - value, ok := slf.controllerMaps[strCallPath] - if ok == false { - err := fmt.Errorf("not find api") - return nil, err - } - - return &value, nil -} - -func (slf *HttpServerService) SetHttps(certfile string, keyfile string) bool { - if certfile == "" || keyfile == "" { - return false - } - - slf.ishttps = true - slf.certfile = certfile - slf.keyfile = keyfile - - return true -} - -//序列化后写入Respone -func (slf *HttpRespone) WriteRespne(v interface{}) error { - StrRet, retErr := json.Marshal(v) - if retErr != nil { - slf.Respone = []byte(`{"Code": 2,"Message":"service error"}`) - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "Json Marshal Error:%v\n", retErr) - } else { - slf.Respone = StrRet - } - - return retErr -} - -func (slf *HttpRespone) WriteRespones(Code int32, Msg string, Data interface{}) { - - var StrRet string - //判断是否有错误码 - if Code > 0 { - StrRet = fmt.Sprintf(`{"RCode": %d,"RMsg":"%s"}`, Code, Msg) - } else { - if Data == nil { - if Msg != "" { - StrRet = fmt.Sprintf(`{"RCode": 0,"RMsg":"%s"}`, Msg) - } else { - StrRet = `{"RCode": 0}` - } - } else { - if reflect.TypeOf(Data).Kind() == reflect.String { - StrRet = fmt.Sprintf(`{"RCode": %d , "Data": "%s"}`, Code, Data) - } else { - JsonRet, Err := json.Marshal(Data) - if Err != nil { - service.GetLogger().Printf(sysmodule.LEVER_ERROR, "common WriteRespone Json Marshal Err %+v", Data) - } else { - StrRet = fmt.Sprintf(`{"RCode": %d , "Data": %s}`, Code, JsonRet) - } - } - } - } - slf.Respone = []byte(StrRet) -} - -func (slf *HttpRespone) Redirect(url string, cookieList []*http.Cookie) { - slf.RedirectData.Url = url - slf.RedirectData.CookieList = cookieList -} - -func (slf *HttpRespone) redirects(w *http.ResponseWriter, req *http.Request) { - if slf.RedirectData.CookieList != nil { - for _, v := range slf.RedirectData.CookieList { - http.SetCookie(*w, v) - } - } - - http.Redirect(*w, req, slf.RedirectData.Url, - // see @andreiavrammsd comment: often 307 > 301 - http.StatusTemporaryRedirect) -} diff --git a/sysservice/pprofservice.go b/sysservice/pprofservice.go deleted file mode 100644 index 41f8b1d..0000000 --- a/sysservice/pprofservice.go +++ /dev/null @@ -1,73 +0,0 @@ -package sysservice - -import ( - "encoding/json" - "fmt" - "runtime/pprof" - "time" - - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/sysservice/originhttp" -) - -type PProfService struct { - service.BaseService - fisttime int -} - -type ProfileData struct { - Name string - Count int -} - -type Profilestruct struct { - Fisttime int - ProfileList []ProfileData -} - -func (slf *PProfService) OnInit() error { - slf.fisttime = (int)(time.Now().UnixNano()) - return nil -} - -func (slf *PProfService) GetPprof() ([]byte, error) { - var pfiles Profilestruct - pfiles.Fisttime = slf.fisttime - - for _, p := range pprof.Profiles() { - pfiles.ProfileList = append(pfiles.ProfileList, ProfileData{ - Name: p.Name(), - Count: p.Count(), - }) - } - - return json.Marshal(pfiles) -} - -func (slf *PProfService) HTTP_DebugPProf(request *originhttp.HttpRequest, resp *originhttp.HttpRespone) error { - var err error - resp.Respone, err = slf.GetPprof() - if err != nil { - resp.Respone = []byte(fmt.Sprint(err)) - } - return nil -} - -func (slf *PProfService) RPC_DebugPProf(arg *string, ret *Profilestruct) error { - - ret.Fisttime = slf.fisttime - for _, p := range pprof.Profiles() { - ret.ProfileList = append(ret.ProfileList, ProfileData{ - Name: p.Name(), - Count: p.Count(), - }) - } - - return nil -} - -func (slf *PProfService) HTTP_Test(request *originhttp.HttpRequest, resp *originhttp.HttpRespone) error { - - resp.Respone = []byte(request.Body) - return nil -} diff --git a/sysservice/synccacheservice/synccacheservice.go b/sysservice/synccacheservice/synccacheservice.go deleted file mode 100644 index bec5309..0000000 --- a/sysservice/synccacheservice/synccacheservice.go +++ /dev/null @@ -1,167 +0,0 @@ -package synccacheservice - -import ( - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/duanhf2012/origin/cluster" - "github.com/duanhf2012/origin/service" - "github.com/duanhf2012/origin/util" -) - -const ( - MAX_SYNC_DATA_CHAN_NUM = 10000 -) - -//CReportService ... -type CSyncCacheService struct { - service.BaseService - mapCache util.Map - syncQueue *util.SyncQueue - - nodeIdList []int - syncDataChanList []chan *SyncCacheData -} - -type SyncCacheData struct { - OperType int8 //0 表示添加或者更新 1表示删除 - Key string - Val string - Wxpire int32 //ms - NodeIdList []int - reTryCount uint32 - reTryTime int64 -} - -//OnInit ... -func (slf *CSyncCacheService) OnInit() error { - slf.syncQueue = util.NewSyncQueue() - var callServiceName string - slf.nodeIdList = cluster.InstanceClusterMgr().GetNodeList("CSyncCacheService.RPC_SyncString", &callServiceName, nil) - for _, nodeId := range slf.nodeIdList { - syncCacheData := make(chan *SyncCacheData, MAX_SYNC_DATA_CHAN_NUM) - slf.syncDataChanList = append(slf.syncDataChanList, syncCacheData) - go slf.syncRouter(nodeId, syncCacheData) - } - - return nil -} - -func (slf *CSyncCacheService) syncRouter(nodeId int, syncDataChan chan *SyncCacheData) error { - - tryCount := 0 - for { - select { - case <-slf.ExitChan: - break - case data := <-syncDataChan: - var ret int - cluster.CallNode(nodeId, "CSyncCacheService.RPC_SyncString", data, &ret) - if ret == 0 { - if tryCount < 3 { - time.Sleep(800 * time.Millisecond) - } else { - time.Sleep(1500 * time.Millisecond) - } - - slf.tryPushSyncData(syncDataChan, data) - tryCount++ - } else { - tryCount = 0 - } - } - } - - return nil -} - -func (slf *CSyncCacheService) tryPushSyncData(syncDataChan chan *SyncCacheData, syncData *SyncCacheData) bool { - if len(syncDataChan) >= MAX_SYNC_DATA_CHAN_NUM { - return false - } - syncDataChan <- syncData - - return true -} - -func (slf *CSyncCacheService) RPC_SyncString(request *SyncCacheData, ret *int) error { - - if request.OperType == 0 { - slf.mapCache.Set(request.Key, request.Val) - } else { - slf.mapCache.Del(request.Key) - } - - *ret = 1 - return nil -} - -func SetStringJson(key string, val interface{}) error { - - byteBuf, err := json.Marshal(val) - if err != nil { - return err - } - - SetString(key, string(byteBuf)) - return nil -} - -func GetStringJson(key string, val interface{}) error { - ret, err := GetString(key) - if err != nil { - return err - } - - err = json.Unmarshal([]byte(ret), val) - return err -} - -func DelString(key string) error { - pubcacheservice := service.InstanceServiceMgr().FindService("CSyncCacheService") - if pubcacheservice == nil { - return errors.New("Cannot find CSyncCacheService") - } - - pPubCacheService := pubcacheservice.(*CSyncCacheService) - syncCacheData := SyncCacheData{1, key, "", 0, pPubCacheService.nodeIdList[:], 0, 0} - - for _, syncChan := range pPubCacheService.syncDataChanList { - pPubCacheService.tryPushSyncData(syncChan, &syncCacheData) - } - - return nil -} - -func SetString(key string, val string) error { - pubcacheservice := service.InstanceServiceMgr().FindService("CSyncCacheService") - if pubcacheservice == nil { - return errors.New("Cannot find CSyncCacheService") - } - - //同步所有远程结点 - pPubCacheService := pubcacheservice.(*CSyncCacheService) - syncCacheData := SyncCacheData{0, key, val, 0, pPubCacheService.nodeIdList[:], 0, 0} - for _, syncChan := range pPubCacheService.syncDataChanList { - pPubCacheService.tryPushSyncData(syncChan, &syncCacheData) - } - - return nil -} - -func GetString(key string) (string, error) { - pubcacheservice := service.InstanceServiceMgr().FindService("CSyncCacheService") - if pubcacheservice == nil { - return "", errors.New("Cannot find CSyncCacheService") - } - - pPubCacheService := pubcacheservice.(*CSyncCacheService) - ret := pPubCacheService.mapCache.Get(key) - if ret == nil { - return "", errors.New(fmt.Sprintf("Cannot find key :%s", key)) - } - - return ret.(string), nil -} diff --git a/sysservice/tcpservice.go b/sysservice/tcpservice.go new file mode 100644 index 0000000..2c29ce1 --- /dev/null +++ b/sysservice/tcpservice.go @@ -0,0 +1,137 @@ +package sysservice + +import ( + "fmt" + "github.com/duanhf2012/originnet/event" + "github.com/duanhf2012/originnet/log" + "github.com/duanhf2012/originnet/network" + "github.com/duanhf2012/originnet/service" + "sync" +) + +type TcpService struct { + tcpServer network.TCPServer + service.Service + + tcpService *TcpService + mapClientLocker sync.RWMutex + mapClient map[uint64] *Client + initClientId uint64 + process network.Processor +} + +const Default_MaxConnNum = 3000 +const Default_PendingWriteNum = 10000 +const Default_LittleEndian = false +const Default_MinMsgLen = 2 +const Default_MaxMsgLen = 65535 + + + +func (slf *TcpService) OnInit() error{ + iConfig := slf.GetServiceCfg() + if iConfig == nil { + return fmt.Errorf("%s service config is error!",slf.GetName()) + } + tcpCfg := iConfig.(map[string]interface{}) + addr,ok := tcpCfg["ListenAddr"] + if ok == false { + return fmt.Errorf("%s service config is error!",slf.GetName()) + } + slf.tcpServer.Addr = addr.(string) + slf.tcpServer.MaxConnNum = Default_MaxConnNum + slf.tcpServer.PendingWriteNum = Default_PendingWriteNum + slf.tcpServer.LittleEndian = Default_LittleEndian + slf.tcpServer.MinMsgLen = Default_MinMsgLen + slf.tcpServer.MaxMsgLen = Default_MaxMsgLen + MaxConnNum,ok := tcpCfg["MaxConnNum"] + if ok == true { + slf.tcpServer.MaxConnNum = int(MaxConnNum.(float64)) + } + PendingWriteNum,ok := tcpCfg["PendingWriteNum"] + if ok == true { + slf.tcpServer.PendingWriteNum = int(PendingWriteNum.(float64)) + } + LittleEndian,ok := tcpCfg["LittleEndian"] + if ok == true { + slf.tcpServer.LittleEndian = LittleEndian.(bool) + } + MinMsgLen,ok := tcpCfg["MinMsgLen"] + if ok == true { + slf.tcpServer.MinMsgLen = uint32(MinMsgLen.(float64)) + } + MaxMsgLen,ok := tcpCfg["MaxMsgLen"] + if ok == true { + slf.tcpServer.MaxMsgLen = uint32(MaxMsgLen.(float64)) + } + slf.tcpServer.NewAgent =slf.NewClient + slf.tcpServer.Start() + //加载配置 + return nil +} + +func (slf *TcpService) SetProcessor(process network.Processor){ + slf.process = process +} + +func (slf *TcpService) NewClient(conn *network.TCPConn) network.Agent { + slf.mapClientLocker.Lock() + defer slf.mapClientLocker.Unlock() + + for { + slf.initClientId+=1 + _,ok := slf.mapClient[slf.initClientId] + if ok == true { + continue + } + + pClient := &Client{tcpConn:conn} + slf.mapClient[slf.initClientId] = pClient + return pClient + } + + return nil +} + + + +type Client struct { + id uint64 + tcpConn *network.TCPConn + tcpService *TcpService +} + +type TcpPack struct { + ClientId uint64 + Data interface{} +} + +func (slf *Client) GetId() uint64 { + return slf.id +} + +func (slf *Client) Run() { + slf.tcpService.GetEventReciver().NotifyEvent(&event.Event{Type:event.Sys_Event_Tcp_Connected,Data:&TcpPack{ClientId:slf.id}}) + for{ + bytes,err := slf.tcpConn.ReadMsg() + if err != nil { + log.Debug("read client id %d is error",err) + break + } + data,err:=slf.tcpService.process.Unmarshal(bytes) + if err != nil { + log.Debug("process.Unmarshal is error:%+v",err) + continue + } + + + slf.tcpService.GetEventReciver().NotifyEvent(&event.Event{Type:event.Sys_Event_Tcp_RecvPack,Data:&TcpPack{ClientId:slf.id,Data:data}}) + } +} + +func (slf *Client) OnClose(){ + slf.tcpService.GetEventReciver().NotifyEvent(&event.Event{Type:event.Sys_Event_Tcp_DisConnected,Data:&TcpPack{ClientId:slf.id}}) + slf.tcpService.mapClientLocker.Lock() + defer slf.tcpService.mapClientLocker.Unlock() + delete (slf.tcpService.mapClient,slf.GetId()) +} diff --git a/sysservice/tcpsocketpbservice.go b/sysservice/tcpsocketpbservice.go deleted file mode 100644 index f0be692..0000000 --- a/sysservice/tcpsocketpbservice.go +++ /dev/null @@ -1,172 +0,0 @@ -package sysservice - -import ( - "errors" - "github.com/duanhf2012/origin/network" - "github.com/duanhf2012/origin/service" - "github.com/golang/protobuf/proto" - "reflect" -) - -type TcpSocketPbService struct { - service.BaseService - listenaddr string - tcpsocketserver network.TcpSocketServer - MsgProcessor -} - - -type MessageHandler func(clientid uint64,msgtype uint16,msg proto.Message) -type MessageRecvHandler func(pClient *network.SClient,pPack *network.MsgBasePack) -type EventHandler func(clientid uint64) -type ExceptMsgHandler func(clientid uint64,pPack *network.MsgBasePack,err error) - -type MsgProcessor struct { - mapMsg map[uint16]MessageInfo - connEvent EventHandler - disconnEvent EventHandler - exceptMsgHandler ExceptMsgHandler - messageRecvHandler MessageRecvHandler -} - - -func (slf *MsgProcessor) InitProcessor(){ - slf.mapMsg = make(map[uint16]MessageInfo) -} - -func (slf *MsgProcessor) RegMessage(msgtype uint16,msg proto.Message,handle MessageHandler){ - var info MessageInfo - - info.msgType = reflect.TypeOf(msg.(proto.Message)) - info.msgHandler = handle - slf.mapMsg[msgtype] = info -} - -func (slf *MsgProcessor) RegConnectEvent(eventHandler EventHandler){ - slf.connEvent = eventHandler -} - -func (slf *MsgProcessor) RegDisconnectEvent(eventHandler EventHandler){ - slf.disconnEvent = eventHandler -} - -func (slf *MsgProcessor) RegExceptMessage(exceptMsgHandler ExceptMsgHandler){ - slf.exceptMsgHandler = exceptMsgHandler -} - -func (slf *MsgProcessor) RegRecvMessage(msgHandler MessageRecvHandler){ - slf.messageRecvHandler = msgHandler -} - -func (slf *MsgProcessor) OnExceptMsg (pClient *network.SClient,pPack *network.MsgBasePack,err error){ - if slf.exceptMsgHandler!=nil { - slf.exceptMsgHandler(pClient.GetId(),pPack,err) - }else{ - pClient.Close() - //记录日志 - service.GetLogger().Printf(service.LEVER_WARN, "OnExceptMsg packtype %d,error %+v",pPack.PackType,err) - } -} - -func NewTcpSocketPbService(listenaddr string) *TcpSocketPbService { - ts := new(TcpSocketPbService) - - ts.listenaddr = listenaddr - ts.mapMsg = make(map[uint16]MessageInfo,1) - ts.tcpsocketserver.Register(listenaddr,ts) - return ts -} - -func (slf *TcpSocketPbService) OnInit() error { - return nil -} - -func (slf *TcpSocketPbService) OnRun() bool { - slf.tcpsocketserver.Start() - - return false -} - - -type MessageInfo struct { - msgType reflect.Type - msgHandler MessageHandler -} - - -func (slf *TcpSocketPbService) RegMessage(msgtype uint16,msg proto.Message,handle MessageHandler){ - var info MessageInfo - - info.msgType = reflect.TypeOf(msg.(proto.Message)) - info.msgHandler = handle - slf.mapMsg[msgtype] = info -} - - -func (slf *TcpSocketPbService) OnConnected(pClient *network.SClient){ - if slf.connEvent!=nil { - slf.connEvent(pClient.GetId()) - } -} - -func (slf *TcpSocketPbService) OnDisconnect(pClient *network.SClient){ - if slf.disconnEvent!=nil { - slf.disconnEvent(pClient.GetId()) - } -} - - -func (slf *MsgProcessor) Handle(pClient *network.SClient,pPack *network.MsgBasePack){ - if info, ok := slf.mapMsg[pPack.PackType]; ok { - msg := reflect.New(info.msgType.Elem()).Interface() - tmp := msg.(proto.Message) - err := proto.Unmarshal(pPack.Body, tmp) - if err != nil { - slf.OnExceptMsg(pClient,pPack,err) - return - } - - info.msgHandler(pClient.GetId(),pPack.PackType, msg.(proto.Message)) - return - }else if slf.messageRecvHandler!=nil { - slf.messageRecvHandler(pClient,pPack) - return - } - - slf.OnExceptMsg(pClient,pPack,errors.New("not found PackType")) -} - - -func (slf *TcpSocketPbService) OnRecvMsg(pClient *network.SClient, pPack *network.MsgBasePack){ - slf.Handle(pClient,pPack) -} - -func DefaultTSPbService() *TcpSocketPbService{ - iservice := service.InstanceServiceMgr().FindService("TcpSocketPbService") - if iservice == nil { - return nil - } - - return iservice.(*TcpSocketPbService) -} - -func GetTcpSocketPbService(serviceName string) *TcpSocketPbService{ - iservice := service.InstanceServiceMgr().FindService(serviceName) - if iservice == nil { - return nil - } - - return iservice.(*TcpSocketPbService) -} - -func (slf *TcpSocketPbService) SendMsg(clientid uint64,packtype uint16,message proto.Message) error{ - return slf.tcpsocketserver.SendMsg(clientid,packtype,message) -} - -func (slf *TcpSocketPbService) Close(clientid uint64) error{ - return slf.tcpsocketserver.Close(clientid) -} - -func (slf *TcpSocketPbService) Send(clientid uint64,pack *network.MsgBasePack) error { - return slf.tcpsocketserver.Send(clientid,pack) -} diff --git a/sysservice/wsagentservice.go b/sysservice/wsagentservice.go deleted file mode 100644 index 08666f6..0000000 --- a/sysservice/wsagentservice.go +++ /dev/null @@ -1,41 +0,0 @@ -package sysservice - -import ( - "github.com/duanhf2012/origin/network" - "github.com/duanhf2012/origin/service" -) - -type WSAgentService struct { - service.BaseService - agentserver network.WSAgentServer - pattern string - port uint16 - bEnableCompression bool -} - -func (ws *WSAgentService) OnInit() error { - ws.AddModule(&ws.agentserver) - ws.agentserver.Init(ws.port) - return nil -} - -func (ws *WSAgentService) OnRun() bool { - ws.agentserver.Start() - - return false -} - -func NewWSAgentService(port uint16) *WSAgentService { - wss := new(WSAgentService) - - wss.port = port - return wss -} - -func (ws *WSAgentService) OnDestory() error { - return nil -} - -func (ws *WSAgentService) SetupAgent(pattern string, agent network.IAgent, bEnableCompression bool) { - ws.agentserver.SetupAgent(pattern, agent, bEnableCompression) -} diff --git a/sysservice/wsserverservice.go b/sysservice/wsserverservice.go deleted file mode 100644 index faabf7d..0000000 --- a/sysservice/wsserverservice.go +++ /dev/null @@ -1,47 +0,0 @@ -package sysservice - -import ( - "github.com/duanhf2012/origin/network" - "github.com/duanhf2012/origin/service" -) - -type WSServerService struct { - service.BaseService - wsserver network.WebsocketServer - - pattern string - port uint16 - messageReciver network.IMessageReceiver - bEnableCompression bool - } - -func (ws *WSServerService) OnInit() error { - - ws.wsserver.Init(ws.port) - return nil -} - -func (ws *WSServerService) OnRun() bool { - ws.wsserver.Start() - - return false -} - -func NewWSServerService(port uint16) *WSServerService { - wss := new(WSServerService) - - wss.port = port - return wss -} - -func (ws *WSServerService) OnDestory() error { - return nil -} -func (ws *WSServerService) SetupReciver(pattern string, messageReciver network.IMessageReceiver, bEnableCompression bool) { - ws.wsserver.SetupReciver(pattern, messageReciver, bEnableCompression) -} - -func (slf *WSServerService) SetWSS(certfile string, keyfile string) bool { - slf.wsserver.SetWSS(certfile, keyfile) - return true -} diff --git a/sysservice/wsservice.go b/sysservice/wsservice.go new file mode 100644 index 0000000..ce47f20 --- /dev/null +++ b/sysservice/wsservice.go @@ -0,0 +1,4 @@ +package sysservice + + + diff --git a/util/Log.go b/util/Log.go deleted file mode 100644 index 197d55d..0000000 --- a/util/Log.go +++ /dev/null @@ -1,10 +0,0 @@ -package util - - - -type PrintLog func(uint, string, ...interface{}) - -var Log PrintLog - - - diff --git a/util/Timer.go b/util/Timer.go deleted file mode 100644 index cc9c508..0000000 --- a/util/Timer.go +++ /dev/null @@ -1,94 +0,0 @@ -package util - -import "time" - -type Timer struct { - lasttime int64 - timeinterval int64 - - setupZeroDBase time.Duration //0表示普通模式 1表示切换分钟模式 -} - -func (slf *Timer) GetTimerinterval() int64 { - return slf.timeinterval -} - -func (slf *Timer) SetupTimer(ms int32) { - slf.lasttime = time.Now().UnixNano() - slf.timeinterval = int64(ms) * 1e6 -} - -func (slf *Timer) SetupTimerEx(tm time.Duration) { - slf.lasttime = time.Now().UnixNano() - slf.timeinterval = int64(tm) -} - - -func (slf *Timer) SetupTimerDouble() { - slf.lasttime = time.Now().UnixNano() - slf.timeinterval *= 2 -} - -func (slf *Timer) SetTimerHalf() { - slf.lasttime = time.Now().UnixNano() - slf.timeinterval /= 2 -} - -//检查整点分钟数触发 -func (slf *Timer) SetupZeroTimer(baseD time.Duration, interval int64) { - timeNow := time.Now() - nt := timeNow.Truncate(baseD) - slf.lasttime = nt.UnixNano() - slf.timeinterval = baseD.Nanoseconds() * interval - slf.setupZeroDBase = baseD -} - -func (slf *Timer) ResetStartTime() { - slf.lasttime = 0 -} - -func (slf *Timer) CheckTimeOut() bool { - now := time.Now() - if slf.setupZeroDBase.Nanoseconds() == 0 { - if now.UnixNano() > slf.lasttime+slf.timeinterval { - slf.lasttime = now.UnixNano() - return true - } - } else { //整点模式 - if now.UnixNano() > slf.lasttime+slf.timeinterval { - slf.SetupZeroTimer(slf.setupZeroDBase, slf.timeinterval/slf.setupZeroDBase.Nanoseconds()) - return true - } - } - - return false -} - -func GetTomorrowTimestamp() int64{ - timeStr := time.Now().Format("2006-01-02") - t, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr+" 23:59:59", time.Local) - return t.Unix()+1 -} - -func IsSameDay(timeFir, timeSec int64) bool{ - firTime:=time.Unix(timeFir,0) - secTime:=time.Unix(timeSec,0) - if firTime.Day()==secTime.Day()&&firTime.Month()==secTime.Month()&&firTime.Year()==secTime.Year(){ - return true - } - - return false -} - -func IsCrossOneDay(timeFir,timeSec int64)bool{ - firTime := time.Unix(timeFir,0) - secTime := time.Unix(timeSec,0) - firTime.Add(time.Hour*24).Day() - - if firTime.Day() == secTime.Day() && - firTime.Month()==secTime.Month()&& - firTime.Year()==secTime.Year(){ - return true - } - return false -} diff --git a/util/aes_encrypt.go b/util/aesencrypt/aes_encrypt.go similarity index 72% rename from util/aes_encrypt.go rename to util/aesencrypt/aes_encrypt.go index 7c34ca3..d42437e 100644 --- a/util/aes_encrypt.go +++ b/util/aesencrypt/aes_encrypt.go @@ -1,17 +1,4 @@ -// Copyright 2014 mqantserver Author. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package util +package aesencrypt import ( "crypto/aes" @@ -86,4 +73,4 @@ func (this *AesEncrypt) Decrypt(src []byte) (strDesc string, err error) { aesDecrypter := cipher.NewCFBDecrypter(aesBlockDecrypter, iv) aesDecrypter.XORKeyStream(decrypted, src) return string(decrypted), nil -} +} \ No newline at end of file diff --git a/util/Coroutine.go b/util/coroutine/coroutine.go similarity index 89% rename from util/Coroutine.go rename to util/coroutine/coroutine.go index 92d6dc7..bb99974 100644 --- a/util/Coroutine.go +++ b/util/coroutine/coroutine.go @@ -1,4 +1,4 @@ -package util +package coroutine import ( "fmt" @@ -12,11 +12,7 @@ func F(callback interface{},recoverNum int, args ...interface{}) { var coreInfo string coreInfo = string(debug.Stack()) coreInfo += "\n" + fmt.Sprintf("Core information is %v\n", r) - if Log != nil { - Log(5, coreInfo) - } else { - fmt.Print(coreInfo) - } + fmt.Print(coreInfo) if recoverNum==-1 ||recoverNum-1 >= 0 { recoverNum -= 1 diff --git a/util/deepcopy.go b/util/deepcopy/deepcopy.go similarity index 95% rename from util/deepcopy.go rename to util/deepcopy/deepcopy.go index 570718a..0d1dda1 100644 --- a/util/deepcopy.go +++ b/util/deepcopy/deepcopy.go @@ -1,9 +1,6 @@ -package util +package deepcopy -// reference: https://github.com/mohae/deepcopy -import ( - "reflect" -) +import "reflect" func deepCopy(dst, src reflect.Value) { switch src.Kind() { diff --git a/util/example_test.go b/util/example_test.go deleted file mode 100644 index e233ba3..0000000 --- a/util/example_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package util_test - -import ( - "fmt" - "testing" - - "github.com/duanhf2012/origin/util" -) - -func ExampleMap(t *testing.T) { - maps := util.NewMapEx() - for i := 0; i < 10000; i++ { - maps.Set(i, i) - } - - for i := 0; i < 10000; i++ { - ret := maps.Get(i) - if ret.(int) != i { - fmt.Printf("cannot find i:%d\n", i) - } - } - - for i := 0; i < 10000; i++ { - maps.LockSet(i, func(key interface{}, val interface{}) interface{} { - return val.(int) + 1 - }) - } - - for i := 0; i < 10000; i++ { - ret := maps.Get(i) - if ret.(int) != i { - fmt.Printf("cannot find i:%d\n", i) - } - } -} diff --git a/util/has/hash.go b/util/has/hash.go new file mode 100644 index 0000000..1ff3915 --- /dev/null +++ b/util/has/hash.go @@ -0,0 +1 @@ +package has diff --git a/util/hash/hash.go b/util/hash/hash.go index 54320be..4dea1b2 100644 --- a/util/hash/hash.go +++ b/util/hash/hash.go @@ -4,4 +4,4 @@ import "hash/crc32" func HashNumber(s string) uint { return uint(crc32.ChecksumIEEE([]byte(s))) -} +} \ No newline at end of file diff --git a/util/queue.go b/util/queue.go deleted file mode 100644 index ff2c667..0000000 --- a/util/queue.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Package queue provides a fast, ring-buffer queue based on the version suggested by Dariusz Górecki. -Using this instead of other, simpler, queue implementations (slice+append or linked list) provides -substantial memory and time benefits, and fewer GC pauses. -The queue implemented here is as fast as it is for an additional reason: it is *not* thread-safe. -*/ -package util - -// minQueueLen is smallest capacity that queue may have. -// Must be power of 2 for bitwise modulus: x % n == x & (n - 1). -const minQueueLen = 16 - -// Queue represents a single instance of the queue data structure. -type Queue struct { - buf []interface{} - head, tail, count int -} - -// New constructs and returns a new Queue. -func NewQueue() *Queue { - return &Queue{ - buf: make([]interface{}, minQueueLen), - } -} - -// Length returns the number of elements currently stored in the queue. -func (q *Queue) Length() int { - return q.count -} - -// resizes the queue to fit exactly twice its current contents -// this can result in shrinking if the queue is less than half-full -func (q *Queue) resize() { - newBuf := make([]interface{}, q.count<<1) - - if q.tail > q.head { - copy(newBuf, q.buf[q.head:q.tail]) - } else { - n := copy(newBuf, q.buf[q.head:]) - copy(newBuf[n:], q.buf[:q.tail]) - } - - q.head = 0 - q.tail = q.count - q.buf = newBuf -} - -// Add puts an element on the end of the queue. -func (q *Queue) Add(elem interface{}) { - if q.count == len(q.buf) { - q.resize() - } - - q.buf[q.tail] = elem - // bitwise modulus - q.tail = (q.tail + 1) & (len(q.buf) - 1) - q.count++ -} - -// Peek returns the element at the head of the queue. This call panics -// if the queue is empty. -func (q *Queue) Peek() interface{} { - if q.count <= 0 { - panic("queue: Peek() called on empty queue") - } - return q.buf[q.head] -} - -// Get returns the element at index i in the queue. If the index is -// invalid, the call will panic. This method accepts both positive and -// negative index values. Index 0 refers to the first element, and -// index -1 refers to the last. -func (q *Queue) Get(i int) interface{} { - // If indexing backwards, convert to positive index. - if i < 0 { - i += q.count - } - if i < 0 || i >= q.count { - panic("queue: Get() called with index out of range") - } - // bitwise modulus - return q.buf[(q.head+i)&(len(q.buf)-1)] -} - -// Remove removes and returns the element from the front of the queue. If the -// queue is empty, the call will panic. -func (q *Queue) Remove() interface{} { - if q.count <= 0 { - panic("queue: Remove() called on empty queue") - } - ret := q.buf[q.head] - q.buf[q.head] = nil - // bitwise modulus - q.head = (q.head + 1) & (len(q.buf) - 1) - q.count-- - // Resize down if buffer 1/4 full. - if len(q.buf) > minQueueLen && (q.count<<2) == len(q.buf) { - q.resize() - } - return ret -} diff --git a/util/queue/.gitignore b/util/queue/.gitignore deleted file mode 100644 index 8365624..0000000 --- a/util/queue/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test diff --git a/util/queue/.travis.yml b/util/queue/.travis.yml deleted file mode 100644 index 235a40a..0000000 --- a/util/queue/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go -sudo: false - -go: - - 1.2 - - 1.3 - - 1.4 diff --git a/util/queue/LICENSE b/util/queue/LICENSE deleted file mode 100644 index d5f36db..0000000 --- a/util/queue/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Evan Huus - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/util/queue/README.md b/util/queue/README.md deleted file mode 100644 index db93693..0000000 --- a/util/queue/README.md +++ /dev/null @@ -1,16 +0,0 @@ -Queue -===== - -[![Build Status](https://travis-ci.org/eapache/queue.svg)](https://travis-ci.org/eapache/queue) -[![GoDoc](https://godoc.org/github.com/eapache/queue?status.svg)](https://godoc.org/github.com/eapache/queue) -[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-active-blue.svg)](https://eapache.github.io/conduct.html) - -A fast Golang queue using a ring-buffer, based on the version suggested by Dariusz Górecki. -Using this instead of other, simpler, queue implementations (slice+append or linked list) provides -substantial memory and time benefits, and fewer GC pauses. - -The queue implemented here is as fast as it is in part because it is *not* thread-safe. - -Follows semantic versioning using https://gopkg.in/ - import from -[`gopkg.in/eapache/queue.v1`](https://gopkg.in/eapache/queue.v1) -for guaranteed API stability. diff --git a/util/rand.go b/util/rand/rand.go similarity index 98% rename from util/rand.go rename to util/rand/rand.go index 557c471..ad98c49 100644 --- a/util/rand.go +++ b/util/rand/rand.go @@ -1,4 +1,4 @@ -package util +package rand import ( "math/rand" diff --git a/util/semaphore.go b/util/semaphore/semaphore.go similarity index 91% rename from util/semaphore.go rename to util/semaphore/semaphore.go index 16fd136..00f5e85 100644 --- a/util/semaphore.go +++ b/util/semaphore/semaphore.go @@ -1,4 +1,4 @@ -package util +package semaphore type Semaphore chan struct{} diff --git a/util/sync_queue.go b/util/sync_queue.go deleted file mode 100644 index 186d464..0000000 --- a/util/sync_queue.go +++ /dev/null @@ -1,102 +0,0 @@ -package util - -import ( - "sync" -) - -// Synchronous FIFO queue -type SyncQueue struct { - lock sync.Mutex - popable *sync.Cond - buffer *Queue - closed bool -} - -// Create a new SyncQueue -func NewSyncQueue() *SyncQueue { - ch := &SyncQueue{ - buffer: NewQueue(), - } - ch.popable = sync.NewCond(&ch.lock) - return ch -} - -// Pop an item from SyncQueue, will block if SyncQueue is empty -func (q *SyncQueue) Pop() (v interface{}) { - c := q.popable - buffer := q.buffer - - q.lock.Lock() - for buffer.Length() == 0 && !q.closed { - c.Wait() - } - - if buffer.Length() > 0 { - v = buffer.Peek() - buffer.Remove() - } - - q.lock.Unlock() - return -} - -// Try to pop an item from SyncQueue, will return immediately with bool=false if SyncQueue is empty -func (q *SyncQueue) TryPop() (v interface{}, ok bool) { - buffer := q.buffer - - q.lock.Lock() - - if buffer.Length() > 0 { - v = buffer.Peek() - buffer.Remove() - ok = true - } else if q.closed { - ok = true - } - - q.lock.Unlock() - return -} - -// Push an item to SyncQueue. Always returns immediately without blocking -func (q *SyncQueue) Push(v interface{}) { - q.lock.Lock() - if !q.closed { - q.buffer.Add(v) - q.popable.Signal() - } - q.lock.Unlock() -} - -// Get the length of SyncQueue -func (q *SyncQueue) Len() (l int) { - q.lock.Lock() - l = q.buffer.Length() - q.lock.Unlock() - return -} - -// Close SyncQueue -// -// After close, Pop will return nil without block, and TryPop will return v=nil, ok=True -func (q *SyncQueue) Close() { - q.lock.Lock() - if !q.closed { - q.closed = true - q.popable.Signal() - } - q.lock.Unlock() -} - -func (q *SyncQueue) IsClose() (v bool) { - q.lock.Lock() - v = q.closed - q.lock.Unlock() - return -} - -func (q *SyncQueue) Get(i int) interface{} { - q.lock.Lock() - defer q.lock.Unlock() - return q.buffer.Get(i) -} diff --git a/util/timer/cronexpr.go b/util/timer/cronexpr.go new file mode 100644 index 0000000..694f0b4 --- /dev/null +++ b/util/timer/cronexpr.go @@ -0,0 +1,268 @@ +package timer + +// reference: https://github.com/robfig/cron +import ( + "fmt" + "math" + "strconv" + "strings" + "time" +) + +// Field name | Mandatory? | Allowed values | Allowed special characters +// ---------- | ---------- | -------------- | -------------------------- +// Seconds | No | 0-59 | * / , - +// Minutes | Yes | 0-59 | * / , - +// Hours | Yes | 0-23 | * / , - +// Day of month | Yes | 1-31 | * / , - +// Month | Yes | 1-12 | * / , - +// Day of week | Yes | 0-6 | * / , - +type CronExpr struct { + sec uint64 + min uint64 + hour uint64 + dom uint64 + month uint64 + dow uint64 +} + +// goroutine safe +func NewCronExpr(expr string) (cronExpr *CronExpr, err error) { + fields := strings.Fields(expr) + if len(fields) != 5 && len(fields) != 6 { + err = fmt.Errorf("invalid expr %v: expected 5 or 6 fields, got %v", expr, len(fields)) + return + } + + if len(fields) == 5 { + fields = append([]string{"0"}, fields...) + } + + cronExpr = new(CronExpr) + // Seconds + cronExpr.sec, err = parseCronField(fields[0], 0, 59) + if err != nil { + goto onError + } + // Minutes + cronExpr.min, err = parseCronField(fields[1], 0, 59) + if err != nil { + goto onError + } + // Hours + cronExpr.hour, err = parseCronField(fields[2], 0, 23) + if err != nil { + goto onError + } + // Day of month + cronExpr.dom, err = parseCronField(fields[3], 1, 31) + if err != nil { + goto onError + } + // Month + cronExpr.month, err = parseCronField(fields[4], 1, 12) + if err != nil { + goto onError + } + // Day of week + cronExpr.dow, err = parseCronField(fields[5], 0, 6) + if err != nil { + goto onError + } + return + +onError: + err = fmt.Errorf("invalid expr %v: %v", expr, err) + return +} + +// 1. * +// 2. num +// 3. num-num +// 4. */num +// 5. num/num (means num-max/num) +// 6. num-num/num +func parseCronField(field string, min int, max int) (cronField uint64, err error) { + fields := strings.Split(field, ",") + for _, field := range fields { + rangeAndIncr := strings.Split(field, "/") + if len(rangeAndIncr) > 2 { + err = fmt.Errorf("too many slashes: %v", field) + return + } + + // range + startAndEnd := strings.Split(rangeAndIncr[0], "-") + if len(startAndEnd) > 2 { + err = fmt.Errorf("too many hyphens: %v", rangeAndIncr[0]) + return + } + + var start, end int + if startAndEnd[0] == "*" { + if len(startAndEnd) != 1 { + err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) + return + } + start = min + end = max + } else { + // start + start, err = strconv.Atoi(startAndEnd[0]) + if err != nil { + err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) + return + } + // end + if len(startAndEnd) == 1 { + if len(rangeAndIncr) == 2 { + end = max + } else { + end = start + } + } else { + end, err = strconv.Atoi(startAndEnd[1]) + if err != nil { + err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) + return + } + } + } + + if start > end { + err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) + return + } + if start < min { + err = fmt.Errorf("out of range [%v, %v]: %v", min, max, rangeAndIncr[0]) + return + } + if end > max { + err = fmt.Errorf("out of range [%v, %v]: %v", min, max, rangeAndIncr[0]) + return + } + + // increment + var incr int + if len(rangeAndIncr) == 1 { + incr = 1 + } else { + incr, err = strconv.Atoi(rangeAndIncr[1]) + if err != nil { + err = fmt.Errorf("invalid increment: %v", rangeAndIncr[1]) + return + } + if incr <= 0 { + err = fmt.Errorf("invalid increment: %v", rangeAndIncr[1]) + return + } + } + + // cronField + if incr == 1 { + cronField |= ^(math.MaxUint64 << uint(end+1)) & (math.MaxUint64 << uint(start)) + } else { + for i := start; i <= end; i += incr { + cronField |= 1 << uint(i) + } + } + } + + return +} + +func (e *CronExpr) matchDay(t time.Time) bool { + // day-of-month blank + if e.dom == 0xfffffffe { + return 1< year+1 { + return time.Time{} + } + + // Month + for 1< 0 { + buf := make([]byte, conf.LenStackBuf) + l := runtime.Stack(buf, false) + log.Error("%v: %s", r, buf[:l]) + } else { + log.Error("%v", r) + } + } + }() + */ + + if t.cbex!=nil { + t.cbex(t) + }else{ + t.cb() + } + +} + +func (disp *Dispatcher) AfterFunc(d time.Duration, cb func()) *Timer { + t := new(Timer) + t.cb = cb + t.t = time.AfterFunc(d, func() { + disp.ChanTimer <- t + }) + return t +} + +func (disp *Dispatcher) AfterFuncEx(d time.Duration, cbex func(timer *Timer)) *Timer { + t := new(Timer) + t.cbex = cbex + t.t = time.AfterFunc(d, func() { + disp.ChanTimer <- t + }) + return t +} + +// Cron +type Cron struct { + t *Timer +} + +func (c *Cron) Stop() { + if c.t != nil { + c.t.Stop() + } +} + +func (disp *Dispatcher) CronFunc(cronExpr *CronExpr, _cb func()) *Cron { + c := new(Cron) + + now := time.Now() + nextTime := cronExpr.Next(now) + if nextTime.IsZero() { + return c + } + + // callback + var cb func() + cb = func() { + defer _cb() + + now := time.Now() + nextTime := cronExpr.Next(now) + if nextTime.IsZero() { + return + } + c.t = disp.AfterFunc(nextTime.Sub(now), cb) + } + + c.t = disp.AfterFunc(nextTime.Sub(now), cb) + return c +} + + +func (disp *Dispatcher) CronFuncEx(cronExpr *CronExpr, _cb func(*Cron)) *Cron { + c := new(Cron) + + now := time.Now() + nextTime := cronExpr.Next(now) + if nextTime.IsZero() { + return c + } + + // callback + var cb func() + cb = func() { + defer _cb(c) + + now := time.Now() + nextTime := cronExpr.Next(now) + if nextTime.IsZero() { + return + } + c.t = disp.AfterFunc(nextTime.Sub(now), cb) + } + + c.t = disp.AfterFunc(nextTime.Sub(now), cb) + return c +} \ No newline at end of file diff --git a/util/map.go b/util/umap/map.go similarity index 98% rename from util/map.go rename to util/umap/map.go index 50d7bfc..a6937e6 100644 --- a/util/map.go +++ b/util/umap/map.go @@ -1,8 +1,6 @@ -package util +package umap -import ( - "sync" -) +import "sync" type Map struct { sync.RWMutex diff --git a/util/mapex.go b/util/umap/mapEx.go similarity index 88% rename from util/mapex.go rename to util/umap/mapEx.go index 8c8bfc6..6dc6dc7 100644 --- a/util/mapex.go +++ b/util/umap/mapEx.go @@ -1,10 +1,9 @@ -package util +package umap import ( "fmt" - "github.com/duanhf2012/origin/util/hash" + "github.com/duanhf2012/originnet/util/hash" "sync" - "sync/atomic" ) const ( @@ -16,7 +15,6 @@ type MapEx struct { m []map[interface{}]interface{} l []sync.RWMutex hashMapNum int - rangeIdx uint32 } func (m *MapEx) Init(hashMapNum int) { @@ -37,19 +35,6 @@ func NewMapEx() *MapEx { return &mapEx } - -func (m *MapEx) NextRLockRange(f func(key interface{}, value interface{})) { - i := atomic.AddUint32(&m.rangeIdx,1)%uint32(m.hashMapNum) - - m.l[i].RLock() - for key, val := range m.m[i] { - f(key, val) - } - - m.l[i].RUnlock() -} - - func (m *MapEx) ClearMap() { for i := 0; i < DEFAULT_SAFE_MAP_MAX_HASH_NUM; i++ { m.l[i].Lock() @@ -64,7 +49,7 @@ func (m *MapEx) GetHashCode(key interface{}) int { func (m *MapEx) GetArrayIdByKey(key interface{}) int { if m.hashMapNum ==0 { - return -1 + return -1 } idx := m.GetHashCode(key) % m.hashMapNum if idx > m.hashMapNum { @@ -211,15 +196,9 @@ func (m *MapEx) LockSet(key interface{}, f func(value interface{}) interface{}) ret, ok := val[key] if ok == false { - ret := f(nil) - if ret != nil { - val[key] =ret - } + val[key] = f(nil) } else { - ret := f(ret) - if ret != nil { - val[key] =ret - } + val[key] = f(ret) } m.l[idx].Unlock() diff --git a/util/uuid/uuid.go b/util/uuid/uuid.go index 4666fb3..a4ef28d 100644 --- a/util/uuid/uuid.go +++ b/util/uuid/uuid.go @@ -1,16 +1,3 @@ -// Copyright 2014 mqant Author. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. package uuid import ( @@ -118,4 +105,4 @@ func randBytes(x []byte) { x[length] = byte(mrand.Int31n(256)) } } -} +} \ No newline at end of file