├── .clang-format ├── .clang-format-ignore ├── .github └── workflows │ ├── build.yml │ ├── lint.yml │ └── test.yml ├── .gitignore ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.MD ├── doc ├── README.MD ├── client.MD ├── server.MD └── source-client.MD ├── include └── net │ ├── co.hpp │ ├── endian.hpp │ ├── epoll.hpp │ ├── event.hpp │ ├── execute_context.hpp │ ├── execute_dispatcher.hpp │ ├── iocp.hpp │ ├── load_balance.hpp │ ├── lock.hpp │ ├── nat.hpp │ ├── net.hpp │ ├── net_exception.hpp │ ├── p2p │ ├── msg.hpp │ ├── peer.hpp │ └── tracker.hpp │ ├── proto │ └── msg.proto │ ├── rudp.hpp │ ├── select.hpp │ ├── socket.hpp │ ├── socket_addr.hpp │ ├── socket_buffer.hpp │ ├── tcp.hpp │ ├── third │ └── ikcp.hpp │ ├── thread_pool.hpp │ ├── timer.hpp │ └── udp.hpp ├── lib ├── CMakeLists.txt └── net │ ├── CMakeLists.txt │ ├── co.cc │ ├── epoll.cc │ ├── event.cc │ ├── execute_context.cc │ ├── execute_dispatcher.cc │ ├── iocp.cc │ ├── load_balance.cc │ ├── nat.cc │ ├── net.cc │ ├── p2p │ ├── peer.cc │ └── tracker.cc │ ├── rudp.cc │ ├── select.cc │ ├── socket.cc │ ├── socket_addr.cc │ ├── socket_buffer.cc │ ├── tcp.cc │ ├── third │ └── ikcp.cc │ ├── thread_pool.cc │ ├── timer.cc │ └── udp.cc ├── src ├── CMakeLists.txt ├── client │ ├── CMakeLists.txt │ ├── main-window.cc │ ├── main-window.hpp │ ├── main-window.ui │ ├── main.cc │ ├── peer.cc │ ├── peer.hpp │ ├── yuvwidget.cc │ └── yuvwidget.hpp ├── edge-server │ ├── CMakeLists.txt │ └── main.cc ├── source-client │ ├── CMakeLists.txt │ ├── main-window.cc │ ├── main-window.hpp │ ├── main-window.ui │ ├── main.cc │ ├── peer.cc │ ├── peer.hpp │ ├── yuvwidget.cc │ └── yuvwidget.hpp └── tracker-server │ ├── CMakeLists.txt │ └── main.cc └── test ├── CMakeLists.txt └── net ├── CMakeLists.txt ├── endian.cc ├── main.cc ├── peer.cc ├── rudp.cc ├── tcp.cc ├── thread_pool.cc ├── timer.cc └── udp.cc /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: MultiLine 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: false 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: true 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Custom 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: true 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 120 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: true 73 | IndentPPDirectives: None 74 | IndentWidth: 4 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: None 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Right 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: false 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 4 119 | UseTab: Never 120 | ... 121 | 122 | -------------------------------------------------------------------------------- /.clang-format-ignore: -------------------------------------------------------------------------------- 1 | ./lib/net/third 2 | ./include/net/third 3 | ./build -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | - '*.yml' 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | container: archlinux:latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: install deps 16 | run: | 17 | pacman -Syu --noconfirm 18 | pacman -S --noconfirm make cmake gcc ffmpeg qt5-base gtest boost git gflags protobuf openssh 19 | git clone git@github.com:google/glog.git 20 | cd glog 21 | cmake -H. -Bbuild -G "Unix Makefiles" 22 | cmake --build build 23 | cmake --build build --target install 24 | - name: build 25 | run: | 26 | mkdir build 27 | cd build 28 | cmake .. 29 | make -j 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | - '*.yml' 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: DoozyX/clang-format-lint-action@v0.5 15 | with: 16 | source: '.' 17 | extensions: 'hpp,cc,h,c,cpp' 18 | clangFormatVersion: 9 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | - '*.yml' 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | container: archlinux:latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: install deps 16 | run: | 17 | pacman -Syu --noconfirm 18 | pacman -S --noconfirm make cmake gcc ffmpeg qt5-base gtest boost git gflags protobuf openssh 19 | git clone git@github.com:google/glog.git 20 | cd glog 21 | cmake -H. -Bbuild -G "Unix Makefiles" 22 | cmake --build build 23 | cmake --build build --target install 24 | - name: build 25 | run: | 26 | mkdir build 27 | cd build 28 | cmake .. 29 | make -j 30 | - name: test 31 | run: | 32 | cd build/bin 33 | ./test-net 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> C++ 2 | # Compiled Object files 3 | *.slo 4 | *.lo 5 | *.o 6 | *.obj 7 | 8 | # Precompiled Headers 9 | *.gch 10 | *.pch 11 | 12 | # Compiled Dynamic libraries 13 | *.so 14 | *.dylib 15 | *.dll 16 | 17 | # Fortran module files 18 | *.mod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | 32 | # C extensions 33 | *.so 34 | 35 | *.log 36 | 37 | 38 | # ---> C 39 | # Object files 40 | *.o 41 | *.ko 42 | *.obj 43 | *.elf 44 | 45 | # Precompiled Headers 46 | *.gch 47 | *.pch 48 | 49 | # Libraries 50 | *.lib 51 | *.a 52 | *.la 53 | *.lo 54 | 55 | # Shared objects (inc. Windows DLLs) 56 | *.dll 57 | *.so 58 | *.so.* 59 | *.dylib 60 | 61 | # Executables 62 | *.exe 63 | *.out 64 | *.app 65 | *.i*86 66 | *.x86_64 67 | *.hex 68 | 69 | # Debug files 70 | *.dSYM/ 71 | build/ 72 | .vscode/ 73 | #qt 74 | CMakeLists.txt.user 75 | *.autosave -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.18) 2 | 3 | project (P2P-Live C CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | option(USE_CLANG "build with clang" OFF) 10 | option(MMDBG "memory debug" OFF) 11 | if (WIN32) 12 | add_definitions(-DOS_WINDOWS) 13 | endif (WIN32) 14 | if(MSVC) 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") 16 | endif(MSVC) 17 | 18 | set(CMAKE_BINARY_DIR ${PROJECT_SOURCE_DIR}/build) 19 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 20 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 21 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 22 | set(DEBUG_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/debug) 23 | set(CMAKE_MODULE_PATH $CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/Modules/") 24 | add_definitions(-DGLOG_NO_ABBREVIATED_SEVERITIES) 25 | 26 | add_definitions(-D__STDC_CONSTANT_MACROS) 27 | 28 | include_directories("include/") 29 | 30 | find_path( AVCODEC_INCLUDE_DIR libavcodec/avcodec.h ) 31 | find_library( AVCODEC_LIBRARY avcodec ) 32 | 33 | find_path( AVDEVICE_INCLUDE_DIR libavdevice/avdevice.h ) 34 | find_library( AVDEVICE_LIBRARY avdevice) 35 | 36 | find_path( AVFILTER_INCLUDE_DIR libavfilter/avfilter.h ) 37 | find_library( AVFILTER_LIBRARY avfilter ) 38 | 39 | find_path( AVFORMAT_INCLUDE_DIR libavformat/avformat.h ) 40 | find_library( AVFORMAT_LIBRARY avformat ) 41 | 42 | find_path( AVUTIL_INCLUDE_DIR libavutil/time.h ) 43 | find_library( AVUTIL_LIBRARY avutil ) 44 | 45 | find_path( SWRESAMPLE_INCLUDE_DIR libswresample/swresample.h) 46 | find_library( SWRESAMPLE_LIBRARY swresample ) 47 | 48 | find_path( SWSCALE_INCLUDE_DIR libswscale/swscale.h) 49 | find_library( SWSCALE_LIBRARY swscale ) 50 | 51 | set (FFMPEG_INCLUDE_DIR ${AVCODEC_INCLUDE_DIR}) 52 | if (MSVC) 53 | set (FFMPEG_LIBRARY ${AVFORMAT_LIBRARY} ${AVDEVICE_LIBRARY} ${AVCODEC_LIBRARY} ${SWRESAMPLE_LIBRARY} ${SWSCALE_LIBRARY} ${AVFILTER_LIBRARY} ${AVUTIL_LIBRARY}) 54 | else() 55 | set (FFMPEG_LIBRARY ${AVFORMAT_LIBRARY} ${AVDEVICE_LIBRARY} ${AVCODEC_LIBRARY} ${SWRESAMPLE_LIBRARY} ${SWSCALE_LIBRARY} ${AVFILTER_LIBRARY} ${AVUTIL_LIBRARY} 56 | va m z) 57 | endif(MSVC) 58 | 59 | find_package(gflags NO_MODULE REQUIRED) 60 | find_package(Threads REQUIRED) 61 | include_directories(${gflags_INCLUDE_DIR}) 62 | 63 | 64 | if (MMDBG) 65 | set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") 66 | set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") 67 | endif(MMDBG) 68 | 69 | if (USE_CLANG) 70 | SET (CMAKE_C_COMPILER "/usr/bin/clang") 71 | SET (CMAKE_CXX_COMPILER "/usr/bin/clang++") 72 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument -Wno-unknown-warning-option -Wno-unused-const-variable") 73 | SET (CMAKE_LINKER "/usr/bin/llvm-link") 74 | else() 75 | SET (CMAKE_C_COMPILER "/usr/bin/gcc") 76 | SET (CMAKE_CXX_COMPILER "/usr/bin/g++") 77 | SET (CMAKE_LINKER "/usr/bin/ld") 78 | endif(USE_CLANG) 79 | 80 | 81 | add_subdirectory(src) 82 | add_subdirectory(lib) 83 | add_subdirectory(test) 84 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM archlinux:latest 2 | RUN pacman -Sy --noconfirm \ 3 | && pacman -S --noconfirm make cmake gcc ffmpeg qt5-base google-glog gtest boost git gflags crypto++ sdl2 4 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # A P2P-based online live broadcast system 2 | ![GPL-3.0](https://img.shields.io/badge/License-GPL-green) 3 | ![status](https://img.shields.io/badge/status-development-blue) 4 | ![Build:master](https://github.com/kadds/P2P-Live/workflows/Build/badge.svg) 5 | ![Test:master](https://github.com/kadds/P2P-Live/workflows/Test/badge.svg) 6 | ![Lint:master](https://github.com/kadds/P2P-Live/workflows/Lint/badge.svg) 7 | 8 | ## Projects 9 | * **client** *src/client* 10 | The peer to peer endpoint. Client can pull video-stream from peer to peer network with multi-channels. 11 | * **source-client** *src/source-client* 12 | The live source client application. Based on QT5&SDL2. Pushing video-stream to edge-server. 13 | * **edge-server** *src/edge-server* 14 | The main functions of it are pull stream from the source client, dispatch live stream to peers, etc. 15 | * **tracker-server** *src/tracker-server* 16 | The main functions of it are peer to peer network control, [**UDP hole punching**](#UDP-hole-punching), etc. 17 | * **libnet** *lib/net* 18 | libnet is a network library that uses **IO multiplexing** ( *select/epoll* ) and no-blocked IO, while using [**coroutines**](#Coroutines) for each connection to improve IO response. Including coroutines, [thread pool](#Thread-pool), timer, tcp/udp encapsulation, peer to peer network sending/receiving, tracker nodes exchanging, reliable udp make by KCP, hole punching. etc... 19 | 20 | ## Building 21 | Setting up development environment with docker (optional): 22 | ```Bash 23 | # build image 24 | docker build -t p2p-live . 25 | # run docker container 26 | docker run -i -t p2p-live /usr/bin/bash 27 | cd root 28 | git clone /url/to/repo 29 | ``` 30 | 31 | Building: 32 | ```Bash 33 | mkdir P2P-Live/build && cd P2P-Live/build 34 | # set library path like -DCMAKE_PREFIX_PATH=/usr/local/lib 35 | cmake .. 36 | make -j 37 | ``` 38 | 39 | ## Testing 40 | ```Bash 41 | ./build/bin/test-net 42 | ``` 43 | 44 | ## Detailed Design 45 | ### UDP hole punching 46 | > NAT types 47 | > * NAT1 48 | > Full Cone NAT 49 | > * NAT2 50 | > Address-Restricted Cone NAT 51 | > * NAT3 52 | > Port-Restricted Cone NAT 53 | > * NAT4 54 | > Symmetric NAT 55 | 56 | If PeerA wants to connect to PeerB: 57 | 58 | | Name | Address | Mark | 59 | | :------ | :---------- | :-------------------------------------------------: | 60 | | ServerT | ipS:portS | RUDP listens connection request and detect NAT port | 61 | | PeerA | ipA:portA | PeerA local address | 62 | | PeerB | ipB:portB | PeerB local address | 63 | | NATA | ipNA:portNA | | 64 | | NATB | ipNB:portNB | | 65 | 66 | 67 | > 1. PeerA sends a connection request to ServerT(ipS:portS) through RUDP. 68 | > 2. ServerT obtains NAT address (ipNA:portNA) of PeerA and forwards the request to PeerB through TCP long connection. 69 | > 3. PeerB attempts to connect to PeerA(ipNA:portNA) directly. If failed. PeerB sends a connection request to ServerT through RUDP. 70 | > 4. ServerT get PeerB's NAT address (ipNB:portNB) and transport the request to PeerA. 71 | > 5. PeerA attempts to connect to PeerB (ipNB:portNB). 72 | > 6. Once PeerA and PeerB send to each other, The hole punching is completed. 73 | 74 | This method works only NATA and NATB is not Symmetric NAT. 75 | 76 | But When NATA or NATB is Symmetric NAT? 77 | > Using port guessing may succeed but isn't stable. We don't process this condition. 78 | 79 | If NTAA and NATB both symmetric NAT, We don't process it and just connect to edge server to get data. 80 | 81 | ### Coroutines 82 | It is a stackfull coroutine switch by Boost.Context. 83 | Coroutine uses: 84 | 1. TCP 85 | When TCP acceptor accepts a new TCP client, a coroutine is built to process it. Coroutines are randomly assigned to the event loop for load balancing. 86 | 2. UDP 87 | Not supports multi-coroutines, only one coroutine is used for sending and writing. 88 | 3. RUDP 89 | Reliable UDP is built by KCP(ARQ) from UDP. A coroutine is built when tell RUDP to establish a new connection. Each connection has a send/recv queue and does not block each other. 90 | 91 | How coroutines are scheduled? 92 | The scheduler has a dispatch queue and dispatches via FIFO. No need for cross-thread scheduling. 93 | 94 | ### Thread pool 95 | Put task to queue. Pop it up to run. 96 | 97 | ## Third-party 98 | Ensure that the following packages are installed: 99 | * [Boost.Context](https://www.boost.org/doc/libs/1_72_0/libs/context/doc/html/index.html) 100 | * [QT5](https://www.qt.io/) 101 | * [GTest](https://github.com/google/googletest) 102 | * [GLog](https://github.com/google/glog) 103 | * [FFmpeg](https://ffmpeg.org) 104 | * [gflags](https://github.com/gflags/gflags) 105 | 106 | ## Maintainers 107 | [@Kadds](https://github.com/Kadds). 108 | [@YShaw99](https://github.com/YShaw99). 109 | 110 | ## License 111 | [GPL-3.0](./LICENSE) © Kadds 112 | 113 | -------------------------------------------------------------------------------- /doc/README.MD: -------------------------------------------------------------------------------- 1 | Building ... -------------------------------------------------------------------------------- /doc/client.MD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadds/P2P-Live/390c8674b973fa02da29b29c5318c8fff7bbbde6/doc/client.MD -------------------------------------------------------------------------------- /doc/server.MD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadds/P2P-Live/390c8674b973fa02da29b29c5318c8fff7bbbde6/doc/server.MD -------------------------------------------------------------------------------- /doc/source-client.MD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadds/P2P-Live/390c8674b973fa02da29b29c5318c8fff7bbbde6/doc/source-client.MD -------------------------------------------------------------------------------- /include/net/endian.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file endian.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief automatic end-to-end conversion on struct。 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "net.hpp" 23 | #include "socket_buffer.hpp" 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace net::serialization 30 | { 31 | template struct typelist_t; 32 | 33 | /// the type list save members in struct. 34 | template struct typelist_t 35 | { 36 | constexpr static bool has_next = false; 37 | using Type = Head; 38 | }; 39 | 40 | // Variadic specialization 41 | template struct typelist_t 42 | { 43 | constexpr static bool has_next = true; 44 | /// next typelist 45 | using Next = typelist_t; 46 | using Type = Head; 47 | }; 48 | 49 | } // namespace net::serialization 50 | 51 | namespace net::endian 52 | { 53 | 54 | ///\note GNU extension 55 | ///\note using c++20 std::endian when enable c++20 56 | constexpr inline bool little_endian() 57 | { 58 | #ifndef __GNUC__ 59 | return true; 60 | #else 61 | return __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__; 62 | #endif 63 | } 64 | 65 | template void cast_struct(T &val); 66 | 67 | template void cast_array(T (&val)[N]); 68 | 69 | /// cast struct to network endian (big endian). 70 | /// do nothing at big endian architecture. 71 | template inline void cast(T &val) 72 | { 73 | if constexpr (little_endian()) 74 | { 75 | if constexpr (std::is_array_v) 76 | { 77 | cast_array(val); 78 | } 79 | else 80 | { 81 | cast_struct(val); 82 | } 83 | } 84 | } 85 | 86 | /// not cast needed 87 | template <> inline void cast(i8 &val) {} 88 | template <> inline void cast(u8 &val) {} 89 | 90 | /// swap bit 0-7 and 8-15 91 | template <> inline void cast(u16 &val) 92 | { 93 | if constexpr (little_endian()) 94 | { 95 | auto v = val; 96 | val = ((v & 0xFF) << 8) | ((v >> 8) & 0xFF); 97 | } 98 | } 99 | 100 | template <> inline void cast(i16 &val) { cast(*(u16 *)&val); } 101 | 102 | template <> inline void cast(u32 &val) 103 | { 104 | if constexpr (little_endian()) 105 | { 106 | auto v = val; 107 | val = ((v & 0xFF) << 24) | (((v >> 8) & 0xFF) << 16) | (((v >> 16) & 0xFF) << 8) | ((v >> 24) & 0xFF); 108 | } 109 | } 110 | 111 | template <> inline void cast(i32 &val) { cast(*(u32 *)&val); } 112 | 113 | template <> inline void cast(u64 &val) 114 | { 115 | if constexpr (little_endian()) 116 | { 117 | auto v = val; 118 | val = ((v & 0xFF) << 56) | (((v >> 8) & 0xFF) << 48) | (((v >> 16) & 0xFF) << 40) | (((v >> 24) & 0xFF) << 32) | 119 | (((v >> 32) & 0xFF) << 24) | (((v >> 40) & 0xFF) << 16) | (((v >> 48) & 0xFF) << 8) | ((v >> 56) & 0xFF); 120 | } 121 | } 122 | 123 | template <> inline void cast(i64 &val) { cast(*(i64 *)&val); } 124 | 125 | /// specialization of arrays 126 | template inline void cast_array(T (&val)[N]) 127 | { 128 | for (size_t i = 0; i < N; i++) 129 | { 130 | cast(val[i]); 131 | } 132 | } 133 | 134 | template inline void cast_struct_impl(void *val) 135 | { 136 | cast(*(typename Typelist::Type *)val); 137 | if constexpr (Typelist::has_next) 138 | { 139 | cast_struct_impl(((char *)val) + sizeof(typename Typelist::Type)); 140 | } 141 | } 142 | 143 | template inline void cast_struct(T &val) 144 | { 145 | static_assert(std::is_pod_v && !std::is_union_v, "struct should be a POD type."); 146 | using Typelist = typename T::member_list_t; 147 | cast(*(typename Typelist::Type *)&val); 148 | 149 | if constexpr (Typelist::has_next) 150 | { 151 | cast_struct_impl(((char *)&val) + sizeof(typename Typelist::Type)); 152 | } 153 | } 154 | 155 | /// get struct from buffer 156 | ///\param buffer the source data 157 | ///\param val the target struct to save data after cast 158 | template inline bool cast_to(socket_buffer_t &buffer, T &val) 159 | { 160 | auto ptr_from = buffer.get(); 161 | if (buffer.get_length() < sizeof(val)) 162 | return false; 163 | memcpy(&val, ptr_from, sizeof(val)); 164 | cast(val); 165 | return true; 166 | } 167 | 168 | /// save struct to buffer 169 | ///\param val the source struct 170 | ///\param buffer the target buffer to save 171 | ///\note the original struct is modified 172 | template inline bool save_to(T &val, socket_buffer_t &buffer) 173 | { 174 | auto ptr = buffer.get(); 175 | if (buffer.get_length() < sizeof(val)) 176 | return false; 177 | cast(val); 178 | memcpy(ptr, &val, sizeof(val)); 179 | return true; 180 | } 181 | 182 | /// cast struct inplace, buffer and struct addresses must be the same 183 | ///\param val the struct to cast 184 | ///\param buffer the origin buffer which sames as struct 'val' 185 | template inline bool cast_inplace(T &val, socket_buffer_t &buffer) 186 | { 187 | assert((byte *)&val == buffer.get()); 188 | if (buffer.get_length() < sizeof(val)) 189 | return false; 190 | cast(val); 191 | return true; 192 | } 193 | } // namespace net::endian 194 | -------------------------------------------------------------------------------- /include/net/epoll.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file epoll.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief epoll demultiplexer implementation 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | 22 | #pragma once 23 | #ifndef OS_WINDOWS 24 | #include "event.hpp" 25 | #include 26 | 27 | namespace net 28 | { 29 | class event_epoll_demultiplexer : public event_demultiplexer 30 | { 31 | int fd; 32 | int ev_fd; 33 | 34 | public: 35 | event_epoll_demultiplexer(); 36 | ~event_epoll_demultiplexer(); 37 | void add(handle_t handle, event_type_t type) override; 38 | handle_t select(event_type_t *type, microsecond_t *timeout) override; 39 | void remove(handle_t handle, event_type_t type) override; 40 | void wake_up(event_loop_t &cur_loop) override; 41 | }; 42 | } // namespace net 43 | #endif 44 | -------------------------------------------------------------------------------- /include/net/event.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file event.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Includes event demultiplexer interface, event handler interface, event loop and event context. 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | 22 | #pragma once 23 | #include "execute_dispatcher.hpp" 24 | #include "lock.hpp" 25 | #include "net.hpp" 26 | #include "timer.hpp" 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace net 34 | { 35 | 36 | using event_type_t = unsigned long; 37 | 38 | /// different handle types on different platforms 39 | using handle_t = long long; 40 | 41 | namespace event_type 42 | { 43 | enum : event_type_t 44 | { 45 | readable = 1, 46 | writable = 2, 47 | error = 4, 48 | def = 8, 49 | }; 50 | }; 51 | /// forward declaration 52 | class socket_t; 53 | class event_context_t; 54 | class event_loop_t; 55 | class execute_context_t; 56 | 57 | enum event_strategy 58 | { 59 | select, 60 | epoll, 61 | /// TODO: Encapsulation of IOCP 62 | IOCP, 63 | AUTO, 64 | }; 65 | 66 | /// Demultiplexer 67 | /// Wait and select an available event, while specifying the wait timeout 68 | ///\note Different implementations may have different thread safety for different platforms, please do not always 69 | /// consider it to be thread safe. 70 | class event_demultiplexer 71 | { 72 | public: 73 | /// register event on handle 74 | /// 75 | ///\param handle the socket handle to listen 76 | ///\param type which event to listen. readable, writable, error. 77 | /// 78 | ///\note adding an already added event will not affect 79 | virtual void add(handle_t handle, event_type_t type) = 0; 80 | 81 | /// listen and return a socket handle which happends event 82 | /// 83 | ///\param type a pointer to event_type. return socket event type 84 | ///\param timeout maximum time to wait. If an error occurs, the parameter is set to 0 85 | ///\return socket happends event, return 0 for error, just recall it 86 | virtual handle_t select(event_type_t *type, microsecond_t *timeout) = 0; 87 | 88 | /// unregister event on handle 89 | /// 90 | ///\param socket handle 91 | ///\param type unregister event type. readable, writable, error. 92 | /// 93 | virtual void remove(handle_t handle, event_type_t type) = 0; 94 | 95 | virtual void wake_up(event_loop_t &cur_loop) = 0; 96 | 97 | virtual ~event_demultiplexer(){}; 98 | }; 99 | 100 | class event_handler_t 101 | { 102 | public: 103 | /// handle event 104 | ///\param context event context 105 | ///\param event_type which event type is distributed. 106 | virtual void on_event(event_context_t &, event_type_t) = 0; 107 | virtual ~event_handler_t(){}; 108 | }; 109 | 110 | /// event loop 111 | /// a loop per thread 112 | /// all event is generate by demultiplexer. it just fetch events and distribute event to event handler and run into 113 | /// event coroutines. 114 | ///\note this class should be created by event context, don't create it by yourself. 115 | class event_loop_t 116 | { 117 | private: 118 | using event_handle_map_t = std::unordered_map; 119 | friend class event_context_t; 120 | friend class event_fd_handler_t; 121 | friend class event_apc_handler_t; 122 | 123 | bool is_exit; 124 | int exit_code; 125 | 126 | event_demultiplexer *demuxer; 127 | /// map handle -> event handler 128 | event_handle_map_t event_map; 129 | /// lock the event map 130 | lock::spinlock_t lock; 131 | 132 | event_context_t *context; 133 | std::unique_ptr time_manager; 134 | 135 | execute_thread_dispatcher_t dispatcher; 136 | bool has_wake_up; 137 | 138 | #ifndef OS_WINDOWS 139 | #else 140 | HANDLE handle; 141 | 142 | public: 143 | HANDLE get_handle() { return handle; } 144 | 145 | private: 146 | #endif 147 | 148 | private: 149 | void add_socket(socket_t *socket_t); 150 | void remove_socket(socket_t *socket_t); 151 | void set_demuxer(event_demultiplexer *demuxer); 152 | void set_context(event_context_t *context) { this->context = context; } 153 | 154 | event_demultiplexer *get_demuxer() const { return demuxer; } 155 | 156 | private: 157 | /// run loop util call exit 158 | int run(); 159 | 160 | public: 161 | event_loop_t(microsecond_t precision); 162 | ~event_loop_t(); 163 | 164 | event_loop_t(const event_loop_t &) = delete; 165 | event_loop_t &operator=(const event_loop_t &) = delete; 166 | 167 | /// exit event loop with code. 168 | void exit(int code); 169 | /// get workload 170 | int load_factor(); 171 | 172 | /// register event 'type' on 'handle', call it repeatedly is allowed 173 | event_loop_t &link(handle_t handle, event_type_t type); 174 | /// unregister event 'type' on 'handle' 175 | event_loop_t &unlink(handle_t handle, event_type_t type); 176 | 177 | /// map handle -> handler 178 | /// thread-safety 179 | void add_event_handler(handle_t handle, event_handler_t *handler); 180 | void remove_event_handler(handle_t handle, event_handler_t *handler); 181 | 182 | timer_registered_t add_timer(timer_t timer); 183 | void remove_timer(timer_registered_t); 184 | 185 | execute_thread_dispatcher_t &get_dispatcher(); 186 | 187 | /// get event loop current thread. 188 | ///\note don't call it before run event_context in current thread. 189 | static event_loop_t ¤t(); 190 | 191 | /// wake up if event loop is sleeping. 192 | void wake_up(); 193 | 194 | event_context_t &get_context() { return *context; } 195 | }; 196 | 197 | /// event context 198 | /// unique global context in an application 199 | class event_context_t 200 | { 201 | /// demultiplexing strategy 202 | event_strategy strategy; 203 | 204 | std::shared_mutex loop_mutex; 205 | std::vector loops; 206 | 207 | /// sync when exit loops 208 | std::mutex exit_mutex; 209 | std::condition_variable cond; 210 | std::atomic_int loop_counter; 211 | 212 | /// timer precistion 213 | microsecond_t precision; 214 | bool exit; 215 | 216 | /// init event loop in current thread 217 | void do_init(); 218 | #ifdef OS_WINDOWS 219 | 220 | handle_t iocp_handle; 221 | #endif 222 | 223 | public: 224 | event_context_t(event_strategy strategy, microsecond_t precision = timer_min_precision); 225 | /// destroy all loops 226 | ///\note Wait for all loops to be destroyed and return 227 | ~event_context_t(); 228 | 229 | void add_executor(execute_context_t *exectx); 230 | void add_executor(execute_context_t *exectx, event_loop_t *loop); 231 | void remove_executor(execute_context_t *exectx); 232 | 233 | /// select a event loop which is minimum work load 234 | event_loop_t &select_loop(); 235 | 236 | /// Inform exit all event loop with exit code 237 | ///\note return immediately 238 | void exit_all(int code); 239 | /// run event loop in current thread 240 | ///\note called for each thread to run the event loop in each thread. 241 | int run(); 242 | 243 | int prepare(); 244 | 245 | event_strategy get_strategy() { return strategy; } 246 | }; 247 | 248 | } // namespace net -------------------------------------------------------------------------------- /include/net/execute_context.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file execute_context.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief The execution context is a CPU execution context, including independent stack and register information. It 5 | should be bound to a random event loop and always run in the event loop. 6 | * \version 0.1 7 | * \date 2020-03-13 8 | * 9 | * @copyright Copyright (c) 2020. 10 | This file is part of P2P-Live. 11 | 12 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 13 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 14 | 15 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 16 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with P2P-Live. If not, see . 20 | * 21 | */ 22 | #pragma once 23 | #include "timer.hpp" 24 | 25 | namespace net 26 | { 27 | namespace co 28 | { 29 | class coroutine_t; 30 | } // namespace co 31 | 32 | class execute_thread_dispatcher_t; 33 | class event_loop_t; 34 | 35 | class execute_context_t 36 | { 37 | co::coroutine_t *co; 38 | event_loop_t *loop; 39 | friend class event_context_t; 40 | friend class execute_thread_dispatcher_t; 41 | timer_registered_t timer; 42 | 43 | public: 44 | /// this sleep can be interrupt by event. Check return value 45 | /// 46 | ///\param ms sleep time in microsecond 47 | ///\return return how much time we sleep, The return value may be smaller than the parameter when an event occurs 48 | /// during sleep 49 | microsecond_t sleep(microsecond_t ms); 50 | void stop(); 51 | 52 | void stop_for(microsecond_t ms, std::function func); 53 | void stop_for(microsecond_t ms); 54 | 55 | event_loop_t *get_loop() const { return loop; } 56 | void set_loop(event_loop_t *loop) { this->loop = loop; } 57 | 58 | /// Rerun the coroutine and push it to the dispatcher queue 59 | void start(); 60 | /// Rerun the coroutine and push it to the dispatcher queue. Call func before resume coroutine. 61 | void start_with(std::function func); 62 | 63 | /// start coroutine and set function. Push it to dispatcher queue 64 | /// 65 | ///\param func the startup function to run. 66 | void run(std::function func); 67 | 68 | /// wake up loop to execute coroutine 69 | void wake_up_thread(); 70 | 71 | execute_context_t(); 72 | ~execute_context_t(); 73 | }; 74 | 75 | } // namespace net 76 | -------------------------------------------------------------------------------- /include/net/execute_dispatcher.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file execute_dispatcher.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief coroutine dispatcher via FIFO 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "lock.hpp" 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace net 29 | { 30 | class execute_context_t; 31 | 32 | class execute_thread_dispatcher_t 33 | { 34 | std::queue>> co_wait_for_resume; 35 | std::unordered_set cancel_contexts; 36 | lock::spinlock_t lock; 37 | /// lock var cancel_contexts 38 | lock::spinlock_t cancel_lock; 39 | 40 | public: 41 | ///\note Must be called by the event loop to execute the execute context in the queue. 42 | ///\note This function is called automatically in event loop. 43 | ///\note Not thread-safe 44 | void dispatch(); 45 | 46 | /// Cancel an execute context 47 | /// Thread-safe 48 | void cancel(execute_context_t *econtext); 49 | 50 | /// Add an execute context to the queue and set the wakeup function to execute 51 | /// Thread-safe 52 | void add(execute_context_t *econtext, std::function func); 53 | }; 54 | } // namespace net 55 | -------------------------------------------------------------------------------- /include/net/iocp.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file iocp.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief iocp demultiplexer implementation 5 | * \version 0.1 6 | * \date 2020-08-22 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | 22 | #pragma once 23 | #include "event.hpp" 24 | #include "net.hpp" 25 | #ifdef OS_WINDOWS 26 | 27 | namespace net 28 | { 29 | 30 | enum class io_type 31 | { 32 | read, 33 | write, 34 | accept, 35 | wake_up, 36 | connect, 37 | }; 38 | 39 | struct io_overlapped 40 | { 41 | WSAOVERLAPPED overlapped; 42 | WSABUF wsaBuf; 43 | unsigned int buffer_do_len; 44 | sockaddr_in addr; 45 | int addr_len; 46 | int err; 47 | HANDLE sock; 48 | io_type type; 49 | void *data; 50 | bool done; 51 | io_overlapped(); 52 | }; 53 | 54 | class event_iocp_demultiplexer : public event_demultiplexer 55 | { 56 | handle_t iocp_handle; 57 | std::unordered_map map; 58 | 59 | public: 60 | static handle_t make(); 61 | static void close(handle_t h); 62 | 63 | event_iocp_demultiplexer(handle_t); 64 | ~event_iocp_demultiplexer(); 65 | void add(handle_t handle, event_type_t type) override; 66 | handle_t select(event_type_t *type, microsecond_t *timeout) override; 67 | void remove(handle_t handle, event_type_t type) override; 68 | void wake_up(event_loop_t &cur_loop) override; 69 | }; 70 | } // namespace net 71 | #endif -------------------------------------------------------------------------------- /include/net/load_balance.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file load_balance.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Four-tier load balancing server implementation 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "endian.hpp" 23 | #include "net.hpp" 24 | #include "tcp.hpp" 25 | #include "timer.hpp" 26 | #include 27 | #include 28 | 29 | /// TODO: load balancing server, not finish yet. Four level load balancing. 30 | namespace net 31 | { 32 | #pragma pack(push, 1) 33 | 34 | struct peer_get_server_request_t 35 | { 36 | u16 version; 37 | u32 room_id; 38 | u32 key; 39 | using member_list_t = net::serialization::typelist_t; 40 | }; 41 | 42 | struct peer_get_server_respond_t 43 | { 44 | u16 version; 45 | u16 port; 46 | u32 ip_addr; 47 | u32 session_id; 48 | u8 state; 49 | using member_list_t = net::serialization::typelist_t; 50 | }; 51 | 52 | struct pull_server_request_common_t 53 | { 54 | u16 type; 55 | using member_list_t = net::serialization::typelist_t; 56 | }; 57 | 58 | struct pull_inner_server_respond_t 59 | { 60 | u32 connect_count; 61 | using member_list_t = net::serialization::typelist_t; 62 | }; 63 | 64 | #pragma pack(pop) 65 | namespace load_balance 66 | { 67 | 68 | /// load balance server 69 | /// context server connect it that sending server work load, address 70 | /// peer client connect it to get the context server address 71 | class front_server_t 72 | { 73 | public: 74 | using server_join_handler_t = std::function; 75 | using front_handler_t = std::function; 77 | 78 | private: 79 | front_handler_t handler; 80 | server_join_handler_t server_handler; 81 | tcp::server_t server, inner_server; 82 | 83 | public: 84 | /// bind tcp acceptor. recvice inner server data. 85 | /// 86 | void bind_inner(event_context_t &context, socket_addr_t addr, bool reuse_addr = false); 87 | 88 | void bind(event_context_t &context, socket_addr_t addr, bool reuse_addr = false); 89 | front_server_t &on_client_request(front_handler_t handler); 90 | front_server_t &on_inner_server_join(server_join_handler_t handler); 91 | }; 92 | 93 | class front_client_t 94 | { 95 | tcp::client_t client; 96 | }; 97 | 98 | } // namespace load_balance 99 | } // namespace net 100 | -------------------------------------------------------------------------------- /include/net/lock.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lock.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Implementation of spin locks and read-write locks 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | 22 | #pragma once 23 | #include 24 | 25 | namespace net::lock 26 | { 27 | class spinlock_t 28 | { 29 | std::atomic_bool flag; 30 | 31 | public: 32 | spinlock_t() 33 | : flag(false) 34 | { 35 | } 36 | ~spinlock_t() {} 37 | 38 | void lock() 39 | { 40 | bool old = false; 41 | do 42 | { 43 | while (flag) 44 | { 45 | /// XXX: sleep cpu here 46 | } 47 | old = false; 48 | } while (!flag.compare_exchange_strong(old, true, std::memory_order_acquire)); 49 | } 50 | 51 | void unlock() { flag.store(false, std::memory_order_release); } 52 | }; 53 | 54 | class rw_lock_t 55 | { 56 | std::atomic_ullong flag; 57 | 58 | public: 59 | rw_lock_t() 60 | : flag(0) 61 | { 62 | } 63 | //. read lock 64 | void rlock() 65 | { 66 | unsigned long long old; 67 | do 68 | { 69 | while (flag & (1ull << 63)) /// is writing 70 | { 71 | } 72 | 73 | old = flag & ((1ull << 63) - 1); 74 | } while (!flag.compare_exchange_strong(old, old + 1, std::memory_order_acquire)); 75 | } 76 | /// read unlock 77 | void runlock() 78 | { 79 | unsigned long long old; 80 | do 81 | { 82 | old = flag & ((1ull << 63) - 1); 83 | } while (!flag.compare_exchange_strong(old, old - 1, std::memory_order_acquire)); 84 | } 85 | /// write lock 86 | void lock() 87 | { 88 | unsigned long long old; 89 | do 90 | { 91 | while (flag != 0) /// is reading or writing 92 | { 93 | } 94 | 95 | old = 0; 96 | } while (!flag.compare_exchange_strong(old, (1ull << 63), std::memory_order_acquire)); 97 | } 98 | /// write unlock 99 | void unlock() { flag.store(0, std::memory_order_release); } 100 | }; 101 | 102 | template struct shared_lock_guard 103 | { 104 | T &ref; 105 | shared_lock_guard(T &ref) 106 | : ref(ref) 107 | { 108 | ref.rlock(); 109 | } 110 | ~shared_lock_guard() { ref.runlock(); } 111 | }; 112 | 113 | template struct lock_guard 114 | { 115 | T &ref; 116 | lock_guard(T &ref) 117 | : ref(ref) 118 | { 119 | ref.lock(); 120 | } 121 | ~lock_guard() { ref.unlock(); } 122 | }; 123 | 124 | } // namespace net::lock -------------------------------------------------------------------------------- /include/net/nat.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file nat.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief NAT detector. Abandoned 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "endian.hpp" 23 | #include "rudp.hpp" 24 | #include "tcp.hpp" 25 | #include 26 | 27 | namespace net 28 | { 29 | 30 | /// TODO: detect NAT type 31 | ///\note: Deliver messages directly from tracker, without detecting NAT type 32 | 33 | enum class nat_type : u8 34 | { 35 | unknown, 36 | none, 37 | full_cone, 38 | ip_cone, 39 | port_cone, 40 | symmetric, 41 | }; 42 | 43 | #pragma pack(push, 1) 44 | // --------------------- network structs 45 | struct nat_request_t 46 | { 47 | u16 port; 48 | u16 udp_port; 49 | u32 ip; 50 | u32 key; 51 | using member_list_t = serialization::typelist_t; 52 | }; 53 | 54 | struct nat_server_heart_t 55 | { 56 | u16 port; 57 | u16 udp_port; 58 | u32 ip; 59 | u32 key; 60 | using member_list_t = serialization::typelist_t; 61 | }; 62 | 63 | struct nat_udp_request_t 64 | { 65 | u32 key; 66 | using member_list_t = serialization::typelist_t; 67 | }; 68 | 69 | struct nat_respond_t 70 | { 71 | nat_type type; 72 | u32 key; 73 | using member_list_t = serialization::typelist_t; 74 | }; 75 | 76 | #pragma pack(pop) 77 | 78 | constexpr u16 nat_detect_server_port = 6789; 79 | 80 | class nat_server_t 81 | { 82 | tcp::server_t server; 83 | rudp_t rudp; 84 | tcp::client_t other_server; 85 | std::unordered_map map; 86 | 87 | void client_main(tcp::connection_t conn); 88 | void other_server_main(tcp::connection_t conn); 89 | 90 | public: 91 | void bind(event_context_t &ctx, socket_addr_t server_addr, bool reuse_addr = false); 92 | void connect_second_server(event_context_t &ctx, socket_addr_t server_addr); 93 | }; 94 | 95 | class net_detector_t 96 | { 97 | tcp::client_t client; 98 | rudp_t rudp; 99 | 100 | u32 key; 101 | bool is_do_request = false; 102 | 103 | public: 104 | using handler_t = std::function; 105 | 106 | void get_nat_type(event_context_t &ctx, socket_addr_t server, handler_t handler); 107 | }; 108 | } // namespace net 109 | -------------------------------------------------------------------------------- /include/net/net.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file net.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief This header file contains all socket header files on different platforms. Including fixed-length integer 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #ifndef OS_WINDOWS 23 | /// linux headers 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #define WOULDBLOCK EAGAIN 39 | #else 40 | /// windows headers 41 | #define NOMINMAX 42 | 43 | #include 44 | 45 | #include 46 | 47 | #include 48 | #include 49 | #pragma comment(lib, "Ws2_32.lib") 50 | #define WOULDBLOCK WSAEWOULDBLOCK 51 | #endif 52 | #include "net_exception.hpp" 53 | #ifdef _MSC_VER 54 | // Disable MSVC warnings that suggest making code non-portable. 55 | #pragma warning(disable : 4244) 56 | #pragma warning(disable : 4251) 57 | #pragma warning(disable : 4819) 58 | #pragma warning(disable : 4616) 59 | #pragma warning(disable : 2825) 60 | #endif 61 | 62 | typedef unsigned char byte; 63 | 64 | namespace net 65 | { 66 | using i64 = int64_t; 67 | using u64 = uint64_t; 68 | using i32 = int32_t; 69 | using u32 = uint32_t; 70 | using i16 = int16_t; 71 | using u16 = uint16_t; 72 | using i8 = int8_t; 73 | using u8 = uint8_t; 74 | 75 | /// Called before run event context 76 | void init_lib(); 77 | 78 | /// Called when the application is closed 79 | void uninit_lib(); 80 | 81 | enum io_result 82 | { 83 | ok, 84 | /// io in process 85 | /// acceptor 86 | in_process, 87 | /// continue io request 88 | cont, 89 | /// connection is closed by peer/self 90 | closed, 91 | /// io request is timeout 92 | /// connector 93 | timeout, 94 | /// io request failed 95 | failed, 96 | /// buffer too small 97 | /// when udp recv 98 | buffer_too_small, 99 | }; 100 | } // namespace net 101 | -------------------------------------------------------------------------------- /include/net/net_exception.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file net_exception.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief exceptions declaration 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include 23 | #include 24 | namespace net 25 | { 26 | enum class connection_state 27 | { 28 | closed, 29 | close_by_peer, 30 | connection_refuse, 31 | address_in_used, 32 | no_resource, 33 | timeout, 34 | secure_check_failed, 35 | invalid_request, 36 | }; 37 | 38 | static const char *connection_state_strings[] = {"the connection is closed unexpectedly", 39 | "the connection is closed by peer", 40 | "remote connection refused", 41 | "local address is occupied", 42 | "system resource limit, check system memory and file desces", 43 | "connection timeout", 44 | "security check failed", 45 | "invalid data request"}; 46 | 47 | class net_connect_exception : public std::exception 48 | { 49 | std::string str; 50 | connection_state state; 51 | 52 | public: 53 | net_connect_exception(std::string str, connection_state state) 54 | : str(str) 55 | , state(state) 56 | { 57 | } 58 | 59 | const char *what() { return str.c_str(); } 60 | 61 | connection_state get_state() const { return state; } 62 | }; 63 | 64 | class net_param_exception : public std::exception 65 | { 66 | std::string str; 67 | 68 | public: 69 | net_param_exception(std::string str) 70 | : str(str) 71 | { 72 | } 73 | 74 | const char *what() { return str.c_str(); } 75 | }; 76 | 77 | class net_io_exception : public std::exception 78 | { 79 | std::string str; 80 | 81 | public: 82 | net_io_exception(std::string str) 83 | : str(str) 84 | { 85 | } 86 | 87 | const char *what() { return str.c_str(); } 88 | }; 89 | 90 | } // namespace net -------------------------------------------------------------------------------- /include/net/p2p/msg.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file msg.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief all p2p message types 5 | * \version 0.1 6 | * \date 2020-03-21 7 | * 8 | * @copyright Copyright (c) 2020. 9 | * This file is part of P2P-Live. 10 | * 11 | * P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 12 | * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | * 14 | * P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 15 | * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with P2P-Live. If not, see . 20 | * 21 | */ 22 | 23 | #pragma once 24 | #include "../endian.hpp" 25 | #include "../net.hpp" 26 | namespace net::p2p 27 | { 28 | /// frame id 29 | using fragment_id_t = u64; 30 | /// session id: room id 31 | using session_id_t = u32; 32 | /// channel: 0: init, 1: video, 2: audio 33 | using channel_t = u8; 34 | 35 | } // namespace net::p2p -------------------------------------------------------------------------------- /include/net/p2p/peer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file peer.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief peer server/client 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "../endian.hpp" 23 | #include "../net.hpp" 24 | #include "../rudp.hpp" 25 | #include "../socket_addr.hpp" 26 | #include "../tcp.hpp" 27 | #include "msg.hpp" 28 | #include "msg.pb.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | /** 36 | * 如果AB都是公网用户 37 | * 直接连接 (多出现在边缘服务节点传输数据,延迟最低) 38 | * 39 | * 如果公网用户A 和NAT锥形用户B 40 | * 如果 B->A (多出现在1级分发时,客户端从边缘服务节点拉取数据) 41 | * B主动连接A端口 42 | * 如果A->B (反向连接)(少见) 43 | * A->tracker->B->A 44 | * A连接到tracker发送请求 45 | * tracker 把request消息转发给B 46 | * B直接连接上A的外网IP和端口 47 | * 48 | * 锥形用户A to 锥形用户B 49 | * A->B 50 | * A->tracker->B->tracker->A | A<=>B 51 | * A通过 udp 连接到 tracker,同时在NAT上留下一个洞 52 | * Tracker把A 的 request消息转发给B, 其中包含A打通的NAT端口 53 | * B得知A的公网地址,尝试连接A (如果为A全锥形型NAT,成功) 54 | * B通过udp连接tracker,同时在NAT上留下一个洞 55 | * Tracker把B的NAT打通的端口发送到A 56 | * A和B相互同时连接,最少两次发包成功 57 | * 58 | * 对称型A to 锥形用户B 59 | * X 60 | * 对称A to 对称B 61 | * X 62 | * 63 | */ 64 | 65 | namespace net 66 | { 67 | class socket_t; 68 | } 69 | 70 | namespace net::p2p 71 | { 72 | 73 | struct channel_info_t 74 | { 75 | std::queue, u8>> frag_request_queue; 76 | std::queue meta_request_queue; 77 | 78 | std::queue> fragment_send_queue; 79 | std::queue> meta_send_queue; 80 | 81 | fragment_id_t fragment_recv_id; 82 | socket_buffer_t fragment_recv_buffer_cache; 83 | 84 | rudp_connection_t conn; 85 | }; 86 | 87 | struct peer_info_t 88 | { 89 | std::unordered_map channel; // map channel -> channel info 90 | /// udp port address 91 | socket_addr_t remote_address; 92 | u64 sid; 93 | microsecond_t last_ping; 94 | bool has_connect; 95 | peer_info_t() 96 | : last_ping(0) 97 | , has_connect(false) 98 | { 99 | } 100 | 101 | bool operator==(const peer_info_t &rt) const { return rt.remote_address == remote_address; } 102 | bool operator!=(const peer_info_t &rt) const { return !operator==(rt); } 103 | }; 104 | 105 | /// hash function 106 | struct peer_hash_t 107 | { 108 | u64 operator()(const socket_addr_t &p) const { return p.hash(); } 109 | }; 110 | 111 | class peer_t 112 | { 113 | public: 114 | using peer_data_recv_t = std::function; 115 | using peer_disconnect_t = std::function; 116 | using peer_connect_ok_t = std::function; 117 | 118 | using pull_request_t = std::function; 119 | 120 | private: 121 | /// Data socket to transfer data 122 | rudp_t udp; 123 | /// session id request/provide 124 | session_id_t sid; 125 | /// peer map 126 | std::unordered_map, peer_hash_t> peers; 127 | std::vector> noconnect_peers; 128 | 129 | peer_data_recv_t meta_recv_handler; 130 | peer_data_recv_t fragment_recv_handler; 131 | 132 | peer_disconnect_t disconnect_handler; 133 | peer_connect_ok_t connect_handler; 134 | pull_request_t fragment_handler; 135 | pull_request_t meta_handler; 136 | 137 | u64 heartbeat_tick = 30000000; 138 | u64 disconnect_tick = 120000000; 139 | std::vector channels; 140 | 141 | private: 142 | void pmain(rudp_connection_t conn); 143 | void heartbeat(rudp_connection_t conn); 144 | 145 | void update_fragments(std::vector ids, u8 priority, rudp_connection_t conn); 146 | void update_metainfo(u64 key, rudp_connection_t conn); 147 | void send_metainfo(u64 key, socket_buffer_t buffer, rudp_connection_t conn); 148 | void send_fragments(fragment_id_t id, socket_buffer_t buffer, rudp_connection_t conn); 149 | 150 | void send_init(rudp_connection_t conn); 151 | void async_do_write(peer_info_t *peer, int channel); 152 | 153 | peer_info_t *find_peer(socket_addr_t addr); 154 | 155 | void bind_udp(); 156 | 157 | public: 158 | peer_t(session_id_t sid); 159 | ~peer_t(); 160 | peer_t(const peer_t &) = delete; 161 | peer_t &operator=(const peer_t &) = delete; 162 | 163 | void bind(event_context_t &context); 164 | void bind(event_context_t &context, socket_addr_t addr_to_bind, bool reuse_addr = false); 165 | 166 | void accept_channels(const std::vector &channels); 167 | 168 | peer_info_t *add_peer(); 169 | void connect_to_peer(peer_info_t *peer, socket_addr_t remote_peer_udp_addr); 170 | void disconnect(peer_info_t *peer); 171 | 172 | bool has_connect_peer(socket_addr_t remote_peer_udp_addr); 173 | 174 | peer_t &on_meta_data_recv(peer_data_recv_t handler); 175 | peer_t &on_fragment_recv(peer_data_recv_t handler); 176 | peer_t &on_peer_disconnect(peer_disconnect_t handler); 177 | peer_t &on_peer_connect(peer_connect_ok_t handler); 178 | 179 | peer_t &on_fragment_pull_request(pull_request_t handler); 180 | peer_t &on_meta_pull_request(pull_request_t handler); 181 | 182 | void pull_fragment_from_peer(peer_info_t *peer, std::vector fid, channel_t channel, u8 priority); 183 | void pull_meta_data(peer_info_t *peer, u64 key, channel_t channel); 184 | 185 | void send_fragment_to_peer(peer_info_t *peer, fragment_id_t fid, channel_t channel, socket_buffer_t buffer); 186 | void send_meta_data_to_peer(peer_info_t *peer, u64 key, channel_t channel, socket_buffer_t buffer); 187 | 188 | socket_t *get_socket() const { return udp.get_socket(); } 189 | 190 | rudp_t &get_udp() { return udp; } 191 | }; 192 | 193 | } // namespace net::p2p -------------------------------------------------------------------------------- /include/net/p2p/tracker.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file tracker.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief tracker server and tracker node client 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "../endian.hpp" 23 | #include "../net.hpp" 24 | #include "../rudp.hpp" 25 | #include "../tcp.hpp" 26 | #include "msg.hpp" 27 | #include "msg.pb.h" 28 | #include 29 | #include 30 | 31 | namespace net::p2p 32 | { 33 | 34 | struct peer_node_t 35 | { 36 | u16 port; 37 | u16 udp_port; 38 | u32 ip; 39 | peer_node_t(){}; 40 | peer_node_t(u16 port, u16 udp_port, u32 ip) 41 | : port(port) 42 | , udp_port(udp_port) 43 | , ip(ip) 44 | { 45 | } 46 | }; 47 | 48 | struct tracker_node_t 49 | { 50 | u16 port; 51 | u16 udp_port; 52 | u32 ip; 53 | tracker_node_t(){}; 54 | tracker_node_t(u16 port, u16 udp_port, u32 ip) 55 | : port(port) 56 | , udp_port(udp_port) 57 | , ip(ip) 58 | { 59 | } 60 | }; 61 | 62 | struct tracker_info_t 63 | { 64 | microsecond_t last_ping; 65 | u32 workload; 66 | u32 trackers; 67 | /// remote 68 | tracker_node_t node; 69 | 70 | tcp::client_t client; 71 | tcp::connection_t conn_server; 72 | bool is_client; 73 | bool closed; 74 | tracker_info_t() 75 | : last_ping(0) 76 | , workload(0) 77 | , trackers(0) 78 | , conn_server(nullptr) 79 | , is_client(false) 80 | , closed(false) 81 | { 82 | } 83 | }; 84 | 85 | struct node_info_t 86 | { 87 | microsecond_t last_ping; 88 | u32 workload; 89 | peer_node_t node; 90 | u64 sid; 91 | tcp::connection_t conn; 92 | 93 | node_info_t() 94 | : last_ping(0) 95 | , workload(0) 96 | , conn(nullptr){}; 97 | }; 98 | 99 | struct addr_hash_func 100 | { 101 | u64 operator()(const socket_addr_t &addr) const { return addr.hash(); } 102 | }; 103 | 104 | // 30s 105 | constexpr static inline u64 node_tick_timespan = 30000000; 106 | constexpr static inline u64 node_tick_times = 2; 107 | 108 | /// BUG: there is not thread-safety, try fix it. 109 | class tracker_server_t 110 | { 111 | private: 112 | using link_error_handler_t = std::function; 113 | using link_handler_t = std::function; 114 | 115 | using peer_add_handler_t = std::function; 116 | using peer_remove_handler_t = std::function; 117 | using peer_error_handler_t = std::function; 118 | 119 | using peer_connect_handler_t = std::function; 120 | 121 | private: 122 | /// 1min 123 | constexpr static inline u64 tick_timespan = 60000000; 124 | constexpr static inline u64 tick_times = 2; 125 | 126 | tcp::server_t server; 127 | rudp_t udp; 128 | u16 udp_port; 129 | std::string edge_key; 130 | 131 | /// save index of tracker_infos 132 | std::unordered_map, addr_hash_func> trackers; 133 | std::unordered_map nodes; 134 | std::vector node_infos; 135 | link_error_handler_t link_error_handler; 136 | link_handler_t link_handler, unlink_handler; 137 | peer_add_handler_t add_handler; 138 | peer_remove_handler_t remove_handler; 139 | peer_error_handler_t peer_error_handler; 140 | peer_connect_handler_t normal_peer_connect_handler; 141 | 142 | private: 143 | void server_main(tcp::connection_t conn); 144 | void client_main(tcp::connection_t conn); 145 | void udp_main(rudp_connection_t conn); 146 | void update_tracker(socket_addr_t addr, tcp::connection_t conn, PingPong &res); 147 | 148 | public: 149 | tracker_server_t(){}; 150 | tracker_server_t(const tracker_server_t &) = delete; 151 | tracker_server_t &operator=(const tracker_server_t &) = delete; 152 | 153 | ~tracker_server_t(); 154 | 155 | void config(std::string edge_key); 156 | 157 | void bind(event_context_t &context, socket_addr_t addr, int max_client_count, bool reuse_addr = false); 158 | void link_other_tracker_server(event_context_t &context, socket_addr_t addr, microsecond_t timeout); 159 | tracker_server_t &on_link_error(link_error_handler_t handler); 160 | tracker_server_t &on_link_server(link_handler_t handler); 161 | tracker_server_t &on_unlink_server(link_handler_t handler); 162 | 163 | tracker_server_t &on_shared_peer_add_connection(peer_add_handler_t handler); 164 | tracker_server_t &on_shared_peer_remove_connection(peer_remove_handler_t handler); 165 | tracker_server_t &on_shared_peer_error(peer_error_handler_t handler); 166 | 167 | tracker_server_t &on_normal_peer_connect(peer_connect_handler_t handler); 168 | 169 | std::vector get_trackers() const; 170 | 171 | void close(); 172 | }; 173 | 174 | /// client under NAT or not 175 | class tracker_node_client_t 176 | { 177 | private: 178 | using nodes_update_handler_t = std::function; 179 | using trackers_update_handler_t = std::function; 180 | using nodes_connect_handler_t = std::function; 181 | using error_handler_t = std::function; 182 | using disconnect_handler_t = std::function; 183 | using tracker_connect_handler_t = std::function; 184 | 185 | private: 186 | tcp::client_t client; 187 | 188 | socket_addr_t server_udp_address; 189 | 190 | u16 client_rudp_port; 191 | u16 client_outer_port; 192 | u32 client_outer_ip; 193 | 194 | nodes_update_handler_t node_update_handler; 195 | trackers_update_handler_t tracker_update_handler; 196 | nodes_connect_handler_t connect_handler; 197 | error_handler_t error_handler; 198 | tracker_connect_handler_t tracker_connect_handler; 199 | u64 sid; 200 | microsecond_t timeout; 201 | bool request_trackers; 202 | bool is_peer_client; 203 | bool wait_next_package; 204 | 205 | std::queue> node_queue; 206 | socket_addr_t remote_server_address; 207 | event_context_t *context; 208 | 209 | /// tracker key is required when sid is 0 210 | std::string key; 211 | 212 | private: 213 | void tmain(tcp::connection_t conn); 214 | void update_trackers(int count); 215 | void update_nodes(); 216 | 217 | public: 218 | tracker_node_client_t(){}; 219 | tracker_node_client_t(const tracker_node_client_t &) = delete; 220 | tracker_node_client_t &operator=(const tracker_node_client_t &) = delete; 221 | 222 | void config(bool as_peer_server, u64 sid, std::string key); 223 | 224 | void connect_server(event_context_t &context, socket_addr_t addr, microsecond_t timeout); 225 | 226 | void request_update_trackers(); 227 | 228 | ///\note the nodes can contain self 229 | tracker_node_client_t &on_nodes_update(nodes_update_handler_t handler); 230 | 231 | void request_update_nodes(int max_request_count, RequestNodeStrategy strategy); 232 | tracker_node_client_t &on_trackers_update(trackers_update_handler_t handler); 233 | 234 | void request_connect_node(peer_node_t node, rudp_t &udp); 235 | tracker_node_client_t &on_node_request_connect(nodes_connect_handler_t handler); 236 | 237 | tracker_node_client_t &on_error(error_handler_t handler); 238 | 239 | tracker_node_client_t &on_tracker_server_connect(tracker_connect_handler_t handler); 240 | 241 | socket_t *get_socket() const { return client.get_socket(); } 242 | 243 | void close(); 244 | 245 | ~tracker_node_client_t(); 246 | }; 247 | 248 | } // namespace net::p2p 249 | -------------------------------------------------------------------------------- /include/net/proto/msg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package net; 4 | 5 | message InitReq { 6 | uint64 sid = 1; 7 | } 8 | 9 | message InitRsp { 10 | uint64 fragment_id_beg = 1; 11 | uint64 fragment_id_end = 2; 12 | } 13 | 14 | message FragmentReq { 15 | uint32 priority = 1; 16 | repeated uint64 fragment_ids = 2; 17 | } 18 | 19 | message FragmentRsp { 20 | uint64 fragment_id = 1; 21 | uint64 length = 2; 22 | bytes data = 3; 23 | } 24 | 25 | message FragmentRspRest { 26 | bool is_rst = 1; 27 | bytes data = 2; 28 | } 29 | 30 | message MetaReq { 31 | uint64 key = 1; 32 | } 33 | 34 | message MetaRsp { 35 | uint64 key = 1; 36 | bytes value = 2; 37 | } 38 | 39 | message Cancel { 40 | repeated uint64 fragment_ids = 1; 41 | } 42 | 43 | message Heart { 44 | 45 | } 46 | 47 | enum RequestNodeStrategy { 48 | random = 0; 49 | min_workload = 1; 50 | edge_nodes = 2; 51 | } 52 | 53 | message PingPong { 54 | uint32 peer_workload = 1; 55 | uint32 tracker_neighbor_count = 2; 56 | uint32 port = 3; 57 | uint32 udp_port = 4; 58 | } 59 | 60 | message TrackerInfoReq { 61 | 62 | } 63 | 64 | message TrackerInfoRsp { 65 | uint32 port = 1; 66 | uint32 udp_port = 2; 67 | uint32 ip = 3; 68 | } 69 | 70 | message InitConnectionReq { 71 | uint64 sid = 1; 72 | bytes key = 2; 73 | } 74 | 75 | message InitConnectionRsp { 76 | 77 | } 78 | 79 | message TrackersReq { 80 | uint32 max_count = 1; 81 | } 82 | 83 | message Tracker { 84 | uint32 port = 1; 85 | uint32 udp_port = 2; 86 | uint32 ip = 3; 87 | } 88 | 89 | message TrackersRsp { 90 | uint32 avl_count = 1; 91 | repeated Tracker trackers = 2; 92 | } 93 | 94 | message Node { 95 | uint32 port = 1; 96 | uint32 udp_port = 2; 97 | uint32 ip = 3; 98 | } 99 | 100 | message NodeReq { 101 | uint32 max_count = 1; 102 | uint64 sid = 2; 103 | RequestNodeStrategy strategy = 3; 104 | } 105 | 106 | message NodeRsp { 107 | uint32 avl_count = 1; 108 | uint64 sid = 2; 109 | repeated Node nodes = 3; 110 | } 111 | 112 | message UDPConnectionReq { 113 | uint32 magic = 1; 114 | uint32 target_port = 2; 115 | uint32 target_ip = 3; 116 | uint32 from_port = 4; 117 | uint32 from_ip = 5; 118 | uint32 from_udp_port = 6; 119 | uint64 sid = 7; 120 | } 121 | 122 | message Package{ 123 | oneof msg { 124 | InitReq init_req = 1; 125 | InitRsp init_rsp = 2; 126 | FragmentReq fragment_req = 3; 127 | FragmentRsp fragment_rsp = 4; 128 | FragmentRspRest fragment_rsp_rest = 5; 129 | 130 | MetaReq meta_req = 6; 131 | MetaRsp meta_rsp = 7; 132 | Cancel cancel = 8; 133 | Heart heart = 10; 134 | PingPong ping = 11; 135 | PingPong pong = 12; 136 | TrackerInfoReq tracker_info_req = 13; 137 | TrackerInfoRsp tracker_info_rsp = 14; 138 | InitConnectionReq init_connection_req = 15; 139 | InitConnectionRsp init_connection_rsp = 16; 140 | TrackersReq trackers_req = 17; 141 | TrackersRsp trackers_rsp = 18; 142 | NodeReq node_req = 19; 143 | NodeRsp node_rsp = 20; 144 | UDPConnectionReq udp_connection_req = 21; 145 | } 146 | } -------------------------------------------------------------------------------- /include/net/rudp.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file rudp.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Reliable UDP implementation with KCP 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "co.hpp" 23 | #include "net.hpp" 24 | #include "socket_addr.hpp" 25 | #include "socket_buffer.hpp" 26 | #include 27 | #include 28 | 29 | namespace net 30 | { 31 | class event_context_t; 32 | class rudp_impl_t; 33 | class socket_t; 34 | 35 | struct rudp_connection_t 36 | { 37 | socket_addr_t address; 38 | int channel; 39 | }; 40 | 41 | class rudp_t 42 | { 43 | public: 44 | using new_connection_handler_t = std::function; 45 | 46 | /// return false will discard current packet 47 | using unknown_handler_t = std::function; 48 | using timeout_handler_t = std::function; 49 | 50 | private: 51 | // impl idiom for third-party libraries 52 | rudp_impl_t *impl; 53 | 54 | public: 55 | rudp_t(); 56 | ~rudp_t(); 57 | 58 | rudp_t(const rudp_t &) = delete; 59 | rudp_t &operator=(const rudp_t &) = delete; 60 | 61 | /// bind a local address 62 | void bind(event_context_t &context, socket_addr_t local_addr, bool reuse_addr = false); 63 | 64 | /// bind random port 65 | void bind(event_context_t &context); 66 | 67 | /// addr remote address 68 | void add_connection(socket_addr_t addr, int channel, microsecond_t inactive_timeout); 69 | 70 | void add_connection(socket_addr_t addr, int channel, microsecond_t inactive_timeout, 71 | std::function co_func); 72 | 73 | /// level 0: faster. level 1: fast, level 2: slow 74 | void config(rudp_connection_t conn, int level); 75 | 76 | void set_wndsize(socket_addr_t addr, int channel, int send, int recv); 77 | 78 | rudp_t &on_new_connection(new_connection_handler_t handler); 79 | 80 | void remove_connection(socket_addr_t addr, int channel); 81 | 82 | void remove_connection(rudp_connection_t conn); 83 | 84 | bool removeable(socket_addr_t addr, int channel); 85 | 86 | /// is current connection removeable 87 | bool removeable(rudp_connection_t conn); 88 | 89 | rudp_t &on_unknown_packet(unknown_handler_t handler); 90 | 91 | rudp_t &on_connection_timeout(timeout_handler_t handler); 92 | 93 | co::async_result_t awrite(co::paramter_t ¶m, rudp_connection_t conn, socket_buffer_t &buffer); 94 | co::async_result_t aread(co::paramter_t ¶m, rudp_connection_t conn, socket_buffer_t &buffer); 95 | 96 | /// call func on connection context 97 | void run_at(rudp_connection_t conn, std::function func); 98 | 99 | socket_t *get_socket() const; 100 | 101 | void close_all_remote(); 102 | 103 | int get_mtu() const 104 | { 105 | return 1472 - 24; // kcp header 24 106 | } 107 | 108 | void close(); 109 | 110 | bool is_bind() const; 111 | }; 112 | 113 | // wrapper functions 114 | co::async_result_t rudp_awrite(co::paramter_t ¶m, rudp_t *rudp, rudp_connection_t conn, 115 | socket_buffer_t &buffer); 116 | co::async_result_t rudp_aread(co::paramter_t ¶m, rudp_t *rudp, rudp_connection_t conn, 117 | socket_buffer_t &buffer); 118 | 119 | } // namespace net 120 | -------------------------------------------------------------------------------- /include/net/select.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file select.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief "Select" API for event demultiplexer 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #ifndef OS_WINDOWS 23 | #include "event.hpp" 24 | #include 25 | 26 | namespace net 27 | { 28 | class event_select_demultiplexer : public event_demultiplexer 29 | { 30 | #ifdef OS_WINDOWS 31 | std::vector events; 32 | std::vector handles; 33 | std::unordered_map map; 34 | #else 35 | fd_set read_set; 36 | fd_set write_set; 37 | fd_set error_set; 38 | int fd; 39 | #endif 40 | 41 | public: 42 | event_select_demultiplexer(); 43 | ~event_select_demultiplexer(); 44 | void add(handle_t handle, event_type_t type) override; 45 | handle_t select(event_type_t *type, microsecond_t *timeout) override; 46 | void remove(handle_t handle, event_type_t type) override; 47 | void wake_up(event_loop_t &cur_loop) override; 48 | }; 49 | 50 | } // namespace net 51 | #endif -------------------------------------------------------------------------------- /include/net/socket.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file socket.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief this part includes socket functions: send, recv, accept, connect, bind. 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "co.hpp" 23 | #include "event.hpp" 24 | #include "execute_context.hpp" 25 | #include "net.hpp" 26 | #include "socket_addr.hpp" 27 | #include "socket_buffer.hpp" 28 | #include 29 | #ifdef OS_WINDOWS 30 | #include "iocp.hpp" 31 | #endif 32 | 33 | namespace net 34 | { 35 | ///\note socket_t is generated by 'new_tcp_socket', 'new_udp_socket' 36 | ///\note socket_t is destoried by 'delete_socket' which also close socketfd 37 | class socket_t : public execute_context_t, event_handler_t 38 | { 39 | protected: 40 | handle_t fd; 41 | socket_addr_t local; 42 | socket_addr_t remote; 43 | bool is_connection_closed; 44 | 45 | friend co::async_result_t connect_to(co::paramter_t &, socket_t *, socket_addr_t); 46 | friend co::async_result_t accept_from(co::paramter_t &, socket_t *in); 47 | friend class event_loop_t; 48 | 49 | public: 50 | socket_t(int fd); 51 | ~socket_t(); 52 | socket_t(const socket_t &) = delete; 53 | socket_t &operator=(const socket_t &) = delete; 54 | 55 | virtual co::async_result_t awrite(co::paramter_t &, socket_buffer_t &buffer) = 0; 56 | virtual co::async_result_t aread(co::paramter_t &, socket_buffer_t &buffer) = 0; 57 | 58 | virtual co::async_result_t awrite_to(co::paramter_t &, socket_buffer_t &buffer, 59 | socket_addr_t target) = 0; 60 | virtual co::async_result_t aread_from(co::paramter_t &, socket_buffer_t &buffer, 61 | socket_addr_t &target) = 0; 62 | 63 | socket_addr_t local_addr(); 64 | socket_addr_t remote_addr(); 65 | 66 | void on_event(event_context_t &context, event_type_t type) override; 67 | 68 | void add_event(event_type_t type); 69 | void remove_event(event_type_t type); 70 | 71 | handle_t get_raw_handle() const { return fd; } 72 | 73 | bool is_connection_alive() const { return !is_connection_closed; } 74 | 75 | void bind_context(event_context_t &context); 76 | void unbind_context(); 77 | }; 78 | 79 | class bsd_socket_t : public socket_t 80 | { 81 | public: 82 | using socket_t::socket_t; 83 | co::async_result_t awrite(co::paramter_t &, socket_buffer_t &buffer) override; 84 | co::async_result_t aread(co::paramter_t &, socket_buffer_t &buffer) override; 85 | 86 | co::async_result_t awrite_to(co::paramter_t &, socket_buffer_t &buffer, socket_addr_t target) override; 87 | co::async_result_t aread_from(co::paramter_t &, socket_buffer_t &buffer, socket_addr_t &target) override; 88 | }; 89 | 90 | class iocp_socket_t : public socket_t 91 | { 92 | public: 93 | using socket_t::socket_t; 94 | co::async_result_t awrite(co::paramter_t &, socket_buffer_t &buffer) override; 95 | co::async_result_t aread(co::paramter_t &, socket_buffer_t &buffer) override; 96 | 97 | co::async_result_t awrite_to(co::paramter_t &, socket_buffer_t &buffer, socket_addr_t target) override; 98 | co::async_result_t aread_from(co::paramter_t &, socket_buffer_t &buffer, socket_addr_t &target) override; 99 | }; 100 | 101 | co::async_result_t socket_awrite(co::paramter_t ¶m, socket_t *socket, socket_buffer_t &buffer); 102 | co::async_result_t socket_aread(co::paramter_t ¶m, socket_t *socket, socket_buffer_t &buffer); 103 | 104 | co::async_result_t socket_awrite_to(co::paramter_t ¶m, socket_t *socket, socket_buffer_t &buffer, 105 | socket_addr_t target); 106 | co::async_result_t socket_aread_from(co::paramter_t ¶m, socket_t *socket, socket_buffer_t &buffer, 107 | socket_addr_t &target); 108 | 109 | /// ----------- socket functions ---------------------- 110 | 111 | socket_t *new_tcp_socket(); 112 | socket_t *new_udp_socket(); 113 | 114 | socket_t *reuse_addr_socket(socket_t *socket, bool reuse); 115 | socket_t *reuse_port_socket(socket_t *socket, bool reuse); 116 | 117 | co::async_result_t connect_to(co::paramter_t ¶m, socket_t *socket, socket_addr_t socket_to_addr); 118 | co::async_result_t connect_udp(co::paramter_t ¶m, socket_t *socket, socket_addr_t socket_to_addr); 119 | 120 | socket_t *bind_at(socket_t *socket, socket_addr_t socket_to_addr); 121 | socket_t *listen_from(socket_t *socket, int max_wait_client); 122 | co::async_result_t accept_from(co::paramter_t ¶m, socket_t *in); 123 | void close_socket(socket_t *socket); 124 | 125 | socket_t *set_socket_send_buffer_size(socket_t *socket, int size); 126 | socket_t *set_socket_recv_buffer_size(socket_t *socket, int size); 127 | int get_socket_send_buffer_size(socket_t *socket); 128 | int get_socket_recv_buffer_size(socket_t *socket); 129 | 130 | /// get local ip address 131 | socket_addr_t get_ip(socket_t *socket); 132 | 133 | } // namespace net 134 | -------------------------------------------------------------------------------- /include/net/socket_addr.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file socket_addr.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Socket address c++ wrapper 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "net.hpp" 23 | #include 24 | namespace net 25 | { 26 | class socket_addr_t 27 | { 28 | sockaddr_in so_addr; 29 | 30 | public: 31 | socket_addr_t(); 32 | /// init address (any:port) 33 | socket_addr_t(int port); 34 | socket_addr_t(u32 addr, int port); 35 | socket_addr_t(sockaddr_in addr); 36 | socket_addr_t(std::string addr, int port); 37 | 38 | /// return string like ip:port 39 | std::string to_string() const; 40 | /// return ip string 41 | std::string get_addr() const; 42 | /// get address local endian 43 | u32 v4_addr() const; 44 | 45 | int get_port() const; 46 | 47 | sockaddr_in get_raw_addr() { return so_addr; } 48 | 49 | bool operator==(const socket_addr_t &addr) const 50 | { 51 | return addr.so_addr.sin_port == so_addr.sin_port && addr.so_addr.sin_family == so_addr.sin_family && 52 | addr.so_addr.sin_addr.s_addr == so_addr.sin_addr.s_addr; 53 | } 54 | 55 | bool operator!=(const socket_addr_t &addr) const { return !operator==(addr); } 56 | u64 hash() const { return ((u64)so_addr.sin_addr.s_addr << 32) | so_addr.sin_port; }; 57 | }; 58 | }; // namespace net -------------------------------------------------------------------------------- /include/net/socket_buffer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file socket_buffer.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief socket buffer is a buffer container with move and reference count semantics 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "net/net.hpp" 23 | #include 24 | #include 25 | namespace net 26 | { 27 | 28 | /// The buffer container can be initialized by size, or using existing memory. It is managed by the 29 | /// socket_buffer_t in the first case, and is managed by the user in the second case. 30 | class socket_buffer_t 31 | { 32 | public: 33 | /// a control block used by socket buffer 34 | struct socket_buffer_header_t 35 | { 36 | /// shared reference count 37 | std::atomic_int ref_count; 38 | }; 39 | 40 | private: 41 | byte *ptr; 42 | socket_buffer_header_t *header; 43 | u64 buffer_size; 44 | u64 valid_data_length; 45 | u64 walk_offset; 46 | 47 | private: 48 | static socket_buffer_t from_struct_inner(byte *buffer_ptr, u64 buffer_length); 49 | 50 | public: 51 | struct except_buffer_helper_t 52 | { 53 | socket_buffer_t *buf; 54 | explicit except_buffer_helper_t(socket_buffer_t *buf) 55 | : buf(buf) 56 | { 57 | } 58 | 59 | except_buffer_helper_t length(u64 len); 60 | except_buffer_helper_t origin_length(); 61 | 62 | socket_buffer_t &operator()() const { return *buf; } 63 | }; 64 | /// init buffer with nothing, the pointer will not be initialized. 65 | socket_buffer_t(); 66 | socket_buffer_t(u64 len); 67 | socket_buffer_t(const google::protobuf::Message &msg) 68 | : socket_buffer_t(msg.ByteSizeLong()) 69 | { 70 | msg.SerializeWithCachedSizesToArray(ptr); 71 | valid_data_length = buffer_size; 72 | } 73 | 74 | /// init buffer with pointer 75 | socket_buffer_t(byte *buffer_ptr, u64 buffer_length); 76 | 77 | socket_buffer_t(const socket_buffer_t &); 78 | socket_buffer_t &operator=(const socket_buffer_t &); 79 | 80 | // move operation 81 | socket_buffer_t(socket_buffer_t &&buf); 82 | socket_buffer_t &operator()(socket_buffer_t &&buf); 83 | 84 | ~socket_buffer_t(); 85 | 86 | template static socket_buffer_t from_struct(T &buf) 87 | { 88 | static_assert(std::is_pod_v); 89 | return from_struct_inner((byte *)&buf, sizeof(T)); 90 | } 91 | 92 | static socket_buffer_t from_string(std::string str); 93 | 94 | byte *get_base_ptr() const { return ptr; } 95 | 96 | /// get pointer at current offset 97 | byte *get() const { return ptr + walk_offset; } 98 | 99 | /// get origin data length 100 | u64 get_data_length() const { return valid_data_length; } 101 | 102 | /// get origin buffer length 103 | u64 get_buffer_origin_length() const { return buffer_size; } 104 | 105 | /// get data length start at the current offset 106 | u64 get_length() const { return valid_data_length - walk_offset; } 107 | 108 | u64 get_walk_offset() const { return walk_offset; } 109 | 110 | /// reset offset and set data length to offset 111 | void finish_walk() 112 | { 113 | valid_data_length = walk_offset; 114 | walk_offset = 0; 115 | } 116 | 117 | /// except size to read/write 118 | ///\note call it before read/write socket data. Decide on the size of the data sent and received 119 | except_buffer_helper_t expect() { return except_buffer_helper_t(this); } 120 | 121 | long write_string(const std::string &str); 122 | std::string to_string() const; 123 | 124 | /// walk in buffer 125 | void walk_step(u64 delta) 126 | { 127 | walk_offset += delta; 128 | if (walk_offset > valid_data_length) 129 | walk_offset = valid_data_length; 130 | } 131 | 132 | /// memzero to buffer 133 | void clear(); 134 | }; 135 | 136 | }; // namespace net -------------------------------------------------------------------------------- /include/net/tcp.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file tcp.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief TCP wrapper. Including TCP package sending/recving, acceptor, connector. 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "co.hpp" 23 | #include "endian.hpp" 24 | #include "socket_addr.hpp" 25 | #include "socket_buffer.hpp" 26 | #include "timer.hpp" 27 | #include 28 | 29 | namespace net 30 | { 31 | class event_context_t; 32 | class socket_t; 33 | }; // namespace net 34 | 35 | namespace net::tcp 36 | { 37 | /// tcp application head 38 | struct package_head_t 39 | { 40 | u32 size; /// payload length 41 | using member_list_t = net::serialization::typelist_t; 42 | }; 43 | 44 | class connection_t 45 | { 46 | socket_t *socket; 47 | 48 | public: 49 | connection_t(socket_t *so) 50 | : socket(so){}; 51 | /// async write data by stream mode 52 | co::async_result_t awrite(co::paramter_t ¶m, socket_buffer_t &buffer); 53 | /// async read data by stream mode 54 | co::async_result_t aread(co::paramter_t ¶m, socket_buffer_t &buffer); 55 | 56 | /// Wait for the next packet and read the tcp application header 57 | co::async_result_t aread_packet_head(co::paramter_t ¶m, package_head_t &head, 58 | socket_buffer_t &buffer); 59 | 60 | co::async_result_t aread_packet_content(co::paramter_t ¶m, socket_buffer_t &buffer); 61 | 62 | /// write package 63 | co::async_result_t awrite_packet(co::paramter_t ¶m, package_head_t &head, socket_buffer_t &buffer); 64 | 65 | socket_t *get_socket() { return socket; } 66 | }; 67 | 68 | /// wrappers 69 | co::async_result_t conn_awrite(co::paramter_t ¶m, connection_t conn, socket_buffer_t &buffer); 70 | co::async_result_t conn_aread(co::paramter_t ¶m, connection_t conn, socket_buffer_t &buffer); 71 | co::async_result_t conn_aread_packet_head(co::paramter_t ¶m, connection_t conn, package_head_t &head); 72 | co::async_result_t conn_aread_packet_content(co::paramter_t ¶m, connection_t conn, 73 | socket_buffer_t &buffer); 74 | co::async_result_t conn_awrite_packet(co::paramter_t ¶m, connection_t conn, package_head_t &head, 75 | socket_buffer_t &buffer); 76 | 77 | class server_t 78 | { 79 | public: 80 | using handler_t = std::function; 81 | using error_handler_t = std::function; 82 | 83 | private: 84 | socket_t *server_socket; 85 | event_context_t *context; 86 | handler_t join_handler; 87 | handler_t exit_handler; 88 | error_handler_t error_handler; 89 | 90 | private: 91 | void wait_client(); 92 | void client_main(socket_t *socket); 93 | 94 | public: 95 | server_t(); 96 | ~server_t(); 97 | /// listen port in acceptor coroutine. 98 | /// 99 | ///\param context event context 100 | ///\param address the address:port to bind 101 | ///\param max_wait_client client count in completion queue 102 | ///\param reuse_addr create socket by SO_REUSEADDR? 103 | void listen(event_context_t &context, socket_addr_t address, int max_wait_client, bool reuse_addr = false); 104 | 105 | server_t &on_client_join(handler_t handler); 106 | server_t &on_client_exit(handler_t handler); 107 | server_t &on_client_error(error_handler_t handler); 108 | 109 | void exit_client(socket_t *client); 110 | 111 | void close_server(); 112 | 113 | socket_t *get_socket() const { return server_socket; } 114 | }; 115 | 116 | class client_t 117 | { 118 | public: 119 | using handler_t = std::function; 120 | using error_handler_t = std::function; 121 | 122 | private: 123 | socket_t *socket; 124 | socket_addr_t connect_addr; 125 | handler_t join_handler; 126 | handler_t exit_handler; 127 | error_handler_t error_handler; 128 | event_context_t *context; 129 | void wait_server(socket_addr_t address, microsecond_t timeout); 130 | 131 | public: 132 | client_t(); 133 | ~client_t(); 134 | /// connect to TCP server 135 | /// 136 | ///\param context event context 137 | ///\param server_address server tcp address to connect 138 | ///\param timeout timeout by microseconds 139 | ///\note call on_server_error when timeout 140 | void connect(event_context_t &context, socket_addr_t server_address, microsecond_t timeout); 141 | client_t &on_server_connect(handler_t handler); 142 | client_t &on_server_disconnect(handler_t handler); 143 | client_t &on_server_error(error_handler_t handler); 144 | 145 | void close(); 146 | 147 | socket_addr_t get_connect_addr() const { return connect_addr; } 148 | socket_t *get_socket() const { return socket; } 149 | tcp::connection_t get_connection() const { return socket; } 150 | 151 | bool is_connect() const; 152 | }; 153 | 154 | } // namespace net::tcp 155 | -------------------------------------------------------------------------------- /include/net/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file thread_pool.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Simple thread pool implementation for high CPU load task runs 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace net 29 | { 30 | 31 | class thread_pool_t 32 | { 33 | private: 34 | std::vector threads; 35 | mutable std::mutex mutex; 36 | std::condition_variable cond; 37 | std::queue> tasks; 38 | // exit flag 39 | bool exit; 40 | std::atomic_int counter; 41 | void wrapper(); 42 | 43 | public: 44 | ///\param count thread count in pool 45 | thread_pool_t(int count); 46 | 47 | thread_pool_t(const thread_pool_t &) = delete; 48 | thread_pool_t &operator=(const thread_pool_t &) = delete; 49 | 50 | ///\note we wait all threads to exit at here 51 | ~thread_pool_t(); 52 | 53 | /// commit a task to thread pool 54 | /// 55 | ///\param task to run 56 | ///\return none 57 | void commit(std::function task); 58 | 59 | /// return idle thread count 60 | int get_idles() const; 61 | 62 | /// return true if there are no tasks in task queue and no threads are running tasks. 63 | bool empty() const; 64 | }; 65 | 66 | } // namespace net 67 | -------------------------------------------------------------------------------- /include/net/timer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file timer.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Timer queue generated by minimum heap 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "net.hpp" 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace net 31 | { 32 | using microsecond_t = u64; 33 | using timer_callback_t = std::function; 34 | // 1ms 35 | inline constexpr microsecond_t timer_min_precision = 1000; 36 | using timer_id = int64_t; 37 | 38 | struct timer_t 39 | { 40 | microsecond_t timepoint; 41 | timer_callback_t callback; 42 | timer_t(microsecond_t timepoint, std::function callback) 43 | : timepoint(timepoint) 44 | , callback(callback) 45 | { 46 | } 47 | }; 48 | 49 | struct timer_slot_t 50 | { 51 | microsecond_t timepoint; 52 | std::vector> callbacks; 53 | timer_slot_t(microsecond_t tp) 54 | : timepoint(tp) 55 | { 56 | } 57 | }; 58 | 59 | using map_t = std::unordered_map; 60 | 61 | timer_t make_timer(microsecond_t span, timer_callback_t callback); 62 | 63 | struct timer_cmp 64 | { 65 | bool operator()(timer_slot_t *lh, timer_slot_t *rh) const { return lh->timepoint > rh->timepoint; } 66 | }; 67 | 68 | struct timer_registered_t 69 | { 70 | timer_id id; 71 | microsecond_t timepoint; 72 | }; 73 | 74 | /// No thread safety. Don't add timers from other threads 75 | struct time_manager_t 76 | { 77 | microsecond_t precision; 78 | /// a minimum heap for timers 79 | std::priority_queue, timer_cmp> queue; 80 | map_t map; 81 | 82 | time_manager_t() {} 83 | 84 | void tick(); 85 | /// add new timer 86 | timer_registered_t insert(timer_t timer); 87 | 88 | /// remove timer 89 | void cancel(timer_registered_t reg); 90 | 91 | /// get the time should be called at next tick 'timepoint' 92 | microsecond_t next_tick_timepoint(); 93 | }; 94 | 95 | std::unique_ptr create_time_manager(microsecond_t precision = timer_min_precision); 96 | 97 | microsecond_t get_current_time(); 98 | 99 | /// Inner use 100 | microsecond_t get_timestamp(); 101 | 102 | constexpr microsecond_t make_timespan(int second, int ms = 0, int us = 0) 103 | { 104 | return (u64)second * 1000000 + (u64)ms * 1000 + us; 105 | } 106 | 107 | constexpr microsecond_t make_timespan_full() { return 0xFFFFFFFFFFFFFFFFULL; } 108 | 109 | } // namespace net -------------------------------------------------------------------------------- /include/net/udp.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file udp.hpp 3 | * \author kadds (itmyxyf@gmail.com) 4 | * \brief Provide basic UDP sending/recving 5 | * \version 0.1 6 | * \date 2020-03-13 7 | * 8 | * @copyright Copyright (c) 2020. 9 | This file is part of P2P-Live. 10 | 11 | P2P-Live is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 13 | 14 | P2P-Live is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 15 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with P2P-Live. If not, see . 19 | * 20 | */ 21 | #pragma once 22 | #include "event.hpp" 23 | #include "socket_addr.hpp" 24 | #include "socket_buffer.hpp" 25 | #include 26 | 27 | namespace net 28 | { 29 | class socket_t; 30 | }; 31 | 32 | namespace net::udp 33 | { 34 | 35 | class server_t 36 | { 37 | private: 38 | socket_t *socket; 39 | event_context_t *context; 40 | 41 | public: 42 | ~server_t(); 43 | socket_t *get_socket() const { return socket; } 44 | socket_t *bind(event_context_t &context, socket_addr_t addr, bool reuse_port = false); 45 | void run(std::function func); 46 | void close(); 47 | }; 48 | 49 | class client_t 50 | { 51 | private: 52 | socket_t *socket; 53 | socket_addr_t connect_addr; 54 | event_context_t *context; 55 | 56 | public: 57 | ~client_t(); 58 | socket_t *get_socket() const { return socket; } 59 | 60 | /// It just binds the socket to an event, not a real connection. Set remote_address_bind_to_socket to true to bind 61 | /// to the address 62 | void connect(event_context_t &context, socket_addr_t addr, bool remote_address_bind_to_socket = true); 63 | 64 | /// must call 'connect' befor call it. 65 | void run(std::function func); 66 | 67 | void close(); 68 | socket_addr_t get_address() const; 69 | }; 70 | 71 | } // namespace net::udp 72 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(net) 2 | -------------------------------------------------------------------------------- /lib/net/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE DIR_SRCS *.cc *.cpp *.CC *.CPP) 2 | file(GLOB_RECURSE DIR_HDRS *.h *.hpp) 3 | file(GLOB_RECURSE PROTOFILES ../../*.proto) 4 | 5 | find_package(Boost REQUIRED COMPONENTS context) 6 | find_package(Protobuf REQUIRED) 7 | if (PROTOBUF_FOUND) 8 | message("protobuf found") 9 | else () 10 | message(FATAL_ERROR "Cannot find Protobuf") 11 | endif () 12 | 13 | PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${PROTOFILES}) 14 | 15 | add_library(net STATIC ${DIR_SRCS} ${PROTO_SRCS} ${PROTO_HDRS}) 16 | 17 | 18 | target_include_directories(net PRIVATE ${PROTOBUF_INCLUDE_DIRS}) 19 | target_include_directories(net PUBLIC ${DIR_HDRS}) 20 | target_include_directories(net PUBLIC ${PROTOBUF_INCLUDE_DIRS}) 21 | target_include_directories(net PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) 22 | 23 | target_include_directories(net PRIVATE) 24 | 25 | target_link_libraries(net PUBLIC Boost::context) 26 | target_link_libraries(net PRIVATE protobuf::libprotoc protobuf::libprotobuf protobuf::libprotobuf-lite) -------------------------------------------------------------------------------- /lib/net/co.cc: -------------------------------------------------------------------------------- 1 | #include "net/co.hpp" 2 | 3 | namespace net::co 4 | { 5 | ctx::fiber &&co_wrapper(ctx::fiber &&sink, coroutine_t *co) 6 | { 7 | co->context = std::move(sink); 8 | try 9 | { 10 | co_cur->func(); 11 | } catch (const coroutine_stop_exception &) 12 | { 13 | } 14 | co->is_stop = true; 15 | return std::move(co->context); 16 | } 17 | 18 | ctx::fiber &&co_reschedule_wrapper(ctx::fiber &&sink, coroutine_t *co, std::function func) 19 | { 20 | co->context = std::move(sink); 21 | func(); 22 | return std::move(co->context); 23 | } 24 | 25 | }; // namespace net::co -------------------------------------------------------------------------------- /lib/net/epoll.cc: -------------------------------------------------------------------------------- 1 | #include "net/epoll.hpp" 2 | #include "net/net_exception.hpp" 3 | #include 4 | #ifndef OS_WINDOWS 5 | namespace net 6 | { 7 | event_epoll_demultiplexer::event_epoll_demultiplexer() 8 | { 9 | fd = ::epoll_create(100); 10 | if (fd <= 0) 11 | { 12 | throw net_param_exception("epoll create failed!"); 13 | } 14 | ev_fd = eventfd(1, 0); 15 | add(ev_fd, event_type::readable); 16 | } 17 | 18 | event_epoll_demultiplexer::~event_epoll_demultiplexer() 19 | { 20 | close(fd); 21 | close(ev_fd); 22 | } 23 | 24 | void event_epoll_demultiplexer::add(handle_t handle, event_type_t type) 25 | { 26 | int e = 0; 27 | if (type & event_type::readable) 28 | e |= EPOLLIN; 29 | if (type & event_type::writable) 30 | e |= EPOLLOUT; 31 | if (type & event_type::error) 32 | e |= EPOLLERR; 33 | 34 | epoll_event ev; 35 | memset(&ev, 0, sizeof(ev)); 36 | ev.events = e | EPOLLET; 37 | ev.data.fd = handle; 38 | epoll_ctl(fd, EPOLL_CTL_ADD, handle, &ev); 39 | } 40 | 41 | handle_t event_epoll_demultiplexer::select(event_type_t *type, microsecond_t *timeout) 42 | { 43 | epoll_event ev; 44 | int t = *timeout / 1000; 45 | int c = ::epoll_wait(fd, &ev, 1, t); 46 | if (c < 0) 47 | { 48 | return 0; 49 | } 50 | if (c == 0) 51 | { 52 | *timeout = 0; 53 | return 0; 54 | } 55 | int fd = 0; 56 | if (ev.events & EPOLLIN) 57 | { 58 | *type = event_type::readable; 59 | fd = ev.data.fd; 60 | } 61 | else if (ev.events & EPOLLOUT) 62 | { 63 | *type = event_type::writable; 64 | fd = ev.data.fd; 65 | } 66 | else if (ev.events & EPOLLERR) 67 | { 68 | *type = event_type::error; 69 | fd = ev.data.fd; 70 | } 71 | else 72 | { 73 | return 0; 74 | } 75 | if (ev.data.fd == ev_fd) 76 | { 77 | eventfd_t vl; 78 | eventfd_read(fd, &vl); 79 | *timeout = 0; 80 | return 0; 81 | } 82 | 83 | return ev.data.fd; 84 | } 85 | 86 | void event_epoll_demultiplexer::remove(handle_t handle, event_type_t type) 87 | { 88 | int e = 0; 89 | if (type & event_type::readable) 90 | e |= EPOLLIN; 91 | if (type & event_type::writable) 92 | e |= EPOLLOUT; 93 | if (type & event_type::error) 94 | e |= EPOLLERR; 95 | 96 | epoll_event ev; 97 | memset(&ev, 0, sizeof(ev)); 98 | ev.events = e; 99 | ev.data.fd = handle; 100 | epoll_ctl(fd, EPOLL_CTL_DEL, handle, &ev); 101 | } 102 | 103 | void event_epoll_demultiplexer::wake_up(event_loop_t &cur_loop) { eventfd_write(ev_fd, 1); } 104 | 105 | } // namespace net 106 | 107 | #endif -------------------------------------------------------------------------------- /lib/net/event.cc: -------------------------------------------------------------------------------- 1 | #include "net/event.hpp" 2 | #include "net/co.hpp" 3 | #include "net/epoll.hpp" 4 | #include "net/execute_context.hpp" 5 | #include "net/iocp.hpp" 6 | #include "net/select.hpp" 7 | #include "net/socket.hpp" 8 | #include 9 | #include 10 | 11 | namespace net 12 | { 13 | thread_local event_loop_t *thread_in_loop; 14 | 15 | event_loop_t::event_loop_t(microsecond_t precision) 16 | : is_exit(false) 17 | , has_wake_up(false) 18 | , exit_code(0) 19 | { 20 | time_manager = create_time_manager(precision); 21 | thread_in_loop = this; 22 | #ifdef OS_WINDOWS 23 | HANDLE hThreadParent; 24 | DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThreadParent, 0, FALSE, 25 | DUPLICATE_SAME_ACCESS); 26 | handle = hThreadParent; 27 | #endif 28 | } 29 | 30 | event_loop_t::~event_loop_t() 31 | { 32 | thread_in_loop = nullptr; 33 | #ifdef OS_WINDOWS 34 | CloseHandle(handle); 35 | #endif 36 | } 37 | 38 | void event_loop_t::set_demuxer(event_demultiplexer *demuxer) { this->demuxer = demuxer; } 39 | 40 | void event_loop_t::add_event_handler(handle_t handle, event_handler_t *handler) 41 | { 42 | lock::lock_guard g(lock); 43 | event_map[handle] = handler; 44 | } 45 | 46 | void event_loop_t::remove_event_handler(handle_t handle, event_handler_t *handler) 47 | { 48 | unlink(handle, event_type::error | event_type::writable | event_type::readable); 49 | 50 | lock::lock_guard g(lock); 51 | auto it = event_map.find(handle); 52 | if (it != event_map.end()) 53 | event_map.erase(it); 54 | } 55 | 56 | event_loop_t &event_loop_t::link(handle_t handle, event_type_t type) 57 | { 58 | demuxer->add(handle, type); 59 | return *this; 60 | } 61 | 62 | event_loop_t &event_loop_t::unlink(handle_t handle, event_type_t type) 63 | { 64 | demuxer->remove(handle, type); 65 | return *this; 66 | } 67 | 68 | int event_loop_t::run() 69 | { 70 | event_type_t type; 71 | while (!is_exit) 72 | { 73 | microsecond_t cur_time = get_current_time(); 74 | auto target_time = time_manager->next_tick_timepoint(); 75 | if (cur_time >= target_time) 76 | { 77 | time_manager->tick(); 78 | } 79 | dispatcher.dispatch(); 80 | auto next = time_manager->next_tick_timepoint(); 81 | cur_time = get_current_time(); 82 | microsecond_t timeout; 83 | if (next > cur_time) 84 | timeout = next - cur_time; 85 | else 86 | timeout = 0; 87 | 88 | if (is_exit) 89 | break; 90 | 91 | has_wake_up = false; 92 | auto handle = demuxer->select(&type, &timeout); 93 | if (handle != 0) 94 | { 95 | event_handle_map_t::iterator ev_it; 96 | { 97 | lock::lock_guard g(lock); 98 | ev_it = event_map.find(handle); 99 | if (ev_it == event_map.end()) 100 | { 101 | continue; 102 | } 103 | } 104 | auto handler = ev_it->second; 105 | handler->on_event(*context, type); 106 | } 107 | dispatcher.dispatch(); 108 | } 109 | return exit_code; 110 | } 111 | 112 | void event_loop_t::exit(int code) 113 | { 114 | exit_code = code; 115 | is_exit = true; 116 | demuxer->wake_up(*this); 117 | } 118 | 119 | void event_loop_t::wake_up() 120 | { 121 | /// no need for wake in current thread 122 | if (this == thread_in_loop) 123 | return; 124 | if (!has_wake_up) 125 | demuxer->wake_up(*this); 126 | has_wake_up = true; 127 | } 128 | 129 | int event_loop_t::load_factor() { return (int)event_map.size(); } 130 | 131 | event_loop_t &event_loop_t::current() { return *thread_in_loop; } 132 | 133 | void event_context_t::add_executor(execute_context_t *exectx) { exectx->loop = &select_loop(); } 134 | 135 | void event_context_t::add_executor(execute_context_t *exectx, event_loop_t *loop) { exectx->loop = loop; } 136 | 137 | void event_context_t::remove_executor(execute_context_t *exectx) { exectx->loop = nullptr; } 138 | 139 | timer_registered_t event_loop_t::add_timer(timer_t timer) { return time_manager->insert(timer); } 140 | 141 | void event_loop_t::remove_timer(timer_registered_t reg) { time_manager->cancel(reg); } 142 | 143 | execute_thread_dispatcher_t &event_loop_t::get_dispatcher() { return dispatcher; } 144 | 145 | event_loop_t &event_context_t::select_loop() 146 | { 147 | /// get minimum workload loop 148 | event_loop_t *min_load_loop; 149 | { 150 | std::shared_lock lock(loop_mutex); 151 | min_load_loop = loops[0]; 152 | int min_fac = min_load_loop->load_factor(); 153 | for (auto loop : loops) 154 | { 155 | int fac = loop->load_factor(); 156 | if (fac < min_fac) 157 | { 158 | min_fac = fac; 159 | min_load_loop = loop; 160 | } 161 | } 162 | } 163 | 164 | return *min_load_loop; 165 | } 166 | 167 | void event_context_t::do_init() 168 | { 169 | if (thread_in_loop == nullptr && !exit) 170 | { 171 | auto loop = new event_loop_t(precision); 172 | std::unique_lock lock(loop_mutex); 173 | if (exit) 174 | { 175 | return; 176 | } 177 | loop->set_context(this); 178 | event_demultiplexer *demuxer = nullptr; 179 | if (strategy == event_strategy::AUTO) 180 | { 181 | #ifdef OS_WINDOWS 182 | strategy = event_strategy::IOCP; 183 | #else 184 | strategy = event_strategy::epoll; 185 | #endif 186 | } 187 | switch (strategy) 188 | { 189 | case event_strategy::select: 190 | #ifdef OS_WINDOWS 191 | throw std::invalid_argument("not support select on windows"); 192 | #else 193 | demuxer = new event_select_demultiplexer(); 194 | #endif 195 | break; 196 | case event_strategy::epoll: 197 | #ifdef OS_WINDOWS 198 | throw std::invalid_argument("not support epoll on windows"); 199 | #else 200 | demuxer = new event_epoll_demultiplexer(); 201 | #endif 202 | break; 203 | case event_strategy::IOCP: 204 | #ifndef OS_WINDOWS 205 | throw std::invalid_argument("not support epoll on unix/linux"); 206 | #else 207 | if (iocp_handle == 0) 208 | { 209 | iocp_handle = event_iocp_demultiplexer::make(); 210 | } 211 | demuxer = new event_iocp_demultiplexer(iocp_handle); 212 | #endif 213 | break; 214 | default: 215 | throw std::invalid_argument("invalid strategy " + std::to_string(strategy)); 216 | } 217 | loop->set_demuxer(demuxer); 218 | loops.push_back(loop); 219 | loop_counter++; 220 | } 221 | } 222 | 223 | event_context_t::event_context_t(event_strategy strategy, microsecond_t precision) 224 | : strategy(strategy) 225 | , loop_counter(0) 226 | , precision(precision) 227 | , exit(false) 228 | { 229 | #ifdef OS_WINDOWS 230 | iocp_handle = 0; 231 | #endif 232 | do_init(); 233 | } 234 | 235 | int event_context_t::run() 236 | { 237 | do_init(); 238 | if (thread_in_loop == nullptr) 239 | return 0; 240 | 241 | int code = thread_in_loop->run(); 242 | 243 | loop_counter--; 244 | std::unique_lock lock(exit_mutex); 245 | cond.notify_all(); 246 | cond.wait(lock, [this]() { return loop_counter == 0; }); 247 | 248 | return code; 249 | } 250 | 251 | int event_context_t::prepare() 252 | { 253 | do_init(); 254 | return 0; 255 | } 256 | 257 | void event_context_t::exit_all(int code) 258 | { 259 | exit = true; 260 | std::unique_lock lock(loop_mutex); 261 | for (auto &loop : loops) 262 | { 263 | loop->exit(code); 264 | } 265 | } 266 | 267 | event_context_t::~event_context_t() 268 | { 269 | std::unique_lock lock(loop_mutex); 270 | for (auto loop : loops) 271 | { 272 | delete loop->get_demuxer(); 273 | delete loop; 274 | } 275 | #ifdef OS_WINDOWS 276 | if (iocp_handle != 0) 277 | { 278 | event_iocp_demultiplexer::close(iocp_handle); 279 | iocp_handle = 0; 280 | } 281 | #endif 282 | } 283 | 284 | } // namespace net 285 | -------------------------------------------------------------------------------- /lib/net/execute_context.cc: -------------------------------------------------------------------------------- 1 | #include "net/execute_context.hpp" 2 | #include "net/co.hpp" 3 | #include "net/event.hpp" 4 | #include "net/execute_dispatcher.hpp" 5 | namespace net 6 | { 7 | 8 | microsecond_t execute_context_t::sleep(microsecond_t ms) 9 | { 10 | auto cur = get_current_time(); 11 | stop_for(ms); 12 | auto now = get_current_time(); 13 | if (now - cur >= ms) 14 | return 0; 15 | return now - cur; 16 | } 17 | 18 | void execute_context_t::stop() { co::coroutine_t::yield(); } 19 | 20 | void execute_context_t::stop_for(microsecond_t ms, std::function func) 21 | { 22 | if (timer.id >= 0) 23 | loop->remove_timer(timer); 24 | timer = loop->add_timer(make_timer(ms, [this, func]() { 25 | timer.id = -1; 26 | start(); 27 | })); 28 | 29 | co::coroutine_t::yield(); 30 | loop->remove_timer(timer); 31 | func(); 32 | } 33 | 34 | void execute_context_t::stop_for(microsecond_t ms) 35 | { 36 | if (timer.id >= 0) 37 | loop->remove_timer(timer); 38 | timer = loop->add_timer(make_timer(ms, [this]() { 39 | timer.id = -1; 40 | start(); 41 | })); 42 | co::coroutine_t::yield(); 43 | loop->remove_timer(timer); 44 | } 45 | 46 | void execute_context_t::start() 47 | { 48 | loop->get_dispatcher().add(this, std::function()); 49 | wake_up_thread(); 50 | } 51 | 52 | void execute_context_t::start_with(std::function func) 53 | { 54 | loop->get_dispatcher().add(this, func); 55 | wake_up_thread(); 56 | } 57 | 58 | void execute_context_t::run(std::function func) 59 | { 60 | co = co::coroutine_t::create(func); 61 | co->set_execute_context(this); 62 | start(); 63 | } 64 | 65 | void execute_context_t::wake_up_thread() { loop->wake_up(); } 66 | 67 | execute_context_t::execute_context_t() 68 | : co(nullptr) 69 | { 70 | timer.id = -1; 71 | } 72 | 73 | execute_context_t::~execute_context_t() 74 | { 75 | if (co) 76 | { 77 | co->set_execute_context(nullptr); 78 | co->stop(); 79 | loop->get_dispatcher().cancel(this); 80 | } 81 | } 82 | 83 | } // namespace net 84 | -------------------------------------------------------------------------------- /lib/net/execute_dispatcher.cc: -------------------------------------------------------------------------------- 1 | #include "net/execute_dispatcher.hpp" 2 | #include "net/co.hpp" 3 | #include "net/execute_context.hpp" 4 | namespace net 5 | { 6 | 7 | void execute_thread_dispatcher_t::dispatch() 8 | { 9 | std::tuple> exec; 10 | 11 | while (!co_wait_for_resume.empty()) 12 | { 13 | { 14 | lock::lock_guard g(lock); 15 | exec = co_wait_for_resume.front(); 16 | co_wait_for_resume.pop(); 17 | } 18 | 19 | auto fn = std::get>(exec); 20 | auto executor = std::get(exec); 21 | { 22 | lock::lock_guard g(cancel_lock); 23 | if (cancel_contexts.find(executor) != cancel_contexts.end()) 24 | continue; 25 | } 26 | 27 | if (fn) 28 | executor->co->resume_with(std::move(fn)); 29 | else 30 | executor->co->resume(); 31 | } 32 | lock::lock_guard g(cancel_lock); 33 | cancel_contexts.clear(); 34 | } 35 | 36 | void execute_thread_dispatcher_t::add(execute_context_t *econtext, std::function func) 37 | { 38 | lock::lock_guard g(lock); 39 | co_wait_for_resume.emplace(econtext, std::move(func)); 40 | } 41 | 42 | void execute_thread_dispatcher_t::cancel(execute_context_t *econtext) 43 | { 44 | lock::lock_guard g(cancel_lock); 45 | cancel_contexts.insert(econtext); 46 | } 47 | } // namespace net 48 | -------------------------------------------------------------------------------- /lib/net/iocp.cc: -------------------------------------------------------------------------------- 1 | #include "net/iocp.hpp" 2 | #ifdef OS_WINDOWS 3 | 4 | namespace net 5 | { 6 | 7 | io_overlapped::io_overlapped() 8 | { 9 | memset(&overlapped, 0, sizeof(overlapped)); 10 | sock = 0; 11 | addr_len = 0; 12 | err = 0; 13 | buffer_do_len = 0; 14 | data = 0; 15 | wsaBuf.len = 0; 16 | wsaBuf.buf = 0; 17 | done = false; 18 | } 19 | 20 | handle_t event_iocp_demultiplexer::make() { return (int)CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); } 21 | 22 | void event_iocp_demultiplexer::close(handle_t handle) { CloseHandle((HANDLE)handle); } 23 | 24 | event_iocp_demultiplexer::event_iocp_demultiplexer(handle_t h) { iocp_handle = h; } 25 | 26 | event_iocp_demultiplexer::~event_iocp_demultiplexer() {} 27 | 28 | void event_iocp_demultiplexer::add(handle_t handle, event_type_t type) 29 | { 30 | if (map.count(handle) == 0) 31 | { 32 | CreateIoCompletionPort((HANDLE)handle, (HANDLE)iocp_handle, handle, 0); 33 | } 34 | } 35 | 36 | handle_t event_iocp_demultiplexer::select(event_type_t *type, microsecond_t *timeout) 37 | { 38 | DWORD ioSize = 0; 39 | void *key = NULL; 40 | LPOVERLAPPED lpOverlapped = NULL; 41 | int err = 0; 42 | if (FALSE == 43 | GetQueuedCompletionStatus((HANDLE)iocp_handle, &ioSize, (PULONG_PTR)&key, &lpOverlapped, *timeout / 1000)) 44 | { 45 | err = WSAGetLastError(); 46 | } 47 | if ((ioSize == 1) && lpOverlapped == NULL) 48 | { 49 | // wakeup 50 | *timeout = 0; 51 | return 0; 52 | } 53 | io_overlapped *io = (io_overlapped *)lpOverlapped; 54 | // timeout 55 | if (io == nullptr) 56 | { 57 | *timeout = 0; 58 | if (key == nullptr) 59 | { 60 | return 0; 61 | } 62 | return *(int *)key; 63 | } 64 | *type = event_type::def; 65 | io->buffer_do_len = ioSize; 66 | io->err = err; 67 | io->done = true; 68 | return (handle_t)io->sock; 69 | } 70 | 71 | void event_iocp_demultiplexer::remove(handle_t handle, event_type_t type) 72 | { 73 | CancelIo((HANDLE)handle); 74 | if (map.count(handle) == 0) 75 | { 76 | return; 77 | } 78 | map.erase(handle); 79 | } 80 | 81 | void event_iocp_demultiplexer::wake_up(event_loop_t &cur_loop) 82 | { 83 | PostQueuedCompletionStatus((HANDLE)iocp_handle, 1, 0, 0); 84 | } 85 | 86 | } // namespace net 87 | #endif -------------------------------------------------------------------------------- /lib/net/load_balance.cc: -------------------------------------------------------------------------------- 1 | #include "net/load_balance.hpp" 2 | #include "net/event.hpp" 3 | #include "net/socket.hpp" 4 | 5 | namespace net::load_balance 6 | { 7 | 8 | void front_server_t::bind_inner(event_context_t &context, socket_addr_t addr, bool reuse_addr) 9 | { 10 | /// content server join 11 | inner_server.on_client_join([this](tcp::server_t &server, tcp::connection_t conn) { 12 | if (server_handler) 13 | server_handler(*this, conn); 14 | }); 15 | inner_server.listen(context, addr, 10, reuse_addr); 16 | } 17 | 18 | void front_server_t::bind(event_context_t &context, socket_addr_t addr, bool reuse_addr) 19 | { 20 | server.on_client_join([this](tcp::server_t &server, tcp::connection_t conn) { 21 | peer_get_server_request_t request; 22 | peer_get_server_respond_t respond; 23 | socket_buffer_t buffer(sizeof(request)); 24 | buffer.expect().origin_length(); 25 | co::await(tcp::conn_aread, conn, buffer); 26 | endian::cast_to<>(buffer, request); 27 | if (handler) 28 | { 29 | if (handler(*this, request, respond, conn)) 30 | { 31 | socket_buffer_t respond_buffer(sizeof(respond)); 32 | respond_buffer.expect().origin_length(); 33 | endian::save_to<>(respond, respond_buffer); 34 | co::await(tcp::conn_awrite, conn, respond_buffer); 35 | } 36 | } 37 | }); 38 | server.listen(context, addr, 10, reuse_addr); 39 | } 40 | 41 | front_server_t &front_server_t::on_client_request(front_handler_t handler) 42 | { 43 | this->handler = handler; 44 | return *this; 45 | } 46 | 47 | front_server_t &front_server_t::on_inner_server_join(server_join_handler_t handler) 48 | { 49 | this->server_handler = handler; 50 | return *this; 51 | } 52 | 53 | } // namespace net::load_balance 54 | -------------------------------------------------------------------------------- /lib/net/nat.cc: -------------------------------------------------------------------------------- 1 | #include "net/nat.hpp" 2 | #include "net/socket.hpp" 3 | namespace net 4 | { 5 | 6 | void nat_server_t::client_main(tcp::connection_t conn) 7 | { 8 | tcp::package_head_t head; 9 | if (co::await(tcp::conn_aread_packet_head, conn, head) != io_result::ok) 10 | return; 11 | 12 | nat_request_t request; 13 | socket_buffer_t buffer = socket_buffer_t::from_struct(request); 14 | buffer.expect().origin_length(); 15 | if (co::await(tcp::conn_aread_packet_content, conn, buffer) != io_result::ok) 16 | return; 17 | endian::cast_inplace(request, buffer); 18 | } 19 | 20 | void nat_server_t::other_server_main(tcp::connection_t conn) {} 21 | 22 | void nat_server_t::bind(event_context_t &ctx, socket_addr_t server_addr, bool reuse_addr) 23 | { 24 | server.on_client_join(std::bind(&nat_server_t::client_main, this, std::placeholders::_2)); 25 | this->server.listen(ctx, server_addr, 10, reuse_addr); 26 | rudp.bind(ctx); 27 | rudp.on_new_connection([this](rudp_connection_t conn) { 28 | socket_buffer_t buffer(rudp.get_mtu()); 29 | buffer.expect().origin_length(); 30 | co::await(rudp_aread, &rudp, conn, buffer); 31 | }); 32 | } 33 | 34 | void nat_server_t::connect_second_server(event_context_t &ctx, socket_addr_t server_addr) 35 | { 36 | other_server.on_server_connect(std::bind(&nat_server_t::other_server_main, this, std::placeholders::_2)); 37 | other_server.connect(ctx, server_addr, make_timespan(10)); 38 | } 39 | 40 | void net_detector_t::get_nat_type(event_context_t &ctx, socket_addr_t server, handler_t handler) 41 | { 42 | if (is_do_request) 43 | return; 44 | key = rand(); 45 | rudp.bind(ctx); 46 | rudp.on_new_connection([this, handler, server](rudp_connection_t conn) { 47 | if (conn.address == server) 48 | { 49 | nat_udp_request_t request; 50 | request.key = key; 51 | socket_buffer_t buffer = socket_buffer_t::from_struct(request); 52 | buffer.expect().origin_length(); 53 | endian::cast_inplace(request, buffer); 54 | co::await(rudp_awrite, &rudp, conn, buffer); 55 | } 56 | 57 | socket_buffer_t buffer(rudp.get_mtu()); 58 | buffer.expect().origin_length(); 59 | co::await(rudp_aread, &rudp, conn, buffer); 60 | is_do_request = false; 61 | 62 | if (handler) 63 | handler(nat_type::unknown); 64 | 65 | is_do_request = false; 66 | }) 67 | .on_connection_timeout([this, handler](rudp_connection_t conn) { 68 | is_do_request = false; 69 | 70 | if (handler) 71 | handler(nat_type::unknown); 72 | }) 73 | .on_unknown_packet([this](socket_addr_t addr) { 74 | rudp.add_connection(addr, 0, make_timespan(10)); 75 | return true; 76 | }); 77 | 78 | auto udp_port = rudp.get_socket()->local_addr().get_port(); 79 | 80 | client.on_server_connect([udp_port, this, handler, server](tcp::client_t &c, tcp::connection_t conn) { 81 | tcp::package_head_t head; 82 | nat_request_t request; 83 | request.port = c.get_socket()->local_addr().get_port(); 84 | request.ip = c.get_socket()->local_addr().v4_addr(); 85 | request.udp_port = udp_port; 86 | request.key = key; 87 | socket_buffer_t buffer = socket_buffer_t::from_struct(request); 88 | buffer.expect().origin_length(); 89 | endian::cast_inplace(request, buffer); 90 | co::await(tcp::conn_awrite_packet, conn, head, buffer); 91 | 92 | /// wait for close 93 | co::await(tcp::conn_aread_packet_head, conn, head); // the peer closed and it will return io_result::close 94 | 95 | rudp.add_connection(server, 0, make_timespan(10)); // send rudp 96 | }); 97 | 98 | client.connect(ctx, server, make_timespan(10)); 99 | } 100 | 101 | } // namespace net 102 | -------------------------------------------------------------------------------- /lib/net/net.cc: -------------------------------------------------------------------------------- 1 | #include "net/net.hpp" 2 | #include 3 | 4 | namespace net 5 | { 6 | #ifdef OS_WINDOWS 7 | WSAData wsa; 8 | #endif 9 | void init_lib() 10 | { 11 | #ifndef OS_WINDOWS 12 | signal(SIGPIPE, SIG_IGN); 13 | #else 14 | WSAStartup(MAKEWORD(2, 2), &wsa); 15 | #endif 16 | } 17 | void uninit_lib() 18 | { 19 | #ifdef OS_WINDOWS 20 | WSACleanup(); 21 | #endif 22 | } 23 | } // namespace net 24 | -------------------------------------------------------------------------------- /lib/net/select.cc: -------------------------------------------------------------------------------- 1 | #include "net/select.hpp" 2 | #ifndef OS_WINDOWS 3 | namespace net 4 | { 5 | event_select_demultiplexer::event_select_demultiplexer() 6 | { 7 | FD_ZERO(&read_set); 8 | FD_ZERO(&write_set); 9 | FD_ZERO(&error_set); 10 | fd = eventfd(1, 0); 11 | add(fd, event_type::readable); 12 | } 13 | 14 | event_select_demultiplexer::~event_select_demultiplexer() { close(fd); } 15 | 16 | /// XXX: the event will be available when call next 'select'. it must be in this thread, so the problem will not occur 17 | void event_select_demultiplexer::add(handle_t handle, event_type_t type) 18 | { 19 | if (type & event_type::readable) 20 | FD_SET(handle, &read_set); 21 | if (type & event_type::writable) 22 | FD_SET(handle, &write_set); 23 | if (type & event_type::error) 24 | FD_SET(handle, &error_set); 25 | } // namespace net 26 | 27 | handle_t event_select_demultiplexer::select(event_type_t *type, microsecond_t *timeout) 28 | { 29 | fd_set rs = read_set; 30 | fd_set ws = write_set; 31 | fd_set es = error_set; 32 | struct timeval t; 33 | t.tv_sec = *timeout / 1000000; 34 | t.tv_usec = *timeout % 1000000; 35 | /// 0 1 2 is STDIN STDOUT STDERR in linux 36 | auto ret = ::select(FD_SETSIZE + 1, &rs, &ws, &es, &t); 37 | if (ret == -1) 38 | return 0; 39 | if (ret == 0) 40 | { 41 | *timeout = 0; 42 | return 0; 43 | } 44 | for (int i = 3; i < FD_SETSIZE; i++) 45 | { 46 | int j = i; 47 | if (FD_ISSET(j, &rs)) 48 | { 49 | *type = event_type::readable; 50 | } 51 | else if (FD_ISSET(j, &ws)) 52 | { 53 | *type = event_type::writable; 54 | } 55 | else if (FD_ISSET(j, &es)) 56 | { 57 | *type = event_type::error; 58 | } 59 | else 60 | { 61 | continue; 62 | } 63 | if (j == fd) 64 | { 65 | // wake up 66 | eventfd_t vl; 67 | eventfd_read(fd, &vl); 68 | *timeout = 0; 69 | return 0; 70 | } 71 | return j; 72 | } 73 | return 0; 74 | } 75 | 76 | void event_select_demultiplexer::remove(handle_t handle, event_type_t type) 77 | { 78 | if (type & event_type::readable) 79 | FD_CLR(handle, &read_set); 80 | if (type & event_type::writable) 81 | FD_CLR(handle, &write_set); 82 | if (type & event_type::error) 83 | FD_CLR(handle, &error_set); 84 | } 85 | 86 | void event_select_demultiplexer::wake_up(event_loop_t &cur_loop) { eventfd_write(fd, 1); } 87 | 88 | } // namespace net 89 | 90 | #endif -------------------------------------------------------------------------------- /lib/net/socket_addr.cc: -------------------------------------------------------------------------------- 1 | #include "net/socket_addr.hpp" 2 | #include 3 | #include 4 | #include 5 | namespace net 6 | { 7 | socket_addr_t::socket_addr_t() 8 | : so_addr({}) 9 | { 10 | so_addr.sin_zero[0] = 1; 11 | } 12 | 13 | socket_addr_t::socket_addr_t(int port) 14 | : so_addr({}) 15 | { 16 | so_addr.sin_family = AF_INET; 17 | so_addr.sin_addr.s_addr = htonl(INADDR_ANY); 18 | so_addr.sin_port = htons(port); 19 | } 20 | 21 | socket_addr_t::socket_addr_t(u32 addr, int port) 22 | : so_addr({}) 23 | { 24 | so_addr.sin_family = AF_INET; 25 | so_addr.sin_addr.s_addr = htonl(addr); 26 | so_addr.sin_port = htons(port); 27 | } 28 | 29 | socket_addr_t::socket_addr_t(sockaddr_in addr) 30 | : so_addr(addr) 31 | { 32 | } 33 | 34 | socket_addr_t::socket_addr_t(std::string addr, int port) 35 | : so_addr({}) 36 | { 37 | so_addr.sin_family = AF_INET; 38 | so_addr.sin_addr.s_addr = inet_addr(addr.c_str()); 39 | so_addr.sin_port = htons(port); 40 | } 41 | 42 | std::string socket_addr_t::get_addr() const 43 | { 44 | if (so_addr.sin_zero[0]) 45 | return "invalid address"; 46 | return inet_ntoa(so_addr.sin_addr); 47 | } 48 | 49 | std::string socket_addr_t::to_string() const 50 | { 51 | std::stringstream ss; 52 | ss << get_addr() << ":" << get_port(); 53 | return ss.str(); 54 | } 55 | 56 | u32 socket_addr_t::v4_addr() const { return ntohl(so_addr.sin_addr.s_addr); } 57 | 58 | int socket_addr_t::get_port() const { return ntohs(so_addr.sin_port); } 59 | 60 | } // namespace net -------------------------------------------------------------------------------- /lib/net/socket_buffer.cc: -------------------------------------------------------------------------------- 1 | #include "net/socket_buffer.hpp" 2 | #include 3 | 4 | namespace net 5 | { 6 | socket_buffer_t::except_buffer_helper_t socket_buffer_t::except_buffer_helper_t::length(u64 len) 7 | { 8 | buf->valid_data_length = len; 9 | buf->walk_offset = 0; 10 | return *this; 11 | } 12 | 13 | socket_buffer_t::except_buffer_helper_t socket_buffer_t::except_buffer_helper_t::origin_length() 14 | { 15 | buf->valid_data_length = buf->buffer_size; 16 | buf->walk_offset = 0; 17 | return *this; 18 | } 19 | 20 | socket_buffer_t::socket_buffer_t() 21 | : ptr(nullptr) 22 | , header(nullptr) 23 | , buffer_size(0) 24 | , valid_data_length(0) 25 | , walk_offset(0) 26 | { 27 | } 28 | 29 | socket_buffer_t::socket_buffer_t(u64 len) 30 | : ptr(new byte[len]) 31 | , header(new socket_buffer_header_t()) 32 | , buffer_size(len) 33 | , valid_data_length(0) 34 | , walk_offset(0) 35 | { 36 | header->ref_count = 1; 37 | } 38 | 39 | socket_buffer_t socket_buffer_t::from_struct_inner(byte *buffer_ptr, u64 buffer_length) 40 | { 41 | socket_buffer_t buffer(buffer_ptr, buffer_length); 42 | return std::move(buffer); 43 | } 44 | 45 | socket_buffer_t socket_buffer_t::from_string(std::string str) 46 | { 47 | socket_buffer_t buffer(str.size()); 48 | memcpy(buffer.ptr, str.c_str(), str.size()); 49 | return std::move(buffer); 50 | } 51 | 52 | socket_buffer_t::socket_buffer_t(byte *buffer_ptr, u64 buffer_length) 53 | : ptr(buffer_ptr) 54 | , header(nullptr) 55 | , buffer_size(buffer_length) 56 | , valid_data_length(0) 57 | , walk_offset(0) 58 | { 59 | } 60 | 61 | socket_buffer_t::socket_buffer_t(const socket_buffer_t &rh) 62 | { 63 | this->ptr = rh.ptr; 64 | this->valid_data_length = rh.valid_data_length; 65 | this->buffer_size = rh.buffer_size; 66 | this->walk_offset = rh.walk_offset; 67 | this->header = rh.header; 68 | if (this->header) 69 | this->header->ref_count++; 70 | } 71 | 72 | socket_buffer_t &socket_buffer_t::operator=(const socket_buffer_t &rh) 73 | { 74 | if (&rh == this) 75 | return *this; 76 | 77 | this->ptr = rh.ptr; 78 | this->valid_data_length = rh.valid_data_length; 79 | this->buffer_size = rh.buffer_size; 80 | this->walk_offset = rh.walk_offset; 81 | this->header = rh.header; 82 | if (this->header) 83 | this->header->ref_count++; 84 | return *this; 85 | } 86 | 87 | socket_buffer_t::socket_buffer_t(socket_buffer_t &&buffer) 88 | { 89 | this->ptr = buffer.ptr; 90 | this->valid_data_length = buffer.valid_data_length; 91 | this->buffer_size = buffer.buffer_size; 92 | this->walk_offset = buffer.walk_offset; 93 | this->header = buffer.header; 94 | 95 | buffer.ptr = nullptr; 96 | buffer.header = nullptr; 97 | buffer.valid_data_length = 0; 98 | buffer.buffer_size = 0; 99 | buffer.walk_offset = 0; 100 | } 101 | 102 | socket_buffer_t &socket_buffer_t::operator()(socket_buffer_t &&buffer) 103 | { 104 | if (this->ptr) 105 | delete[] ptr; 106 | 107 | this->ptr = buffer.ptr; 108 | this->valid_data_length = buffer.valid_data_length; 109 | this->buffer_size = buffer.buffer_size; 110 | this->walk_offset = buffer.walk_offset; 111 | this->header = buffer.header; 112 | 113 | buffer.ptr = nullptr; 114 | buffer.header = nullptr; 115 | buffer.valid_data_length = 0; 116 | buffer.buffer_size = 0; 117 | buffer.walk_offset = 0; 118 | 119 | return *this; 120 | } 121 | 122 | socket_buffer_t::~socket_buffer_t() 123 | { 124 | if (ptr && header && --header->ref_count == 0) 125 | { 126 | delete[] ptr; 127 | delete header; 128 | } 129 | } 130 | 131 | long socket_buffer_t::write_string(const std::string &str) 132 | { 133 | auto len = str.size(); 134 | if (len > get_length()) 135 | len = get_length(); 136 | 137 | memcpy(get(), str.c_str(), len); 138 | 139 | return len; 140 | } 141 | 142 | std::string socket_buffer_t::to_string() const 143 | { 144 | std::string str; 145 | str.resize(get_length()); 146 | byte *start_ptr = get(); 147 | for (long i = 0; i < str.size(); i++) 148 | { 149 | str[i] = *((char *)start_ptr + i); 150 | } 151 | return str; 152 | } 153 | 154 | void socket_buffer_t::clear() 155 | { 156 | if (ptr) 157 | { 158 | auto start_ptr = get(); 159 | auto size = get_length(); 160 | memset(start_ptr, 0, size); 161 | } 162 | } 163 | 164 | } // namespace net 165 | -------------------------------------------------------------------------------- /lib/net/tcp.cc: -------------------------------------------------------------------------------- 1 | #include "net/tcp.hpp" 2 | #include "net/socket.hpp" 3 | #include 4 | #include 5 | 6 | namespace net::tcp 7 | { 8 | 9 | co::async_result_t connection_t::awrite(co::paramter_t ¶m, socket_buffer_t &buffer) 10 | { 11 | return socket_awrite(param, socket, buffer); 12 | } 13 | 14 | co::async_result_t connection_t::aread(co::paramter_t ¶m, socket_buffer_t &buffer) 15 | { 16 | return socket_aread(param, socket, buffer); 17 | } 18 | 19 | /// wait next packet and read tcp application head 20 | co::async_result_t connection_t::aread_packet_head(co::paramter_t ¶m, package_head_t &head, 21 | socket_buffer_t &buffer) 22 | { 23 | assert((char *)buffer.get() == (char *)&head); 24 | /// first read version bytes 25 | auto ret = socket_aread(param, socket, buffer); 26 | if (ret.is_finish()) 27 | { 28 | endian::cast_inplace(head, buffer); 29 | return ret(); 30 | } 31 | return ret; 32 | } 33 | 34 | co::async_result_t connection_t::aread_packet_content(co::paramter_t ¶m, socket_buffer_t &buffer) 35 | { 36 | return socket_aread(param, socket, buffer); 37 | } 38 | 39 | template 40 | co::async_result_t set_head_and_send(co::paramter_t ¶m, E &head, u64 buf_size, socket_t *socket) 41 | { 42 | int head_buffer_size = sizeof(E); 43 | head.size = buf_size; 44 | socket_buffer_t head_buffer(head_buffer_size); 45 | 46 | head_buffer.expect().origin_length(); 47 | endian::save_to(head, head_buffer); 48 | auto ret = socket_awrite(param, socket, head_buffer); 49 | if (ret.is_finish()) 50 | { 51 | if (ret() == io_result::ok) 52 | { 53 | return ret; 54 | } 55 | } 56 | return ret; 57 | } 58 | 59 | co::async_result_t connection_t::awrite_packet(co::paramter_t ¶m, package_head_t &head, 60 | socket_buffer_t &buffer) 61 | { 62 | int head_buffer_size = sizeof(head); 63 | head.size = buffer.get_length(); 64 | socket_buffer_t head_buffer(head_buffer_size); 65 | 66 | head_buffer.expect().origin_length(); 67 | endian::save_to(head, head_buffer); 68 | auto ret = co::await_p(std::ref(param), socket_awrite, socket, head_buffer); 69 | if (ret != io_result::ok) 70 | { 71 | return ret; 72 | } 73 | 74 | return co::await_p(std::ref(param), socket_awrite, socket, buffer); 75 | } 76 | 77 | co::async_result_t conn_awrite(co::paramter_t ¶m, connection_t conn, socket_buffer_t &buffer) 78 | { 79 | return conn.awrite(param, buffer); 80 | } 81 | co::async_result_t conn_aread(co::paramter_t ¶m, connection_t conn, socket_buffer_t &buffer) 82 | { 83 | return conn.aread(param, buffer); 84 | } 85 | co::async_result_t conn_aread_packet_head(co::paramter_t ¶m, connection_t conn, package_head_t &head) 86 | { 87 | auto buf = socket_buffer_t::from_struct(head); 88 | buf.expect().origin_length(); 89 | return conn.aread_packet_head(param, head, buf); 90 | } 91 | co::async_result_t conn_aread_packet_content(co::paramter_t ¶m, connection_t conn, 92 | socket_buffer_t &buffer) 93 | { 94 | return conn.aread_packet_content(param, buffer); 95 | } 96 | co::async_result_t conn_awrite_packet(co::paramter_t ¶m, connection_t conn, package_head_t &head, 97 | socket_buffer_t &buffer) 98 | { 99 | return conn.awrite_packet(param, head, buffer); 100 | } 101 | 102 | server_t::server_t() 103 | : server_socket(nullptr) 104 | { 105 | } 106 | 107 | server_t::~server_t() { close_server(); } 108 | 109 | void server_t::client_main(socket_t *socket) 110 | { 111 | auto remote = socket->remote_addr(); 112 | try 113 | { 114 | if (join_handler) 115 | join_handler(*this, socket); 116 | } catch (net::net_connect_exception &e) 117 | { 118 | if (error_handler) 119 | error_handler(*this, socket, remote, e.get_state()); 120 | } 121 | exit_client(socket); 122 | } 123 | 124 | void server_t::wait_client() 125 | { 126 | while (1) 127 | { 128 | auto socket = co::await(accept_from, server_socket); 129 | socket->bind_context(*context); 130 | socket->run(std::bind(&server_t::client_main, this, socket)); 131 | socket->wake_up_thread(); 132 | } 133 | } 134 | 135 | void server_t::listen(event_context_t &context, socket_addr_t address, int max_client, bool reuse_addr) 136 | { 137 | if (server_socket != nullptr) 138 | return; 139 | this->context = &context; 140 | server_socket = new_tcp_socket(); 141 | if (reuse_addr) 142 | reuse_addr_socket(server_socket, true); 143 | 144 | listen_from(bind_at(server_socket, address), max_client); 145 | 146 | server_socket->bind_context(context); 147 | server_socket->run(std::bind(&server_t::wait_client, this)); 148 | } 149 | 150 | void server_t::exit_client(socket_t *client) 151 | { 152 | assert(client != server_socket); 153 | 154 | if (!client) 155 | return; 156 | if (exit_handler) 157 | exit_handler(*this, client); 158 | 159 | client->unbind_context(); 160 | close_socket(client); 161 | } 162 | 163 | void server_t::close_server() 164 | { 165 | if (!server_socket) 166 | return; 167 | server_socket->unbind_context(); 168 | close_socket(server_socket); 169 | server_socket = nullptr; 170 | } 171 | 172 | server_t &server_t::on_client_join(handler_t handler) 173 | { 174 | join_handler = handler; 175 | return *this; 176 | } 177 | 178 | server_t &server_t::on_client_exit(handler_t handler) 179 | { 180 | exit_handler = handler; 181 | return *this; 182 | } 183 | 184 | server_t &server_t::on_client_error(error_handler_t handler) 185 | { 186 | error_handler = handler; 187 | return *this; 188 | } 189 | 190 | client_t::client_t() 191 | : socket(nullptr) 192 | { 193 | } 194 | 195 | client_t::~client_t() { close(); } 196 | 197 | void client_t::wait_server(socket_addr_t address, microsecond_t timeout) 198 | { 199 | auto ret = co::await_timeout(timeout, connect_to, socket, address); 200 | if (io_result::ok == ret) 201 | { 202 | try 203 | { 204 | if (join_handler) 205 | join_handler(*this, socket); 206 | } catch (net::net_connect_exception &e) 207 | { 208 | if (error_handler) 209 | error_handler(*this, socket, address, e.get_state()); 210 | } 211 | } 212 | else 213 | { 214 | if (error_handler) 215 | { 216 | if (io_result::timeout == ret) 217 | error_handler(*this, socket, address, connection_state::timeout); 218 | else if (io_result::failed == ret) 219 | error_handler(*this, socket, address, connection_state::connection_refuse); 220 | else 221 | error_handler(*this, socket, address, connection_state::closed); 222 | } 223 | } 224 | close(); 225 | } 226 | 227 | void client_t::connect(event_context_t &context, socket_addr_t address, microsecond_t timeout) 228 | { 229 | if (socket != nullptr) 230 | return; 231 | this->context = &context; 232 | socket = new_tcp_socket(); 233 | connect_addr = address; 234 | socket->bind_context(context); 235 | socket->run(std::bind(&client_t::wait_server, this, address, timeout)); 236 | socket->wake_up_thread(); 237 | } 238 | 239 | client_t &client_t::on_server_connect(handler_t handler) 240 | { 241 | join_handler = handler; 242 | return *this; 243 | } 244 | 245 | client_t &client_t::on_server_disconnect(handler_t handler) 246 | { 247 | exit_handler = handler; 248 | return *this; 249 | } 250 | 251 | client_t &client_t::on_server_error(error_handler_t handler) 252 | { 253 | error_handler = handler; 254 | return *this; 255 | } 256 | 257 | void client_t::close() 258 | { 259 | if (!socket) 260 | return; 261 | if (exit_handler) 262 | exit_handler(*this, socket); 263 | socket->unbind_context(); 264 | 265 | close_socket(socket); 266 | socket = nullptr; 267 | } 268 | 269 | bool client_t::is_connect() const { return socket && socket->is_connection_alive(); } 270 | 271 | } // namespace net::tcp 272 | -------------------------------------------------------------------------------- /lib/net/thread_pool.cc: -------------------------------------------------------------------------------- 1 | #include "net/thread_pool.hpp" 2 | namespace net 3 | { 4 | 5 | void thread_pool_t::wrapper() 6 | { 7 | while (!exit || !empty()) 8 | { 9 | std::function task; 10 | counter++; 11 | { 12 | std::unique_lock lock(mutex); 13 | cond.wait(lock, [this]() { return !this->empty() || exit; }); 14 | if (exit && empty()) 15 | return; 16 | task = std::move(tasks.front()); 17 | tasks.pop(); 18 | } 19 | counter--; 20 | task(); 21 | } 22 | } 23 | 24 | thread_pool_t::thread_pool_t(int count) 25 | : exit(false) 26 | , counter(0) 27 | { 28 | for (auto i = 0; i < count; i++) 29 | { 30 | std::thread thread(std::bind(&thread_pool_t::wrapper, this)); 31 | threads.emplace_back(std::move(thread)); 32 | } 33 | } 34 | 35 | thread_pool_t::~thread_pool_t() 36 | { 37 | exit = true; 38 | cond.notify_all(); 39 | 40 | for (auto &i : threads) 41 | { 42 | i.join(); 43 | } 44 | } 45 | 46 | void thread_pool_t::commit(std::function task) 47 | { 48 | if (exit) // don't push task 49 | return; 50 | { 51 | std::unique_lock lock(mutex); 52 | tasks.emplace(task); 53 | } 54 | cond.notify_one(); 55 | } 56 | 57 | /// return idle thread count 58 | int thread_pool_t::get_idles() const { return counter; } 59 | 60 | /// return true if there are no tasks in task queue and no threads are running tasks. 61 | bool thread_pool_t::empty() const { return tasks.empty(); } 62 | 63 | } // namespace net -------------------------------------------------------------------------------- /lib/net/timer.cc: -------------------------------------------------------------------------------- 1 | #include "net/timer.hpp" 2 | #include 3 | #include 4 | 5 | namespace net 6 | { 7 | timer_t make_timer(microsecond_t span, timer_callback_t callback) 8 | { 9 | auto cur = get_current_time(); 10 | if (std::numeric_limits::max() - span < cur) // overflow 11 | return timer_t(std::numeric_limits::max(), callback); 12 | 13 | return timer_t(span + cur, callback); 14 | } 15 | 16 | std::unique_ptr create_time_manager(microsecond_t precision) 17 | { 18 | std::unique_ptr time_wheel = std::make_unique(); 19 | time_wheel->precision = precision; 20 | 21 | return std::move(time_wheel); 22 | } 23 | 24 | void time_manager_t::tick() 25 | { 26 | auto us = get_current_time(); 27 | while (!queue.empty()) 28 | { 29 | auto timers = queue.top(); 30 | if (timers->timepoint > us) 31 | { 32 | break; 33 | } 34 | queue.pop(); 35 | for (auto &i : timers->callbacks) 36 | { 37 | if (i.second) 38 | { 39 | i.first(); 40 | } 41 | } 42 | map.erase(timers->timepoint); 43 | delete timers; 44 | } 45 | } 46 | 47 | timer_registered_t time_manager_t::insert(timer_t timer) 48 | { 49 | if (std::numeric_limits::max() - timer.timepoint < precision - 1) // overflow 50 | { 51 | } 52 | else 53 | { 54 | timer.timepoint = (timer.timepoint + precision - 1) / precision * precision; // alignment percistion 55 | } 56 | 57 | auto it = map.find(timer.timepoint); 58 | if (it == map.end()) 59 | { 60 | it = map.emplace(timer.timepoint, new timer_slot_t(timer.timepoint)).first; 61 | queue.push(it->second); 62 | } 63 | 64 | it->second->callbacks.emplace_back(timer.callback, true); 65 | return {(timer_id)it->second->callbacks.size(), timer.timepoint}; 66 | } 67 | 68 | void time_manager_t::cancel(timer_registered_t reg) 69 | { 70 | auto it = map.find(reg.timepoint); 71 | if (it != map.end()) 72 | { 73 | it->second->callbacks[reg.id - 1].second = false; 74 | } 75 | } 76 | 77 | microsecond_t time_manager_t::next_tick_timepoint() 78 | { 79 | if (!queue.empty()) 80 | { 81 | auto timer = queue.top(); 82 | return timer->timepoint; 83 | } 84 | 85 | return 0xFFFFFFFFFFFFFFFFLLU; 86 | } 87 | 88 | microsecond_t get_timestamp() 89 | { 90 | return std::chrono::time_point_cast(std::chrono::system_clock::now()) 91 | .time_since_epoch() 92 | .count(); 93 | } 94 | 95 | microsecond_t get_current_time() 96 | { 97 | #ifndef OS_WINDOWS 98 | struct timeval timeval; 99 | gettimeofday(&timeval, nullptr); 100 | return timeval.tv_usec + (microsecond_t)timeval.tv_sec * 1000000; 101 | #else 102 | return get_timestamp(); 103 | 104 | #endif 105 | } 106 | 107 | } // namespace net -------------------------------------------------------------------------------- /lib/net/udp.cc: -------------------------------------------------------------------------------- 1 | #include "net/udp.hpp" 2 | #include "net/socket.hpp" 3 | namespace net::udp 4 | { 5 | socket_t *server_t::bind(event_context_t &context, socket_addr_t addr, bool reuse_port) 6 | { 7 | this->context = &context; 8 | socket = new_udp_socket(); 9 | socket->bind_context(context); 10 | if (reuse_port) 11 | reuse_port_socket(socket, true); 12 | bind_at(socket, addr); 13 | return socket; 14 | } 15 | 16 | void server_t::run(std::function func) 17 | { 18 | socket->run([func, this]() { 19 | func(); 20 | close(); 21 | }); 22 | socket->wake_up_thread(); 23 | } 24 | 25 | server_t::~server_t() { close(); } 26 | 27 | void server_t::close() 28 | { 29 | if (!socket) 30 | return; 31 | socket->unbind_context(); 32 | 33 | close_socket(socket); 34 | socket = nullptr; 35 | } 36 | 37 | client_t::~client_t() { close(); } 38 | 39 | void client_t::connect(event_context_t &context, socket_addr_t addr, bool remote_address_bind_to_socket) 40 | { 41 | this->context = &context; 42 | connect_addr = addr; 43 | socket = new_udp_socket(); 44 | socket->bind_context(context); 45 | if (remote_address_bind_to_socket) 46 | { 47 | /// connect udp must return immediately. 48 | co::await(connect_udp, socket, addr); 49 | } 50 | } 51 | 52 | void client_t::run(std::function func) 53 | { 54 | socket->run([func, this]() { 55 | func(); 56 | close(); 57 | }); 58 | socket->wake_up_thread(); 59 | } 60 | 61 | socket_addr_t client_t::get_address() const { return connect_addr; } 62 | 63 | void client_t::close() 64 | { 65 | if (!socket) 66 | return; 67 | socket->unbind_context(); 68 | 69 | close_socket(socket); 70 | socket = nullptr; 71 | } 72 | 73 | } // namespace net::udp 74 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(client) 2 | add_subdirectory(source-client) 3 | add_subdirectory(tracker-server) 4 | add_subdirectory(edge-server) 5 | -------------------------------------------------------------------------------- /src/client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE DIR_SRCS *.cc *.cpp *.CC *.CPP) 2 | file(GLOB_RECURSE WINDOW_SRCS *-window.cc *-window.cpp *-window.CC *-window.CPP) 3 | file(GLOB_RECURSE WINDOW_UI *-window.ui *-window.UI) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_AUTOUIC ON) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) 10 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 11 | 12 | if (MSVC) 13 | add_executable(client WIN32 ${DIR_SRCS} ${WINDOW_UI}) 14 | else() 15 | add_executable(client ${DIR_SRCS} ${WINDOW_UI}) 16 | endif(MSVC) 17 | 18 | find_package(GLOG NO_MODULE REQUIRED) 19 | 20 | target_include_directories(client PUBLIC ${QT_INCLUDES} ) 21 | target_include_directories(client PUBLIC ${FFMPEG_INCLUDE_DIR}) 22 | 23 | target_link_libraries(client PUBLIC ${FFMPEG_LIBRARY} Qt5::Widgets Qt5::Core Qt5::Gui net glog::glog gflags Threads::Threads) 24 | 25 | add_custom_command(TARGET client POST_BUILD 26 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 27 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 28 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /src/client/main-window.cc: -------------------------------------------------------------------------------- 1 | #include "main-window.hpp" 2 | #include "ui_main-window.h" 3 | #include "yuvwidget.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | extern "C" { 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | } 20 | 21 | void MainWindow::video_main() 22 | { 23 | 24 | if (avcodec_open2(video_codec, avcodec_find_decoder(video_codec->codec_id), NULL) < 0) 25 | { 26 | LOG(FATAL) << "Could not open video codec.\n"; 27 | exit(-1); 28 | } 29 | 30 | AVPacket *packet; 31 | AVFrame *frame, *frameYUV; 32 | if (target_width == 0) 33 | { 34 | target_width = video_codec->width; 35 | } 36 | if (target_height == 0) 37 | { 38 | target_height = video_codec->height; 39 | } 40 | 41 | AVPixelFormat format = AV_PIX_FMT_YUV420P; 42 | AVPixelFormat pixFormat; 43 | switch (video_codec->pix_fmt) 44 | { 45 | case AV_PIX_FMT_YUVJ420P: 46 | pixFormat = AV_PIX_FMT_YUV420P; 47 | break; 48 | case AV_PIX_FMT_YUVJ422P: 49 | pixFormat = AV_PIX_FMT_YUV422P; 50 | break; 51 | case AV_PIX_FMT_YUVJ444P: 52 | pixFormat = AV_PIX_FMT_YUV444P; 53 | break; 54 | case AV_PIX_FMT_YUVJ440P: 55 | pixFormat = AV_PIX_FMT_YUV440P; 56 | break; 57 | default: 58 | pixFormat = video_codec->pix_fmt; 59 | break; 60 | } 61 | 62 | LOG(INFO) << "video: width: " << video_codec->width << ",height: " << video_codec->height 63 | << ",format: " << video_codec->pix_fmt << ",desc: " << video_codec->codec_descriptor->long_name; 64 | 65 | auto sws_ctx = sws_getContext(video_codec->width, video_codec->height, pixFormat, target_width, target_height, 66 | format, SWS_BILINEAR, NULL, NULL, NULL); 67 | packet = av_packet_alloc(); 68 | frame = av_frame_alloc(); 69 | frameYUV = av_frame_alloc(); 70 | auto vsize = avpicture_get_size(format, video_codec->width, video_codec->height); 71 | 72 | char *out_buffer = new char[vsize]; 73 | avpicture_fill((AVPicture *)frameYUV, (const uint8_t *)out_buffer, format, video_codec->width, video_codec->height); 74 | auto size_byte = video_codec->width * video_codec->height; 75 | while (run) 76 | { 77 | if (av_read_frame(video_format_ctx, packet) < 0) 78 | { 79 | LOG(INFO) << "read from stream failed"; 80 | continue; 81 | } 82 | int dec_frame = 0; 83 | auto ret = avcodec_decode_video2(video_codec, frame, &dec_frame, packet); 84 | 85 | if (ret < 0) 86 | { 87 | LOG(INFO) << "decode from stream failed"; 88 | continue; 89 | } 90 | sws_scale(sws_ctx, (const unsigned char *const *)frame->data, frame->linesize, 0, video_codec->height, 91 | frameYUV->data, frameYUV->linesize); 92 | std::shared_ptr dataframe(new char[vsize]); 93 | int offset = 0; 94 | memcpy(dataframe.get(), frameYUV->data[0], size_byte); 95 | offset += size_byte; 96 | memcpy(dataframe.get() + offset, frameYUV->data[1], size_byte >> 2); 97 | offset += size_byte >> 2; 98 | memcpy(dataframe.get() + offset, frameYUV->data[2], size_byte >> 2); 99 | 100 | QMetaObject::invokeMethod(ui->display, "update_frame", Qt::QueuedConnection, 101 | Q_ARG(std::shared_ptr, std::move(dataframe)), Q_ARG(int, frame->width), 102 | Q_ARG(int, frame->height)); 103 | av_free_packet(packet); 104 | } 105 | av_free_packet(packet); 106 | av_frame_free(&frameYUV); 107 | av_frame_free(&frame); 108 | sws_freeContext(sws_ctx); 109 | delete[] out_buffer; 110 | 111 | avcodec_close(video_codec); 112 | } 113 | 114 | void MainWindow::audio_main() 115 | { 116 | if (avcodec_open2(audio_codec, avcodec_find_decoder(audio_codec->codec_id), NULL) < 0) 117 | { 118 | LOG(FATAL) << "Could not open audio codec.\n"; 119 | exit(-1); 120 | } 121 | LOG(INFO) << "audio: " 122 | << ",desc: " << audio_codec->codec_descriptor->long_name; 123 | } 124 | 125 | void MainWindow::get_device() 126 | { 127 | avdevice_register_all(); 128 | avcodec_register_all(); 129 | av_register_all(); 130 | AVFormatContext *format_ctx = nullptr; 131 | 132 | std::unordered_map> devs = { 133 | {"video4linux2", {"/dev/video0", "/dev/video1"}}, 134 | {"alsa", {"/dev/audio0", "/dev/audio1", "hw:0", "hw:1", "hw2"}}}; 135 | 136 | for (auto &it : devs) 137 | { 138 | auto [name, dev_name] = it; 139 | AVInputFormat *ifmt = av_find_input_format(name.c_str()); 140 | if (!ifmt) 141 | continue; 142 | for (auto dev : dev_name) 143 | { 144 | if (!format_ctx) 145 | format_ctx = avformat_alloc_context(); 146 | if (avformat_open_input(&format_ctx, dev.c_str(), ifmt, NULL) != 0) 147 | { 148 | LOG(INFO) << "Couldn't open input stream. " << name << ":" << dev; 149 | continue; 150 | } 151 | 152 | if (avformat_find_stream_info(format_ctx, NULL) < 0) 153 | { 154 | LOG(INFO) << "Couldn't find stream information.\n"; 155 | } 156 | bool used = false; 157 | for (int i = 0; i < format_ctx->nb_streams; i++) 158 | { 159 | if (format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && videoindex == -1) 160 | { 161 | videoindex = i; 162 | video_codec = format_ctx->streams[i]->codec; 163 | video_format_ctx = format_ctx; 164 | format_ctx = nullptr; 165 | used = true; 166 | break; 167 | } 168 | else if (format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO && audioindex == -1) 169 | { 170 | audioindex = i; 171 | audio_codec = format_ctx->streams[i]->codec; 172 | audio_format_ctx = format_ctx; 173 | format_ctx = nullptr; 174 | used = true; 175 | break; 176 | } 177 | } 178 | if (videoindex >= 0 && audioindex >= 0) 179 | break; 180 | } 181 | } 182 | 183 | if (videoindex == -1) 184 | { 185 | LOG(FATAL) << "Couldn't find video stream.\n"; 186 | } 187 | 188 | if (audioindex == -1) 189 | { 190 | LOG(ERROR) << "Couldn't find audio stream.\n"; 191 | } 192 | } 193 | 194 | MainWindow::MainWindow(QWidget *parent) 195 | : QMainWindow(parent) 196 | , ui(new Ui::MainWindow) 197 | , target_width(0) 198 | , target_height(0) 199 | , run(true) 200 | , videoindex(-1) 201 | , audioindex(-1) 202 | { 203 | qRegisterMetaType>("std::shared_ptr"); 204 | 205 | ui->setupUi(this); 206 | get_device(); 207 | video_thread = std::thread(std::bind(&MainWindow::video_main, this)); 208 | audio_thread = std::thread(std::bind(&MainWindow::audio_main, this)); 209 | } 210 | 211 | MainWindow::~MainWindow() 212 | { 213 | run = false; 214 | video_thread.join(); 215 | audio_thread.join(); 216 | avformat_close_input(&video_format_ctx); 217 | avformat_free_context(video_format_ctx); 218 | avformat_close_input(&audio_format_ctx); 219 | avformat_free_context(audio_format_ctx); 220 | delete ui; 221 | } 222 | -------------------------------------------------------------------------------- /src/client/main-window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | extern "C" { 9 | #include 10 | } 11 | namespace Ui 12 | { 13 | class MainWindow; 14 | } 15 | 16 | class MainWindow : public QMainWindow 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit MainWindow(QWidget *parent = 0); 21 | ~MainWindow(); 22 | void video_main(); 23 | void audio_main(); 24 | void get_device(); 25 | 26 | private: 27 | Ui::MainWindow *ui; 28 | std::thread video_thread, audio_thread; 29 | int target_width; 30 | int target_height; 31 | bool run; 32 | AVFormatContext *video_format_ctx, *audio_format_ctx; 33 | int videoindex; 34 | int audioindex; 35 | AVCodecContext *video_codec, *audio_codec; 36 | }; 37 | -------------------------------------------------------------------------------- /src/client/main-window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 755 10 | 499 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 0 22 | 755 23 | 25 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | yuvWidget 32 | QWidget 33 |
yuvwidget.hpp
34 |
35 |
36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /src/client/main.cc: -------------------------------------------------------------------------------- 1 | #include "main-window.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void atexit_func() { google::ShutdownGoogleLogging(); } 9 | int main(int argc, char *argv[]) 10 | { 11 | google::InitGoogleLogging(argv[0]); 12 | google::ParseCommandLineFlags(&argc, &argv, false); 13 | google::SetLogDestination(google::GLOG_FATAL, "./client.fatal.log"); 14 | google::SetLogDestination(google::GLOG_ERROR, "./client.error.log"); 15 | google::SetLogDestination(google::GLOG_INFO, "./client.info.log"); 16 | google::SetLogDestination(google::GLOG_WARNING, "./client.warning.log"); 17 | google::SetStderrLogging(google::GLOG_INFO); 18 | 19 | atexit(atexit_func); 20 | QApplication app(argc, argv); 21 | MainWindow mainWindow; 22 | mainWindow.show(); 23 | return app.exec(); 24 | } 25 | -------------------------------------------------------------------------------- /src/client/peer.cc: -------------------------------------------------------------------------------- 1 | #include "peer.hpp" 2 | #include "net/event.hpp" 3 | #include "net/p2p/peer.hpp" 4 | #include "net/p2p/tracker.hpp" 5 | 6 | using namespace net; 7 | using namespace net::p2p; 8 | 9 | peer_t *globl_peer; 10 | 11 | void thread_main(u64 sid, socket_addr_t tserver_addr, microsecond_t timeout) 12 | { 13 | event_context_t context(event_strategy::epoll); 14 | auto tracker_client = std::make_unique(); 15 | auto peer = std::make_unique(sid); 16 | 17 | tracker_client->config(false, sid, ""); 18 | tracker_client->connect_server(context, tserver_addr, timeout); 19 | tracker_client->on_error([](tracker_node_client_t &c, socket_addr_t remote, connection_state state) { 20 | 21 | }); 22 | 23 | context.run(); 24 | } 25 | 26 | void init_peer(u64 sid, socket_addr_t tserver_addr, microsecond_t timeout) 27 | { 28 | std::thread thread(std::bind(thread_main, sid, tserver_addr, timeout)); 29 | thread.detach(); 30 | } 31 | 32 | void close_peer() {} 33 | 34 | void on_connect_error(std::function) {} 35 | 36 | void enable_share() {} 37 | 38 | void disable_share() {} 39 | 40 | void cancel_fragment(net::u64 fid, int channel) {} 41 | 42 | net::socket_buffer_t get_fragment(u64 fid, int channel) { return net::socket_buffer_t(); } 43 | 44 | net::socket_buffer_t get_meta(int key, int channel) { return net::socket_buffer_t(); } 45 | -------------------------------------------------------------------------------- /src/client/peer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "net/net.hpp" 3 | #include "net/socket_addr.hpp" 4 | #include "net/socket_buffer.hpp" 5 | #include "net/timer.hpp" 6 | 7 | void init_peer(net::u64 sid, net::socket_addr_t tserver_addr, net::microsecond_t timeout); 8 | 9 | void close_peer(); 10 | 11 | void on_connect_error(std::function); 12 | 13 | void enable_share(); 14 | 15 | void disable_share(); 16 | 17 | void cancel_fragment(net::u64 fid, int channel); 18 | 19 | net::socket_buffer_t get_fragment(net::u64 fid, int channel); 20 | 21 | net::socket_buffer_t get_meta(int key, int channel); 22 | -------------------------------------------------------------------------------- /src/client/yuvwidget.cc: -------------------------------------------------------------------------------- 1 | #include "yuvwidget.hpp" 2 | 3 | #define VERTEXIN 0 4 | #define TEXTUREIN 1 5 | 6 | yuvWidget::yuvWidget(QWidget *parent) 7 | : QOpenGLWidget(parent) 8 | { 9 | } 10 | 11 | yuvWidget::~yuvWidget() 12 | { 13 | makeCurrent(); 14 | vbo.destroy(); 15 | texture_y->destroy(); 16 | texture_u->destroy(); 17 | texture_v->destroy(); 18 | program->release(); 19 | 20 | doneCurrent(); 21 | } 22 | 23 | void yuvWidget::update_frame(std::shared_ptr data, int width, int height) 24 | { 25 | this->data = std::move(data); 26 | video_width = width; 27 | video_height = height; 28 | update(); 29 | } 30 | 31 | void yuvWidget::initializeGL() 32 | { 33 | initializeOpenGLFunctions(); 34 | glEnable(GL_DEPTH_TEST); 35 | 36 | static const GLfloat vertices[]{ 37 | -1.0f, -1.0f, -1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, 38 | 39 | 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 40 | }; 41 | 42 | vbo.create(); 43 | vbo.bind(); 44 | vbo.allocate(vertices, sizeof(vertices)); 45 | 46 | vs = std::make_unique(QOpenGLShader::Vertex, this); 47 | const char *vsrc = "attribute vec4 vertexIn; \ 48 | attribute vec2 textureIn; \ 49 | varying vec2 textureOut; \ 50 | void main(void) \ 51 | { \ 52 | gl_Position = vertexIn; \ 53 | textureOut = textureIn; \ 54 | }"; 55 | vs->compileSourceCode(vsrc); 56 | 57 | ps = std::make_unique(QOpenGLShader::Fragment, this); 58 | const char *fsrc = "varying vec2 textureOut; \ 59 | uniform sampler2D tex_y; \ 60 | uniform sampler2D tex_u; \ 61 | uniform sampler2D tex_v; \ 62 | void main(void) \ 63 | { \ 64 | vec3 yuv; \ 65 | vec3 rgb; \ 66 | yuv.x = texture2D(tex_y, textureOut).r; \ 67 | yuv.y = texture2D(tex_u, textureOut).r - 0.5; \ 68 | yuv.z = texture2D(tex_v, textureOut).r - 0.5; \ 69 | rgb = mat3( 1, 1, 1, \ 70 | 0, -0.39465, 2.03211, \ 71 | 1.13983, -0.58060, 0) * yuv; \ 72 | gl_FragColor = vec4(rgb, 1); \ 73 | }"; 74 | ps->compileSourceCode(fsrc); 75 | 76 | program = std::make_unique(this); 77 | program->addShader(vs.get()); 78 | program->addShader(ps.get()); 79 | program->bindAttributeLocation("vertexIn", VERTEXIN); 80 | program->bindAttributeLocation("textureIn", TEXTUREIN); 81 | program->link(); 82 | program->bind(); 83 | program->enableAttributeArray(VERTEXIN); 84 | program->enableAttributeArray(TEXTUREIN); 85 | program->setAttributeBuffer(VERTEXIN, GL_FLOAT, 0, 2, 2 * sizeof(GLfloat)); 86 | program->setAttributeBuffer(TEXTUREIN, GL_FLOAT, 8 * sizeof(GLfloat), 2, 2 * sizeof(GLfloat)); 87 | 88 | texture_uniform_y = program->uniformLocation("tex_y"); 89 | texture_uniform_u = program->uniformLocation("tex_u"); 90 | texture_uniform_v = program->uniformLocation("tex_v"); 91 | texture_y = std::make_unique(QOpenGLTexture::Target2D); 92 | texture_u = std::make_unique(QOpenGLTexture::Target2D); 93 | texture_v = std::make_unique(QOpenGLTexture::Target2D); 94 | texture_y->create(); 95 | texture_u->create(); 96 | texture_v->create(); 97 | idy = texture_y->textureId(); 98 | idu = texture_u->textureId(); 99 | idv = texture_v->textureId(); 100 | glClearColor(0.0, 0.0, 0.0, 0.0); 101 | } 102 | 103 | void yuvWidget::paintGL() 104 | { 105 | auto data = this->data; 106 | char *ptr = data.get(); 107 | if (ptr == nullptr) 108 | return; 109 | 110 | glActiveTexture(GL_TEXTURE0); 111 | glBindTexture(GL_TEXTURE_2D, idy); 112 | 113 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, video_width, video_height, 0, GL_RED, GL_UNSIGNED_BYTE, ptr); 114 | // GL_NEAREST 115 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 116 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 117 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 118 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 119 | 120 | glActiveTexture(GL_TEXTURE1); 121 | glBindTexture(GL_TEXTURE1, idu); 122 | 123 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, video_width >> 1, video_height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, 124 | ptr + video_width * video_height); 125 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 126 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 127 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 128 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 129 | 130 | glActiveTexture(GL_TEXTURE2); 131 | glBindTexture(GL_TEXTURE_2D, idv); 132 | 133 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, video_width >> 1, video_height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, 134 | ptr + video_width * video_height * 5 / 4); 135 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 136 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 137 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 138 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 139 | glUniform1i(texture_uniform_y, 0); 140 | glUniform1i(texture_uniform_u, 1); 141 | glUniform1i(texture_uniform_v, 2); 142 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 143 | } 144 | 145 | void yuvWidget::resizeGL(int width, int height) { glViewport(0, 0, width, height); } 146 | -------------------------------------------------------------------------------- /src/client/yuvwidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) 11 | QT_FORWARD_DECLARE_CLASS(QOpenGLTexture) 12 | 13 | class yuvWidget : public QOpenGLWidget, protected QOpenGLFunctions 14 | { 15 | Q_OBJECT 16 | public: 17 | yuvWidget(QWidget *parent = 0); 18 | ~yuvWidget(); 19 | 20 | public: 21 | Q_INVOKABLE void update_frame(std::shared_ptr data, int width, int height); //显示一帧Yuv图像 22 | 23 | protected: 24 | void initializeGL() Q_DECL_OVERRIDE; 25 | void paintGL() Q_DECL_OVERRIDE; 26 | void resizeGL(int width, int height) Q_DECL_OVERRIDE; 27 | 28 | private: 29 | QOpenGLBuffer vbo; 30 | GLuint texture_uniform_y, texture_uniform_u, texture_uniform_v; 31 | std::unique_ptr texture_y, texture_u, texture_v; 32 | GLuint idy, idu, idv; 33 | std::shared_ptr data; 34 | int video_width, video_height; 35 | 36 | std::unique_ptr vs, ps; 37 | std::unique_ptr program; 38 | }; 39 | -------------------------------------------------------------------------------- /src/edge-server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE DIR_SRCS *.cc *.cpp *.CC *.CPP) 2 | 3 | add_executable(edge-server ${DIR_SRCS}) 4 | find_package(GLOG NO_MODULE REQUIRED) 5 | target_link_libraries(edge-server net glog::glog gflags Threads::Threads) 6 | -------------------------------------------------------------------------------- /src/edge-server/main.cc: -------------------------------------------------------------------------------- 1 | #include "net/event.hpp" 2 | #include "net/p2p/peer.hpp" 3 | #include "net/p2p/tracker.hpp" 4 | #include "net/socket.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | DEFINE_string(ip, "0.0.0.0", "edge server ip address"); 11 | DEFINE_uint32(port, 2769, "edge server bind port"); 12 | DEFINE_bool(resuse, false, "reuse address"); 13 | DEFINE_uint32(threads, 0, "thread count"); 14 | DEFINE_bool(cmd, false, "command mode"); 15 | DEFINE_string(tip, "0.0.0.0", "tracker server address"); 16 | DEFINE_uint32(tport, 2769, "tracker server port"); 17 | DEFINE_uint32(timeout, 5000, "tracker server connect timeout (ms)"); 18 | 19 | net::event_context_t *app_context; 20 | 21 | void thread_main() { app_context->run(); } 22 | 23 | static void atexit_func() { google::ShutdownGoogleLogging(); } 24 | 25 | void server_connect_error(net::p2p::tracker_node_client_t &client, net::socket_addr_t remote, 26 | net::connection_state state) 27 | { 28 | LOG(ERROR) << "connect to tracker server failed. server: " << remote.to_string() 29 | << ", reason: " << net::connection_state_strings[(int)state] << "."; 30 | LOG(INFO) << "sleep 5 seconds to reconnect server."; 31 | net::event_loop_t::current().add_timer(net::make_timer(net::make_timespan(5), [&client]() { 32 | client.connect_server(*app_context, net::socket_addr_t(FLAGS_tip, FLAGS_tport), FLAGS_timeout * 1000); 33 | })); 34 | } 35 | 36 | void server_connect(net::p2p::tracker_node_client_t &client, net::socket_addr_t remote) 37 | { 38 | LOG(INFO) << "connect to tracker server ok. server: " << remote.to_string(); 39 | } 40 | 41 | void node_request_connect(net::p2p::tracker_node_client_t &client, net::p2p::peer_node_t node, net::p2p::peer_t *peer) 42 | { 43 | net::socket_addr_t remote(node.ip, node.port); 44 | net::socket_addr_t udp_remote(net::socket_addr_t(node.ip, node.udp_port)); 45 | if (!peer->has_connect_peer(udp_remote)) 46 | { 47 | LOG(INFO) << "node request connect " << remote.to_string() << " udp:" << node.udp_port << "."; 48 | auto info = peer->add_peer(); 49 | peer->connect_to_peer(info, udp_remote); 50 | client.request_connect_node(node, peer->get_udp()); 51 | } 52 | } 53 | 54 | void on_peer_connect(net::p2p::peer_t &ps, net::p2p::peer_info_t *peer) 55 | { 56 | net::socket_addr_t remote = peer->remote_address; 57 | LOG(INFO) << "new peer connect " << remote.to_string(); 58 | } 59 | 60 | void on_peer_disconnect(net::p2p::peer_t &ps, net::p2p::peer_info_t *peer) 61 | { 62 | net::socket_addr_t remote = peer->remote_address; 63 | LOG(INFO) << "peer disconnect " << remote.to_string(); 64 | } 65 | 66 | struct session_fragment_content 67 | { 68 | std::queue queue; 69 | std::unordered_map map; 70 | 71 | std::vector vector; 72 | 73 | void add_data(net::socket_buffer_t buffer, net::u64 fragment_id) 74 | { 75 | queue.emplace(vector.size()); 76 | map.emplace(fragment_id, vector.size()); 77 | vector.emplace_back(buffer); 78 | 79 | while (queue.size() > 50) 80 | { 81 | queue.pop(); 82 | } 83 | } 84 | 85 | std::optional get_data(net::u64 key) 86 | { 87 | auto idx = map.find(key); 88 | if (idx != map.end()) 89 | { 90 | return vector[idx->second]; 91 | } 92 | return std::make_optional(); 93 | } 94 | }; 95 | 96 | struct session_meta_content 97 | { 98 | std::unordered_map raw_data; 99 | void add_data(net::socket_buffer_t buffer, net::u64 key) { raw_data[key] = buffer; } 100 | std::optional get_data(net::u64 key) 101 | { 102 | auto idx = raw_data.find(key); 103 | if (idx != raw_data.end()) 104 | { 105 | return idx->second; 106 | } 107 | return std::make_optional(); 108 | } 109 | }; 110 | 111 | struct channel_t 112 | { 113 | std::unique_ptr fragment; 114 | std::unique_ptr meta; 115 | }; 116 | 117 | std::unordered_map> globl_data; 118 | 119 | /// BUG: add mutex here!!! 120 | void on_fragment_pull_request(net::p2p::peer_t &ps, net::p2p::peer_info_t *p, net::u64 fid, int channel) 121 | { 122 | auto buf = globl_data[p->sid][channel].fragment->get_data(fid); 123 | if (buf.has_value()) 124 | { 125 | ps.send_meta_data_to_peer(p, fid, channel, buf.value()); 126 | } 127 | else 128 | { 129 | LOG(INFO) << "request from " << p->remote_address.to_string() << "'s fragment " << fid << " channel " << channel 130 | << " is unknown."; 131 | } 132 | } 133 | 134 | void on_meta_pull_request(net::p2p::peer_t &ps, net::p2p::peer_info_t *p, net::u64 key, int channel) 135 | { 136 | auto buf = globl_data[p->sid][channel].meta->get_data(key); 137 | if (buf.has_value()) 138 | { 139 | ps.send_meta_data_to_peer(p, key, channel, buf.value()); 140 | } 141 | else 142 | { 143 | LOG(INFO) << "request from " << p->remote_address.to_string() << "'s meta info " << key << " channel " 144 | << channel << " is unknown."; 145 | } 146 | } 147 | 148 | void on_fragment_recv(net::p2p::peer_t &ps, net::p2p::peer_info_t *p, net::socket_buffer_t buffer, net::u64 fid, 149 | int channel) 150 | { 151 | globl_data[p->sid][channel].fragment->add_data(buffer, fid); 152 | } 153 | 154 | void on_meta_data_recv(net::p2p::peer_t &ps, net::p2p::peer_info_t *p, net::socket_buffer_t buffer, net::u64 key, 155 | int channel) 156 | { 157 | globl_data[p->sid][channel].meta->add_data(buffer, key); 158 | } 159 | 160 | int main(int argc, char **argv) 161 | { 162 | google::InitGoogleLogging(argv[0]); 163 | google::ParseCommandLineFlags(&argc, &argv, false); 164 | google::SetLogDestination(google::GLOG_FATAL, "./edge-server.fatal.log"); 165 | google::SetLogDestination(google::GLOG_ERROR, "./edge-server.error.log"); 166 | google::SetLogDestination(google::GLOG_INFO, "./edge-server.info.log"); 167 | google::SetLogDestination(google::GLOG_WARNING, "./edge-server.warning.log"); 168 | google::SetStderrLogging(google::GLOG_INFO); 169 | 170 | atexit(atexit_func); 171 | 172 | LOG(INFO) << "init libnet"; 173 | net::init_lib(); 174 | 175 | LOG(INFO) << "create application context"; 176 | net::event_context_t context(net::event_strategy::AUTO); 177 | app_context = &context; 178 | 179 | if (FLAGS_threads == 0) 180 | { 181 | FLAGS_threads = 4; 182 | } 183 | LOG(INFO) << "thread detect " << FLAGS_threads; 184 | 185 | for (int i = 0; i < FLAGS_threads - 1; i++) 186 | { 187 | std::thread thd(thread_main); 188 | thd.detach(); 189 | } 190 | 191 | std::unique_ptr tracker_client = 192 | std::make_unique(); 193 | std::unique_ptr peer = std::make_unique(0); 194 | 195 | tracker_client->config(true, 0, "edge server key"); 196 | tracker_client->connect_server(context, net::socket_addr_t(FLAGS_tip, FLAGS_tport), FLAGS_timeout * 1000); 197 | tracker_client->on_error(server_connect_error); 198 | tracker_client->on_node_request_connect( 199 | std::bind(node_request_connect, std::placeholders::_1, std::placeholders::_2, peer.get())); 200 | tracker_client->on_tracker_server_connect(server_connect); 201 | 202 | peer->bind(context); 203 | peer->accept_channels({1, 2}); 204 | peer->on_peer_connect(on_peer_connect); 205 | peer->on_peer_disconnect(on_peer_disconnect); 206 | peer->on_fragment_pull_request(on_fragment_pull_request); 207 | peer->on_fragment_recv(on_fragment_recv); 208 | peer->on_meta_pull_request(on_meta_pull_request); 209 | peer->on_meta_data_recv(on_meta_data_recv); 210 | LOG(INFO) << "udp bind at port " << peer->get_udp().get_socket()->local_addr().get_port(); 211 | 212 | LOG(INFO) << "run event loop"; 213 | auto ret = app_context->run(); 214 | net::uninit_lib(); 215 | return ret; 216 | } 217 | -------------------------------------------------------------------------------- /src/source-client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE DIR_SRCS *.cc *.cpp *.CC *.CPP) 2 | file(GLOB_RECURSE WINDOW_SRCS *-window.cc *-window.cpp *-window.CC *-window.CPP) 3 | file(GLOB_RECURSE WINDOW_UI *-window.ui *-window.UI) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_AUTOUIC ON) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | FIND_PACKAGE(Qt5 COMPONENTS Core Gui Widgets REQUIRED) 10 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 11 | 12 | if (MSVC) 13 | add_executable(source-client WIN32 ${DIR_SRCS} ${WINDOW_UI}) 14 | else() 15 | add_executable(source-client ${DIR_SRCS} ${WINDOW_UI}) 16 | endif(MSVC) 17 | 18 | find_package(GLOG NO_MODULE REQUIRED) 19 | 20 | target_include_directories(source-client PUBLIC ${QT_INCLUDES} ) 21 | target_include_directories(source-client PUBLIC ${FFMPEG_INCLUDE_DIR}) 22 | 23 | target_link_libraries(source-client PUBLIC ${FFMPEG_LIBRARY} Qt5::Widgets Qt5::Core Qt5::Gui net glog::glog gflags Threads::Threads) 24 | 25 | add_custom_command(TARGET source-client POST_BUILD 26 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 27 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 28 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 29 | ) -------------------------------------------------------------------------------- /src/source-client/main-window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | extern "C" { 9 | #include 10 | } 11 | 12 | namespace Ui 13 | { 14 | class MainWindow; 15 | } 16 | 17 | class MainWindow : public QMainWindow 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit MainWindow(QWidget *parent = 0); 23 | ~MainWindow(); 24 | void video_main(); 25 | void audio_main(); 26 | void get_device(); 27 | 28 | private: 29 | Ui::MainWindow *ui; 30 | std::thread video_thread, audio_thread; 31 | int target_width; 32 | int target_height; 33 | bool run; 34 | AVFormatContext *video_format_ctx, *audio_format_ctx; 35 | int videoindex; 36 | int audioindex; 37 | AVCodecContext *video_codec, *audio_codec; 38 | 39 | std::mutex device_ready; 40 | std::condition_variable cond_dev; 41 | }; 42 | -------------------------------------------------------------------------------- /src/source-client/main-window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 755 10 | 499 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 0 22 | 755 23 | 25 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | yuvWidget 32 | QWidget 33 |
yuvwidget.hpp
34 |
35 |
36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /src/source-client/main.cc: -------------------------------------------------------------------------------- 1 | #include "peer.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; ///- 9 | 10 | ////函数 11 | 12 | static void atexit_func() { google::ShutdownGoogleLogging(); } 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | google::InitGoogleLogging(argv[0]); 17 | google::SetLogDestination(google::GLOG_FATAL, "./source-client.fatal.log"); 18 | google::SetLogDestination(google::GLOG_ERROR, "./source-client.error.log"); 19 | google::SetLogDestination(google::GLOG_INFO, "./source-client.info.log"); 20 | google::SetLogDestination(google::GLOG_WARNING, "./source-client.warning.log"); 21 | google::SetStderrLogging(google::GLOG_INFO); 22 | atexit(atexit_func); 23 | 24 | net::init_lib(); 25 | on_connection_error([](net::connection_state state) { 26 | LOG(INFO) << "reconnecting..."; 27 | return true; 28 | }); 29 | on_edge_server_prepared([]() { LOG(INFO) << "connect to edge server ok"; }); 30 | init_peer(1, net::socket_addr_t("127.0.0.1", 2769), net::make_timespan(2)); 31 | 32 | QApplication app(argc, argv); 33 | MainWindow mainWindow; 34 | mainWindow.show(); 35 | int v = app.exec(); 36 | net::uninit_lib(); 37 | return v; 38 | } 39 | -------------------------------------------------------------------------------- /src/source-client/peer.cc: -------------------------------------------------------------------------------- 1 | #include "peer.hpp" 2 | #include "net/event.hpp" 3 | #include "net/p2p/peer.hpp" 4 | #include "net/p2p/tracker.hpp" 5 | #include "net/socket.hpp" 6 | #include 7 | #include 8 | 9 | using namespace net::p2p; 10 | using namespace net; 11 | event_context_t *app_context; 12 | std::function error_handler; 13 | std::function edge_server_prepared_handler; 14 | 15 | peer_t *glob_peer; 16 | peer_info_t *edge_peer_target = nullptr; 17 | 18 | std::atomic_bool is_connect_edge_server = false; 19 | 20 | void thread_main(u64 sid, socket_addr_t ts_server_addr, microsecond_t timeout) 21 | { 22 | event_context_t context(event_strategy::AUTO); 23 | app_context = &context; 24 | auto peer = std::make_unique(sid); 25 | auto tracker = std::make_unique(); 26 | glob_peer = peer.get(); 27 | tracker->config(false, sid, ""); 28 | tracker->connect_server(*app_context, ts_server_addr, timeout); 29 | tracker->on_error([ts_server_addr, timeout](tracker_node_client_t &t, socket_addr_t addr, connection_state state) { 30 | is_connect_edge_server = false; 31 | 32 | if (error_handler) 33 | { 34 | if (error_handler(state)) 35 | { 36 | event_loop_t::current().add_timer(make_timer(make_timespan(1), [&t, ts_server_addr, timeout]() { 37 | t.connect_server(*app_context, ts_server_addr, timeout); 38 | })); 39 | } 40 | else 41 | { 42 | app_context->exit_all(-1); 43 | } 44 | } 45 | }); 46 | 47 | tracker->on_tracker_server_connect([](tracker_node_client_t &c, socket_addr_t) { 48 | c.request_update_trackers(); 49 | c.request_update_nodes(2, RequestNodeStrategy::edge_nodes); 50 | }); 51 | 52 | tracker->on_nodes_update([](tracker_node_client_t &c, peer_node_t *nodes, u64 count) { 53 | if (count <= 0) 54 | { 55 | if (error_handler) 56 | { 57 | if (error_handler(connection_state::connection_refuse)) 58 | { 59 | event_loop_t::current().add_timer(make_timer( 60 | make_timespan(1), [&c]() { c.request_update_nodes(1, RequestNodeStrategy::edge_nodes); })); 61 | } 62 | else 63 | { 64 | LOG(INFO) << "exit all context"; 65 | app_context->exit_all(-1); 66 | } 67 | } 68 | return; 69 | } 70 | auto addr = socket_addr_t(nodes[0].ip, nodes[0].udp_port); 71 | c.request_connect_node(nodes[0], glob_peer->get_udp()); 72 | }); 73 | peer->bind(context); 74 | peer->accept_channels({1, 2}); 75 | peer->on_peer_connect([](peer_t &, peer_info_t *info) { 76 | LOG(INFO) << "peer server connect ok"; 77 | if (edge_peer_target != nullptr) 78 | { 79 | LOG(ERROR) << "invalid state, new target " << info->remote_address.to_string(); 80 | } 81 | edge_peer_target = info; 82 | is_connect_edge_server = true; 83 | if (edge_server_prepared_handler) 84 | edge_server_prepared_handler(); 85 | }); 86 | 87 | peer->on_peer_disconnect([](peer_t &, peer_info_t *info) { 88 | is_connect_edge_server = false; 89 | if (edge_peer_target != info) 90 | { 91 | LOG(ERROR) << "invalid state, new target " << info->remote_address.to_string(); 92 | } 93 | if (error_handler && error_handler(connection_state::close_by_peer)) 94 | { 95 | glob_peer->connect_to_peer(info, edge_peer_target->remote_address); 96 | } 97 | else 98 | { 99 | edge_peer_target = nullptr; 100 | } 101 | }); 102 | LOG(INFO) << "udp bind at port " << peer->get_udp().get_socket()->local_addr().get_port(); 103 | 104 | context.run(); 105 | glob_peer = nullptr; 106 | edge_peer_target = nullptr; 107 | } 108 | 109 | void init_peer(u64 sid, socket_addr_t ts_server_addr, microsecond_t timeout) 110 | { 111 | std::thread thread(std::bind(thread_main, sid, ts_server_addr, timeout)); 112 | thread.detach(); 113 | } 114 | 115 | void send_data(void *buffer_ptr, int size, int channel, net::u64 fragment_id) 116 | { 117 | if (glob_peer && is_connect_edge_server) 118 | { 119 | socket_buffer_t buffer(size); 120 | memcpy(buffer.get(), buffer_ptr, size); 121 | glob_peer->get_socket()->start_with([buffer, channel, fragment_id]() { 122 | glob_peer->send_fragment_to_peer(edge_peer_target, fragment_id, channel, buffer); 123 | }); 124 | } 125 | } 126 | 127 | void send_meta_info(void *buffer_ptr, int size, int channel, int key) 128 | { 129 | if (glob_peer && is_connect_edge_server) 130 | { 131 | socket_buffer_t buffer(size); 132 | memcpy(buffer.get(), buffer_ptr, size); 133 | glob_peer->get_socket()->start_with( 134 | [buffer, channel, key]() { glob_peer->send_meta_data_to_peer(edge_peer_target, key, channel, buffer); }); 135 | } 136 | } 137 | 138 | bool is_connect() { return is_connect_edge_server; } 139 | 140 | void on_connection_error(std::function func) { error_handler = func; } 141 | 142 | void on_edge_server_prepared(std::function func) { edge_server_prepared_handler = func; } 143 | 144 | void close_peer() { app_context->exit_all(0); } 145 | -------------------------------------------------------------------------------- /src/source-client/peer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "net/event.hpp" 3 | #include "net/net.hpp" 4 | #include "net/socket_addr.hpp" 5 | #include 6 | 7 | void init_peer(net::u64 sid, net::socket_addr_t ts_server_addr, net::microsecond_t timeout); 8 | void send_data(void *buffer, int size, int channel, net::u64 fragment_id); 9 | void send_meta_info(void *buffer, int size, int channel, int key); 10 | 11 | bool is_connect(); 12 | 13 | // return true for reconnect 14 | void on_connection_error(std::function); 15 | void on_edge_server_prepared(std::function); 16 | 17 | void close_peer(); -------------------------------------------------------------------------------- /src/source-client/yuvwidget.cc: -------------------------------------------------------------------------------- 1 | #include "yuvwidget.hpp" 2 | 3 | #define VERTEXIN 0 4 | #define TEXTUREIN 1 5 | 6 | yuvWidget::yuvWidget(QWidget *parent) 7 | : QOpenGLWidget(parent) 8 | { 9 | } 10 | 11 | yuvWidget::~yuvWidget() 12 | { 13 | makeCurrent(); 14 | vbo.destroy(); 15 | texture_y->destroy(); 16 | texture_u->destroy(); 17 | texture_v->destroy(); 18 | program->release(); 19 | 20 | doneCurrent(); 21 | } 22 | 23 | void yuvWidget::update_frame(std::shared_ptr data, int width, int height) 24 | { 25 | this->data = std::move(data); 26 | video_width = width; 27 | video_height = height; 28 | update(); 29 | } 30 | 31 | void yuvWidget::initializeGL() 32 | { 33 | initializeOpenGLFunctions(); 34 | glEnable(GL_DEPTH_TEST); 35 | 36 | static const GLfloat vertices[]{ 37 | -1.0f, -1.0f, -1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, 38 | 39 | 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 40 | }; 41 | 42 | vbo.create(); 43 | vbo.bind(); 44 | vbo.allocate(vertices, sizeof(vertices)); 45 | 46 | vs = std::make_unique(QOpenGLShader::Vertex, this); 47 | const char *vsrc = "attribute vec4 vertexIn; \ 48 | attribute vec2 textureIn; \ 49 | varying vec2 textureOut; \ 50 | void main(void) \ 51 | { \ 52 | gl_Position = vertexIn; \ 53 | textureOut = textureIn; \ 54 | }"; 55 | vs->compileSourceCode(vsrc); 56 | 57 | ps = std::make_unique(QOpenGLShader::Fragment, this); 58 | const char *fsrc = "varying vec2 textureOut; \ 59 | uniform sampler2D tex_y; \ 60 | uniform sampler2D tex_u; \ 61 | uniform sampler2D tex_v; \ 62 | void main(void) \ 63 | { \ 64 | vec3 yuv; \ 65 | vec3 rgb; \ 66 | yuv.x = texture2D(tex_y, textureOut).r; \ 67 | yuv.y = texture2D(tex_u, textureOut).r - 0.5; \ 68 | yuv.z = texture2D(tex_v, textureOut).r - 0.5; \ 69 | rgb = mat3( 1, 1, 1, \ 70 | 0, -0.39465, 2.03211, \ 71 | 1.13983, -0.58060, 0) * yuv; \ 72 | gl_FragColor = vec4(rgb, 1); \ 73 | }"; 74 | ps->compileSourceCode(fsrc); 75 | 76 | program = std::make_unique(this); 77 | program->addShader(vs.get()); 78 | program->addShader(ps.get()); 79 | program->bindAttributeLocation("vertexIn", VERTEXIN); 80 | program->bindAttributeLocation("textureIn", TEXTUREIN); 81 | program->link(); 82 | program->bind(); 83 | program->enableAttributeArray(VERTEXIN); 84 | program->enableAttributeArray(TEXTUREIN); 85 | program->setAttributeBuffer(VERTEXIN, GL_FLOAT, 0, 2, 2 * sizeof(GLfloat)); 86 | program->setAttributeBuffer(TEXTUREIN, GL_FLOAT, 8 * sizeof(GLfloat), 2, 2 * sizeof(GLfloat)); 87 | 88 | texture_uniform_y = program->uniformLocation("tex_y"); 89 | texture_uniform_u = program->uniformLocation("tex_u"); 90 | texture_uniform_v = program->uniformLocation("tex_v"); 91 | texture_y = std::make_unique(QOpenGLTexture::Target2D); 92 | texture_u = std::make_unique(QOpenGLTexture::Target2D); 93 | texture_v = std::make_unique(QOpenGLTexture::Target2D); 94 | texture_y->create(); 95 | texture_u->create(); 96 | texture_v->create(); 97 | idy = texture_y->textureId(); 98 | idu = texture_u->textureId(); 99 | idv = texture_v->textureId(); 100 | glClearColor(0.0, 0.0, 0.0, 0.0); 101 | } 102 | 103 | void yuvWidget::paintGL() 104 | { 105 | auto data = this->data; 106 | char *ptr = data.get(); 107 | if (ptr == nullptr) 108 | return; 109 | 110 | glActiveTexture(GL_TEXTURE0); 111 | glBindTexture(GL_TEXTURE_2D, idy); 112 | 113 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, video_width, video_height, 0, GL_RED, GL_UNSIGNED_BYTE, ptr); 114 | // GL_NEAREST 115 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 116 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 117 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 118 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 119 | 120 | glActiveTexture(GL_TEXTURE1); 121 | glBindTexture(GL_TEXTURE1, idu); 122 | 123 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, video_width >> 1, video_height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, 124 | ptr + video_width * video_height); 125 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 126 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 127 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 128 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 129 | 130 | glActiveTexture(GL_TEXTURE2); 131 | glBindTexture(GL_TEXTURE_2D, idv); 132 | 133 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, video_width >> 1, video_height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, 134 | ptr + video_width * video_height * 5 / 4); 135 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 136 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 137 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 138 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 139 | glUniform1i(texture_uniform_y, 0); 140 | glUniform1i(texture_uniform_u, 1); 141 | glUniform1i(texture_uniform_v, 2); 142 | glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 143 | } 144 | 145 | void yuvWidget::resizeGL(int width, int height) { glViewport(0, 0, width, height); } 146 | -------------------------------------------------------------------------------- /src/source-client/yuvwidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) 11 | QT_FORWARD_DECLARE_CLASS(QOpenGLTexture) 12 | 13 | class yuvWidget : public QOpenGLWidget, protected QOpenGLFunctions 14 | { 15 | Q_OBJECT 16 | public: 17 | yuvWidget(QWidget *parent = 0); 18 | ~yuvWidget(); 19 | 20 | public: 21 | Q_INVOKABLE void update_frame(std::shared_ptr data, int width, int height); //显示一帧Yuv图像 22 | 23 | protected: 24 | void initializeGL() Q_DECL_OVERRIDE; 25 | void paintGL() Q_DECL_OVERRIDE; 26 | void resizeGL(int width, int height) Q_DECL_OVERRIDE; 27 | 28 | private: 29 | QOpenGLBuffer vbo; 30 | GLuint texture_uniform_y, texture_uniform_u, texture_uniform_v; 31 | std::unique_ptr texture_y, texture_u, texture_v; 32 | GLuint idy, idu, idv; 33 | std::shared_ptr data; 34 | int video_width, video_height; 35 | 36 | std::unique_ptr vs, ps; 37 | std::unique_ptr program; 38 | }; 39 | -------------------------------------------------------------------------------- /src/tracker-server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE DIR_SRCS *.cc *.cpp *.CC *.CPP) 2 | 3 | add_executable(tracker-server ${DIR_SRCS}) 4 | find_package(GLOG NO_MODULE REQUIRED) 5 | 6 | target_link_libraries(tracker-server net glog::glog gflags Threads::Threads) 7 | -------------------------------------------------------------------------------- /src/tracker-server/main.cc: -------------------------------------------------------------------------------- 1 | #include "net/event.hpp" 2 | #include "net/p2p/tracker.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | DEFINE_string(ip, "0.0.0.0", "tracker server bind ip address"); 8 | DEFINE_uint32(port, 2769, "tracker server port"); 9 | DEFINE_uint32(rudp_port, 2770, "tracker server rudp port"); 10 | DEFINE_uint32(threads, 0, "threads count"); 11 | DEFINE_bool(reuse, true, "resuse address"); 12 | 13 | net::event_context_t *app_context; 14 | 15 | void thread_main() { app_context->run(); } 16 | 17 | static void atexit_func() 18 | { 19 | LOG(INFO) << "exit server..."; 20 | google::ShutdownGoogleLogging(); 21 | } 22 | 23 | void on_shared_peer_add(net::p2p::tracker_server_t &server, net::p2p::peer_node_t node, net::u64 sid) 24 | { 25 | net::socket_addr_t addr(node.ip, node.port); 26 | LOG(INFO) << "new shared peer " << addr.to_string() << " udp: " << node.udp_port << " sid: " << sid; 27 | } 28 | 29 | void on_shared_peer_remove(net::p2p::tracker_server_t &server, net::p2p::peer_node_t node, net::u64 sid) 30 | { 31 | net::socket_addr_t addr(node.ip, node.port); 32 | LOG(INFO) << "exit shared peer " << addr.to_string() << " udp: " << node.udp_port << " sid " << sid; 33 | } 34 | 35 | void on_shared_peer_error(net::p2p::tracker_server_t &server, net::socket_addr_t addr, net::u64 sid, 36 | net::connection_state state) 37 | 38 | { 39 | LOG(INFO) << "exit shared peer " << addr.to_string() << " sid: " << sid 40 | << " reason: " << net::connection_state_strings[(int)state]; 41 | } 42 | 43 | void on_peer_hole_connect(net::p2p::tracker_server_t &server, net::p2p::peer_node_t node) 44 | { 45 | net::socket_addr_t addr(node.ip, node.port); 46 | 47 | LOG(INFO) << "peer request connect to -> " << addr.to_string(); 48 | } 49 | 50 | int main(int argc, char **argv) 51 | { 52 | google::InitGoogleLogging(argv[0]); 53 | google::ParseCommandLineFlags(&argc, &argv, false); 54 | google::SetLogDestination(google::GLOG_FATAL, "./tracker-server.fatal.log"); 55 | google::SetLogDestination(google::GLOG_ERROR, "./tracker-server.error.log"); 56 | google::SetLogDestination(google::GLOG_INFO, "./tracker-server.info.log"); 57 | google::SetLogDestination(google::GLOG_WARNING, "./tracker-server.warning.log"); 58 | google::SetStderrLogging(google::GLOG_INFO); 59 | 60 | atexit(atexit_func); 61 | 62 | LOG(INFO) << "init libnet"; 63 | net::init_lib(); 64 | 65 | LOG(INFO) << "create application context"; 66 | net::event_context_t context(net::event_strategy::AUTO); 67 | app_context = &context; 68 | 69 | if (FLAGS_threads == 0) 70 | { 71 | FLAGS_threads = 4; 72 | } 73 | LOG(INFO) << "thread detect " << FLAGS_threads; 74 | 75 | for (int i = 0; i < FLAGS_threads - 1; i++) 76 | { 77 | std::thread thd(thread_main); 78 | thd.detach(); 79 | } 80 | 81 | LOG(INFO) << "start tracker server at " << FLAGS_ip << ":" << FLAGS_port; 82 | 83 | std::unique_ptr tracker_server = std::make_unique(); 84 | tracker_server->bind(context, net::socket_addr_t(FLAGS_ip, FLAGS_port), 50, FLAGS_reuse); 85 | /// configurate edge server key 86 | tracker_server->config("edge server key"); 87 | tracker_server->on_shared_peer_add_connection(on_shared_peer_add); 88 | tracker_server->on_shared_peer_remove_connection(on_shared_peer_remove); 89 | tracker_server->on_shared_peer_error(on_shared_peer_error); 90 | tracker_server->on_normal_peer_connect(on_peer_hole_connect); 91 | 92 | LOG(INFO) << "run event loop"; 93 | auto ret = app_context->run(); 94 | net::uninit_lib(); 95 | return ret; 96 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | add_subdirectory(net) -------------------------------------------------------------------------------- /test/net/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE DIR_SRCS *.cc *.cpp *.CC *.CPP) 2 | find_package(GTest REQUIRED) 3 | 4 | add_executable(test-net ${DIR_SRCS}) 5 | target_link_libraries(test-net net Threads::Threads ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES}) 6 | gtest_discover_tests(test-net) -------------------------------------------------------------------------------- /test/net/endian.cc: -------------------------------------------------------------------------------- 1 | #include "net/endian.hpp" 2 | #include 3 | #include 4 | using namespace net; 5 | 6 | #pragma pack(push, 1) 7 | struct data2_t 8 | { 9 | u8 tag; // 1 10 | u16 tag2; // 2 11 | u32 version; // 4 12 | u64 version2; // 8 13 | using member_list_t = net::serialization::typelist_t; 14 | }; 15 | 16 | struct data_t 17 | { 18 | u16 head; // 2 19 | data2_t header; // 20 | // char data[24]; // 24 21 | i16 tail; // 2 22 | u8 data[4]; 23 | i16 data2[3]; 24 | using member_list_t = net::serialization::typelist_t; 25 | }; 26 | #pragma pack(pop) 27 | 28 | TEST(EndianTest, Test) 29 | { 30 | data_t data; 31 | 32 | data2_t data2; 33 | data2.tag = 1; 34 | data2.tag2 = 384; 35 | data2.version = 2048; 36 | data2.version2 = 0x8040201008040201ULL; 37 | memcpy(&data.header, &data2, sizeof(data2)); 38 | net::endian::cast(data2); 39 | 40 | GTEST_ASSERT_EQ(data2.tag, 1); 41 | GTEST_ASSERT_EQ(data2.tag2, 32769); 42 | GTEST_ASSERT_EQ(data2.version, 524288); 43 | GTEST_ASSERT_EQ(data2.version2, 0x102040810204080ULL); 44 | /// nest struct 45 | data.head = 2064; 46 | data.tail = -8000; 47 | data.data[0] = 1; 48 | data.data[1] = 2; 49 | data.data[2] = 0; 50 | data.data[3] = 4; 51 | 52 | data.data2[0] = 123; 53 | data.data2[1] = -8000; 54 | data.data2[2] = 0; 55 | 56 | net::endian::cast(data); 57 | GTEST_ASSERT_EQ(data.header.tag, 1); 58 | GTEST_ASSERT_EQ(data.header.tag2, 32769); 59 | GTEST_ASSERT_EQ(data.header.version, 524288); 60 | GTEST_ASSERT_EQ(data.header.version2, 0x102040810204080ULL); 61 | GTEST_ASSERT_EQ(data.head, 4104); 62 | GTEST_ASSERT_EQ(data.tail, -16160); 63 | GTEST_ASSERT_EQ(data.data[0], 1); 64 | GTEST_ASSERT_EQ(data.data[1], 2); 65 | GTEST_ASSERT_EQ(data.data[2], 0); 66 | GTEST_ASSERT_EQ(data.data[3], 4); 67 | GTEST_ASSERT_EQ(data.data2[0], 31488); 68 | GTEST_ASSERT_EQ(data.data2[1], -16160); 69 | GTEST_ASSERT_EQ(data.data2[2], 0); 70 | } 71 | -------------------------------------------------------------------------------- /test/net/main.cc: -------------------------------------------------------------------------------- 1 | #include "net/net.hpp" 2 | #include 3 | int main(int argc, char **argv) 4 | { 5 | testing::InitGoogleTest(&argc, argv); 6 | net::init_lib(); 7 | 8 | int v = 0; 9 | try 10 | { 11 | v = RUN_ALL_TESTS(); 12 | net::uninit_lib(); 13 | } catch (std::exception e) 14 | { 15 | std::cout << e.what() << std::endl; 16 | return v; 17 | } 18 | return v; 19 | } -------------------------------------------------------------------------------- /test/net/rudp.cc: -------------------------------------------------------------------------------- 1 | #include "net/rudp.hpp" 2 | #include "net/event.hpp" 3 | #include "net/net.hpp" 4 | #include 5 | using namespace net; 6 | static std::string test_data = "12345678abcdefghe"; 7 | 8 | TEST(RUDPTest, Interface) 9 | { 10 | event_context_t ctx(event_strategy::AUTO); 11 | 12 | socket_addr_t addr1("127.0.0.1", 2001); 13 | socket_addr_t addr2("127.0.0.1", 2000); 14 | 15 | rudp_t rudp1, rudp2; 16 | rudp1.bind(ctx, addr1, true); 17 | rudp2.bind(ctx, addr2, true); 18 | 19 | rudp1.add_connection(addr2, 0, make_timespan(5)); 20 | rudp2.add_connection(addr1, 0, make_timespan(5)); 21 | 22 | int count_flag = 0; 23 | 24 | rudp1.on_new_connection([&rudp1, &ctx, &count_flag](rudp_connection_t conn) { 25 | socket_buffer_t buffer = socket_buffer_t::from_string(test_data); 26 | buffer.expect().origin_length(); 27 | GTEST_ASSERT_EQ(co::await(rudp_awrite, &rudp1, conn, buffer), io_result::ok); 28 | buffer.expect().origin_length(); 29 | GTEST_ASSERT_EQ(co::await(rudp_aread, &rudp1, conn, buffer), io_result::ok); 30 | 31 | GTEST_ASSERT_EQ(buffer.to_string(), test_data); 32 | if (++count_flag > 1) 33 | { 34 | ctx.exit_all(0); 35 | } 36 | }); 37 | 38 | rudp2.on_new_connection([&rudp2, &ctx, &count_flag](rudp_connection_t conn) { 39 | socket_buffer_t buffer = socket_buffer_t::from_string(test_data); 40 | 41 | buffer.expect().origin_length(); 42 | GTEST_ASSERT_EQ(co::await(rudp_awrite, &rudp2, conn, buffer), io_result::ok); 43 | buffer.expect().origin_length(); 44 | GTEST_ASSERT_EQ(co::await(rudp_aread, &rudp2, conn, buffer), io_result::ok); 45 | 46 | GTEST_ASSERT_EQ(buffer.to_string(), test_data); 47 | if (++count_flag > 1) 48 | { 49 | ctx.exit_all(0); 50 | } 51 | }); 52 | event_loop_t::current().add_timer(make_timer(net::make_timespan(1), [&ctx]() { ctx.exit_all(-1); })); 53 | ctx.run(); 54 | GTEST_ASSERT_EQ(count_flag, 2); 55 | } 56 | 57 | TEST(RUDPTest, FlowControl) 58 | { 59 | constexpr u64 test_count = 50; 60 | 61 | event_context_t ctx(event_strategy::AUTO); 62 | 63 | socket_addr_t addr1("127.0.0.1", 2002); 64 | socket_addr_t addr2("127.0.0.1", 2003); 65 | 66 | rudp_t rudp1, rudp2; 67 | rudp1.bind(ctx, addr1, true); 68 | rudp2.bind(ctx, addr2, true); 69 | 70 | rudp1.add_connection(addr2, 0, make_timespan(5)); 71 | rudp2.add_connection(addr1, 0, make_timespan(5)); 72 | rudp1.set_wndsize(addr2, 0, 5, 3); 73 | rudp2.set_wndsize(addr1, 0, 3, 5); 74 | 75 | int count_flag = 0; 76 | 77 | rudp1.on_new_connection([&rudp1, &ctx, &count_flag, test_count](rudp_connection_t conn) { 78 | socket_addr_t addr; 79 | socket_buffer_t buffer(1280); 80 | buffer.clear(); 81 | for (int i = 0; i < test_count; i++) 82 | { 83 | buffer.expect().origin_length(); 84 | buffer.clear(); 85 | GTEST_ASSERT_EQ(co::await(rudp_awrite, &rudp1, conn, buffer), io_result::ok); 86 | } 87 | 88 | if (++count_flag > 1) 89 | { 90 | ctx.exit_all(0); 91 | } 92 | }); 93 | 94 | rudp2.on_new_connection([&rudp2, &ctx, &count_flag, test_count](rudp_connection_t conn) { 95 | socket_buffer_t buffer(1280); 96 | for (int i = 0; i < test_count; i++) 97 | { 98 | buffer.expect().origin_length(); 99 | GTEST_ASSERT_EQ(co::await(rudp_aread, &rudp2, conn, buffer), io_result::ok); 100 | GTEST_ASSERT_EQ(buffer.get_length(), 1280); 101 | } 102 | 103 | if (++count_flag > 1) 104 | { 105 | ctx.exit_all(0); 106 | } 107 | }); 108 | event_loop_t::current().add_timer(make_timer(net::make_timespan(2), [&ctx]() { ctx.exit_all(-1); })); 109 | ctx.run(); 110 | GTEST_ASSERT_EQ(count_flag, 2); 111 | } 112 | 113 | static void thread_main(event_context_t *context, socket_addr_t addr1, socket_addr_t addr2, int test_count, 114 | std::atomic_int &count_flag) 115 | { 116 | 117 | context->prepare(); 118 | rudp_t rudp2; 119 | rudp2.bind(*context, addr2, true); 120 | 121 | rudp2.add_connection(addr1, 0, make_timespan(5)); 122 | rudp2.set_wndsize(addr1, 0, 3, 5); 123 | 124 | rudp2.on_new_connection([&rudp2, context, &count_flag, &test_count](rudp_connection_t conn) { 125 | socket_buffer_t buffer(1280); 126 | for (int i = 0; i < test_count; i++) 127 | { 128 | buffer.expect().origin_length(); 129 | GTEST_ASSERT_EQ(co::await(rudp_aread, &rudp2, conn, buffer), io_result::ok); 130 | GTEST_ASSERT_EQ(buffer.get_length(), 1280); 131 | } 132 | 133 | if (++count_flag > 1) 134 | { 135 | context->exit_all(0); 136 | } 137 | }); 138 | 139 | context->run(); 140 | } 141 | 142 | TEST(RUDPTest, MultithreadFlowControl) 143 | { 144 | constexpr u64 test_count = 50; 145 | 146 | event_context_t ctx(event_strategy::AUTO); 147 | 148 | socket_addr_t addr1("127.0.0.1", 2004); 149 | socket_addr_t addr2("127.0.0.1", 2005); 150 | std::atomic_int count_flag = 0; 151 | 152 | std::thread thread(std::bind(&thread_main, &ctx, addr1, addr2, test_count, std::ref(count_flag))); 153 | 154 | rudp_t rudp1; 155 | rudp1.bind(ctx, addr1, true); 156 | 157 | rudp1.add_connection(addr2, 0, make_timespan(5)); 158 | rudp1.set_wndsize(addr2, 0, 5, 3); 159 | 160 | rudp1.on_new_connection([&rudp1, &ctx, &count_flag, test_count](rudp_connection_t conn) { 161 | socket_addr_t addr; 162 | socket_buffer_t buffer(1280); 163 | buffer.clear(); 164 | for (int i = 0; i < test_count; i++) 165 | { 166 | buffer.expect().origin_length(); 167 | buffer.clear(); 168 | GTEST_ASSERT_EQ(co::await(rudp_awrite, &rudp1, conn, buffer), io_result::ok); 169 | } 170 | 171 | if (++count_flag > 1) 172 | { 173 | ctx.exit_all(0); 174 | } 175 | }); 176 | 177 | event_loop_t::current().add_timer(make_timer(net::make_timespan(200), [&ctx]() { ctx.exit_all(0); })); 178 | ctx.run(); 179 | GTEST_ASSERT_EQ(count_flag, 2); 180 | thread.join(); 181 | } 182 | -------------------------------------------------------------------------------- /test/net/thread_pool.cc: -------------------------------------------------------------------------------- 1 | #include "net/thread_pool.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | TEST(ThreadPoolTest, BaseTest) 7 | { 8 | int ok = false; 9 | { 10 | net::thread_pool_t pool(4); 11 | pool.commit([&ok]() { ok = 1; }); 12 | } 13 | GTEST_ASSERT_EQ(ok, 1); 14 | } 15 | 16 | void calc(std::mutex &mutex, std::condition_variable &cv, std::atomic_int &c) 17 | { 18 | #ifndef OS_WINDOWS 19 | sleep(1); 20 | #else 21 | _sleep(1); 22 | #endif 23 | std::unique_lock lock(mutex); 24 | c--; 25 | cv.notify_one(); 26 | } 27 | 28 | TEST(ThreadPoolTest, MultiThread) 29 | { 30 | int x = 10; 31 | std::mutex mutex; 32 | std::condition_variable cv; 33 | std::atomic_int c = x; 34 | 35 | net::thread_pool_t poll(4); 36 | 37 | for (int i = 0; i < x; i++) 38 | { 39 | poll.commit(std::bind(calc, std::ref(mutex), std::ref(cv), std::ref(c))); 40 | } 41 | std::unique_lock lock(mutex); 42 | cv.wait(lock, [&c]() { return c == 0; }); 43 | } -------------------------------------------------------------------------------- /test/net/timer.cc: -------------------------------------------------------------------------------- 1 | #include "net/timer.hpp" 2 | #include "net/co.hpp" 3 | #include "net/event.hpp" 4 | #include "net/socket.hpp" 5 | #include "net/socket_buffer.hpp" 6 | #include "net/tcp.hpp" 7 | #include 8 | #include 9 | 10 | using namespace net; 11 | 12 | TEST(TimerTest, TimerShortTick) 13 | { 14 | event_context_t ctx(event_strategy::AUTO); 15 | microsecond_t point = get_current_time(); 16 | microsecond_t point2; 17 | // 500ms 18 | microsecond_t span = 500000; 19 | 20 | event_loop_t::current().add_timer(::net::make_timer(span, [&ctx, &point2]() { 21 | point2 = get_current_time(); 22 | ctx.exit_all(1); 23 | })); 24 | ctx.run(); 25 | GTEST_ASSERT_GE(point2 - point, span); 26 | } 27 | 28 | TEST(TimerTest, TimerLongTick) 29 | { 30 | event_context_t ctx(event_strategy::AUTO, 500000); 31 | // 500ms 32 | microsecond_t point = get_current_time(); 33 | microsecond_t point2; 34 | // 550ms 35 | microsecond_t span = 550000; 36 | 37 | event_loop_t::current().add_timer(::net::make_timer(span, [&ctx, &point2]() { 38 | point2 = get_current_time(); 39 | ctx.exit_all(1); 40 | })); 41 | ctx.run(); 42 | GTEST_ASSERT_GE(point2 - point, span); 43 | } 44 | 45 | void work() 46 | { 47 | event_loop_t::current().add_timer(::net::timer_t(get_current_time() + 200000, []() { work(); })); 48 | // work load 49 | std::this_thread::sleep_for(std::chrono::milliseconds(180)); 50 | } 51 | 52 | TEST(TimerTest, TimerFullWorkLoad) 53 | { 54 | // 100ms 55 | event_context_t ctx(event_strategy::AUTO, 100000); 56 | 57 | microsecond_t point = get_current_time(); 58 | microsecond_t point2; 59 | // 800ms 60 | microsecond_t span = 800000; 61 | event_loop_t::current().add_timer(::net::make_timer(span, [&ctx, &point2]() { 62 | point2 = get_current_time(); 63 | ctx.exit_all(1); 64 | })); 65 | 66 | event_loop_t::current().add_timer(::net::timer_t(get_current_time() + 200000, []() { work(); })); 67 | 68 | ctx.run(); 69 | GTEST_ASSERT_GE(point2 - point, span); 70 | } 71 | 72 | TEST(TimerTest, TimerRemove) 73 | { 74 | event_context_t ctx(event_strategy::AUTO, 100000); 75 | microsecond_t point = get_current_time(); 76 | 77 | auto tick = event_loop_t::current().add_timer(make_timer(make_timespan(1, 500), []() { 78 | std::string str = "timer remove failed"; 79 | GTEST_ASSERT_EQ(str, ""); 80 | })); 81 | 82 | event_loop_t::current().add_timer( 83 | make_timer(make_timespan(1), [&tick]() { event_loop_t::current().remove_timer(tick); })); 84 | event_loop_t::current().add_timer(make_timer(make_timespan(1, 800), [&ctx, &tick]() { ctx.exit_all(0); })); 85 | 86 | ctx.run(); 87 | } 88 | 89 | TEST(TimerTest, SocketTimer) 90 | { 91 | socket_addr_t test_addr("127.0.0.1", 2222); 92 | event_context_t ctx(event_strategy::AUTO); 93 | tcp::server_t server; 94 | 95 | // 500ms 96 | microsecond_t span = 500000, point = 0; 97 | 98 | server.on_client_join([span, &point](tcp::server_t &s, tcp::connection_t conn) { 99 | point = get_current_time(); 100 | conn.get_socket()->sleep(span); 101 | socket_buffer_t buffer = socket_buffer_t::from_string("hi"); 102 | buffer.expect().origin_length(); 103 | GTEST_ASSERT_EQ(co::await(tcp::conn_awrite, conn, buffer), io_result::ok); 104 | }); 105 | 106 | server.listen(ctx, test_addr, 1, true); 107 | 108 | tcp::client_t client; 109 | client 110 | .on_server_connect([](tcp::client_t &c, tcp::connection_t conn) { 111 | socket_buffer_t buffer(2); 112 | buffer.expect().origin_length(); 113 | GTEST_ASSERT_EQ(co::await(tcp::conn_aread, conn, buffer), io_result::ok); 114 | }) 115 | .on_server_disconnect([&ctx](tcp::client_t &c, tcp::connection_t conn) { ctx.exit_all(0); }); 116 | 117 | client.connect(ctx, test_addr, 1000); 118 | 119 | ctx.run(); 120 | GTEST_ASSERT_GE(get_current_time() - point, span); 121 | } 122 | -------------------------------------------------------------------------------- /test/net/udp.cc: -------------------------------------------------------------------------------- 1 | #include "net/udp.hpp" 2 | #include "net/co.hpp" 3 | #include "net/event.hpp" 4 | #include "net/socket.hpp" 5 | #include "net/socket_buffer.hpp" 6 | #include 7 | #include 8 | 9 | static std::string test_data = "test string"; 10 | using namespace net; 11 | 12 | TEST(UDPTest, UDPPackageTest) 13 | { 14 | socket_addr_t test_addr("127.0.0.1", 2224); 15 | event_context_t ctx(event_strategy::AUTO); 16 | udp::server_t server; 17 | 18 | server.bind(ctx, test_addr); 19 | server.run([&server, &ctx]() { 20 | auto socket = server.get_socket(); 21 | socket_buffer_t buffer(test_data.size()); 22 | buffer.expect().origin_length(); 23 | socket_addr_t addr; 24 | GTEST_ASSERT_EQ(co::await(socket_aread_from, socket, buffer, addr), io_result::ok); 25 | GTEST_ASSERT_EQ(buffer.to_string(), test_data); 26 | buffer.expect().origin_length(); 27 | GTEST_ASSERT_EQ(co::await(socket_awrite_to, socket, buffer, addr), io_result::ok); 28 | }); 29 | 30 | udp::client_t client; 31 | client.connect(ctx, test_addr, false); 32 | client.run([&client, &test_addr, &ctx]() { 33 | auto socket = client.get_socket(); 34 | socket_buffer_t buffer = socket_buffer_t::from_string(test_data); 35 | buffer.expect().origin_length(); 36 | socket_addr_t addr = test_addr; 37 | GTEST_ASSERT_EQ(co::await(socket_awrite_to, socket, buffer, addr), io_result::ok); 38 | buffer.expect().origin_length(); 39 | GTEST_ASSERT_EQ(co::await(socket_aread_from, socket, buffer, addr), io_result::ok); 40 | GTEST_ASSERT_EQ(buffer.to_string(), test_data); 41 | ctx.exit_all(0); 42 | }); 43 | ctx.run(); 44 | } --------------------------------------------------------------------------------