├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── command.cc ├── command.h ├── command_receiver.cc ├── command_receiver.h ├── command_sender.cc ├── command_sender.h ├── context2.cc ├── context2.h ├── net_snoop_client.cc ├── net_snoop_client.h ├── net_snoop_server.cc ├── net_snoop_server.h ├── netsnoop.cc ├── netsnoop.h ├── netsnoop_multicast.cc ├── netsnoop_select.cc ├── netsnoop_test.cc ├── peer.cc ├── peer.h ├── sock.cc ├── sock.h ├── tcp.cc ├── tcp.h ├── udp.cc └── udp.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.o 3 | *.stackdump 4 | .vscode/ 5 | tags 6 | GTAGS 7 | *.log 8 | *.swp 9 | *.dll 10 | *.obj 11 | *.zip 12 | *.core 13 | *.dmp 14 | netsnoop 15 | netsnoop_test 16 | netsnoop_select 17 | netsnoop_multicast 18 | data/ 19 | out/ 20 | publish/ 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: gcc 3 | script: make package 4 | env: 5 | - CXX=g++-4.8 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.8 12 | - binutils-mingw-w64 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX?=g++ 2 | CXXFLAGS+= -std=c++11 -I. 3 | LIBS=-pthread 4 | OBJ=.o 5 | EXE= 6 | 7 | ifeq ($(OS), Windows_NT) 8 | ARCH=WIN32 9 | endif 10 | 11 | ifeq ($(ARCH),WIN32) 12 | ifneq ($(OS), Windows_NT) 13 | CXX = i686-w64-mingw32-g++-posix 14 | endif 15 | CXXFLAGS += -D WIN32 -D_WIN32_WINNT=0x601 -DFD_SETSIZE=1024 16 | LIBS += -lws2_32 17 | OBJ = .obj 18 | EXE = .exe 19 | endif 20 | 21 | ifeq ($(BUILD),DEBUG) 22 | CXXFLAGS += -O0 -g -D_DEBUG 23 | else 24 | CXXFLAGS += -O3 -s 25 | endif 26 | 27 | BUILD_VERSION?=0.1.$(shell git rev-list --count HEAD) 28 | CXXFLAGS += -DBUILD_VERSION=$(BUILD_VERSION) 29 | 30 | PUBLISHDIR:=./publish 31 | 32 | DEPS = netsnoop.h command.h 33 | OBJS = command$(OBJ) context2$(OBJ) \ 34 | sock$(OBJ) tcp$(OBJ) udp$(OBJ) \ 35 | command_receiver$(OBJ) command_sender$(OBJ) \ 36 | peer$(OBJ) \ 37 | net_snoop_client$(OBJ) net_snoop_server$(OBJ) 38 | EXES = netsnoop$(EXE) netsnoop_test$(EXE) netsnoop_select$(EXE) netsnoop_multicast$(EXE) 39 | 40 | .PHONY: all 41 | all: $(EXES) 42 | 43 | $(EXES): %$(EXE): $(OBJS) %$(OBJ) 44 | $(CXX) $(CXXFLAGS) -o $@ $^ $(LIBS) 45 | 46 | %$(OBJ): %.cc $(DEPS) 47 | $(CXX) $(CXXFLAGS) -c -o $@ $< 48 | 49 | .PHONY: win32 50 | win32: 51 | @make ARCH=WIN32 52 | 53 | .PHONY: win32_debug 54 | win32_debug: 55 | @make ARCH=WIN32 BUILD=DEBUG 56 | 57 | .PHONY: debug 58 | debug: 59 | @make BUILD=DEBUG 60 | 61 | .PHONY: publish 62 | publish: all win32 63 | @mkdir -p $(PUBLISHDIR) 64 | @echo $(EXES) | xargs -n1 | xargs -i cp ./{} $(PUBLISHDIR) 65 | $(eval EXE=.exe) 66 | @echo $(EXES) | xargs -n1 | xargs -i cp ./{} $(PUBLISHDIR) 67 | @echo publish to $(PUBLISHDIR) dir success! 68 | @echo 69 | 70 | .PHONY: package 71 | package: publish 72 | rm -f netsnoop-v$(BUILD_VERSION).zip 73 | zip -r netsnoop-v$(BUILD_VERSION).zip $(PUBLISHDIR) 74 | @echo pack to netsnoop-$(BUILD_VERSION).zip success! 75 | 76 | .PHONY: clean 77 | clean: 78 | @rm -rf *.o *.obj 79 | @echo $(EXES) | xargs -n1 rm -f 80 | $(eval EXE=.exe) 81 | @echo $(EXES) | xargs -n1 rm -f 82 | @echo clean success! 83 | 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # netsnoop 3 | 4 | [![Build Status](https://travis-ci.com/keyou/netsnoop.svg?token=wDoYyAQNx1yEgEQp2UUi&branch=master)](https://travis-ci.com/keyou/netsnoop) 5 | 6 | A network snoop tool, support detect multi-client (which is not supported by iperf3 now) network (unicast/multicast) bandwith,delay,jitter,loss. 7 | 8 | ## Usage 9 | 10 | ```sh 11 | $ ./netsnoop -h 12 | usage: 13 | netsnoop -s 4000 (start server) 14 | netsnoop -c 4000 (start client) 15 | -------- 16 | command: 17 | ping count 10 (test delay) 18 | send count 1000 (test unicast) 19 | send count 1000 multicast true (test multicast) 20 | send speed 500 time 3000 (test unicast) 21 | 22 | version: v0.1.85 (Aug 28 2019 15:02:50) 23 | ``` 24 | 25 | In server side run: 26 | 27 | ```sh 28 | netsnoop -s 29 | ``` 30 | 31 | In all clients run: 32 | 33 | ```sh 34 | # Note: you must specif a NON-loopback ip to make multicast valid. 35 | netsnoop -c 36 | ``` 37 | 38 | When all clients has connected, you can send commands like belows in server side: 39 | 40 | ```python 41 | # detect use default params. 42 | ping # detect network delay and packet loss rate. 43 | send # detect network bandwith and packet loss rate. 44 | send multicast true # detect multicast bandwith. 45 | 46 | # you can also specif params as you like. 47 | 48 | # send 10 packets, send one packet every 200 milliseconds, every packet contains 1472 bytes data. 49 | ping count 10 interval 200 size 1472 50 | 51 | # send 200 packets, send one packet every 10 milliseconds, every packet contains 1472 bytes data. 52 | # you can add 'multicast true' to detect multicast performance. 53 | # you can add 'wait 500' to make client wait 500 milliseconds until stop receive data. 54 | send count 200 interal 10 size 1472 55 | 56 | # send in 500KB/s speed, 3000 milliseconds. 57 | send speed 500 time 3000 58 | ``` 59 | 60 | ## Subcommands 61 | 62 | `ping` Format: 63 | 64 | ```python 65 | ping [count ] [interval ] [size ] [wait ] 66 | ``` 67 | 68 | `send` Format: 69 | 70 | ```python 71 | send [count ] [interval ] [size ] [wait ] \ 72 | [speed ] [time ] [timeout ] 73 | ``` 74 | 75 | ## Advanced Usage 76 | 77 | You can use script file with netsnoop to run multiple commands automatically: 78 | 79 | ```sh 80 | # check script.snoop file contents. 81 | $ cat script.snoop 82 | peers 2 83 | ping count 10 84 | send count 100 interval 10 wait 500 85 | sleep 5 86 | send multicast true speed 100 time 3000 87 | 88 | # run netsnoop as a server with a script file 89 | $ netsnoop -s < script.snoop 90 | ``` 91 | 92 | The `script.snoop` file is a text file: 93 | 94 | - `peers 2`: wait until 2 peers connect; 95 | - `sleep 5`: wait 5 seconds; 96 | 97 | ## Features 98 | 99 | Currently, `netsnoop` support these 32 features: 100 | 101 | Property Name | Explain | Notes 102 | ---------|----------|--------- 103 | loss | Loss Rate | 104 | (send/recv)_speed | Send/Recv Speed | 105 | (send/recv)_avg_speed | Average Send/Recv Speed | 106 | (max/min)_(send/recv)_speed | Max/Min Send/Recv Speed | 107 | (send/recv)_packets | Send/Recv Packets Count | 108 | illegal_packets | Illegal Packets Count | 109 | reorder_packets | Reorder Packets Count | 110 | duplicate_packets | Duplicate Packets Count | 111 | timeout_packets | Timeout Packets Count | 112 | (send/recv)_pps | Send/Recv pps | 113 | (send/recv)_bytes | Send/Recv Bytes | 114 | \[(min/max)_\]\(send/recv\)_time | Send/Recv Average/Min/Max Time | 115 | \[(min/max)_\]delay | Packets Average/Min/Max Delay | 116 | jitter | Packets Delay Jitter | 117 | jitter_std | Jitter Standard Deviation | 118 | peers_count | Connect Clients Count (when test start) | 119 | peers_failed | Disconnect Clients Count | 120 | 121 | ## For developers 122 | 123 | Welcome to expand more subcommands. This tool is not fully complete, although the code is friendly with unicast, it is not friendly with multicast now. Welcome to reflector the multicast code. 124 | 125 | TODO: 126 | 127 | - Support 'recv' command which is revese of 'send'. 128 | - Support NAT environment. 129 | - Optimize multicast code. 130 | - Optimize command line arguements resolve code. 131 | - Refine documents about netsnoop_select, netsnoop_multicast. 132 | 133 | ### Compile 134 | 135 | #### Linux 136 | 137 | In Linux alike system just run `make`. 138 | 139 | And you can run `make package` to compile and pack the binary to zip archive. 140 | It will compile linux and win32 binaries(mingw needed.). 141 | 142 | #### Windows 143 | 144 | In windows system you should install mingw or msys2 first, then run `make`. 145 | -------------------------------------------------------------------------------- /command.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "command.h" 3 | 4 | #define REGISER_COMMAND(name, T) \ 5 | static CommandRegister __command_##name(#name) 6 | #define REGISER_PRIVATE_COMMAND(name, T) \ 7 | static CommandRegister __command_##name(#name,true) 8 | 9 | 10 | REGISER_COMMAND(ping,EchoCommand); 11 | REGISER_COMMAND(send,SendCommand); 12 | REGISER_PRIVATE_COMMAND(ack,AckCommand); 13 | REGISER_PRIVATE_COMMAND(stop,StopCommand); 14 | REGISER_PRIVATE_COMMAND(result,ResultCommand); 15 | 16 | 17 | -------------------------------------------------------------------------------- /command.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "command_receiver.h" 12 | #include "command_sender.h" 13 | #include "netsnoop.h" 14 | 15 | #define MAX_CMD_LENGTH 1024 16 | #define MAX_TOKEN_LENGTH 10 17 | // time in microseconds wait to give a chance for client receive all data 18 | #define STOP_WAIT_TIME 500*1000 19 | 20 | // use as an identity of a main command 21 | #define VISIABLE_LATTERS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 22 | 23 | class CommandFactory; 24 | class Command; 25 | class NetStat; 26 | 27 | extern std::map g_cmd_map; 28 | 29 | using CommandCallback = std::function)>; 30 | 31 | #pragma region CommandFactory 32 | 33 | using CommandArgs = std::map; 34 | using Ctor = CommandFactory *; 35 | using CommandContainer = std::map; 36 | 37 | class CommandFactory 38 | { 39 | public: 40 | static std::shared_ptr New(const std::string &cmd) 41 | { 42 | ASSERT_RETURN(cmd.length() <= MAX_CMD_LENGTH - MAX_TOKEN_LENGTH, NULL, "cmd too long."); 43 | CommandArgs args; 44 | std::stringstream ss(cmd); 45 | std::string name, key, value; 46 | ss >> name; 47 | if (Container().find(name) == Container().end()) 48 | { 49 | LOGWP("illegal command: %s", cmd.c_str()); 50 | return NULL; 51 | } 52 | while (ss >> key) 53 | { 54 | if (ss >> value) 55 | { 56 | ASSERT_RETURN(args.find(key) == args.end(),NULL); 57 | args[key] = value; 58 | } 59 | else 60 | { 61 | ASSERT_RETURN(!value.empty(),NULL); 62 | args[key] = ""; 63 | } 64 | } 65 | return Container()[name]->NewCommand(cmd, args); 66 | } 67 | 68 | protected: 69 | static CommandContainer &Container() 70 | { 71 | static CommandContainer commands; 72 | return commands; 73 | } 74 | virtual std::shared_ptr NewCommand(const std::string &cmd, CommandArgs args) = 0; 75 | }; 76 | template 77 | class CommandRegister : public CommandFactory 78 | { 79 | public: 80 | CommandRegister(const std::string &name) : CommandRegister(name, false) {} 81 | CommandRegister(const std::string &name, bool is_private) : name_(name), is_private_(is_private) 82 | { 83 | ASSERT(Container().find(name) == Container().end()); 84 | LOGVP("register command: %s", name.c_str()); 85 | Container()[name] = this; 86 | } 87 | std::shared_ptr NewCommand(const std::string &cmd, CommandArgs args) override 88 | { 89 | auto command = std::make_shared(cmd); 90 | command->is_private = is_private_; 91 | if (command->ResolveArgs(args)) 92 | { 93 | LOGDP("create new command: %s (%s)", command->GetCmd().c_str(),command->ToString().c_str()); 94 | return command; 95 | } 96 | return NULL; 97 | } 98 | 99 | private: 100 | const std::string name_; 101 | bool is_private_; 102 | }; 103 | 104 | #pragma endregion 105 | 106 | #pragma region NetStat 107 | 108 | /** 109 | * @brief Network State 110 | * 111 | */ 112 | struct NetStat 113 | { 114 | /** 115 | * @brief Network delay in millseconds 116 | * 117 | */ 118 | int delay; 119 | int max_delay; 120 | int min_delay; 121 | 122 | /** 123 | * @brief Jitter in millseconds 124 | * 125 | */ 126 | int jitter; 127 | 128 | /** 129 | * @brief The standard deviation of jitter 130 | * 131 | */ 132 | long long jitter_std; 133 | 134 | /** 135 | * @brief Packet loss percent 136 | * 137 | */ 138 | double loss; 139 | 140 | /** 141 | * @brief Send/Recv packets count 142 | * 143 | */ 144 | long long send_packets; 145 | long long recv_packets; 146 | 147 | /** 148 | * @brief Recved illegal packets count 149 | * 150 | */ 151 | long long illegal_packets; 152 | long long reorder_packets; 153 | long long duplicate_packets; 154 | /** 155 | * @brief The packets that stay too long in the network. 156 | * 157 | */ 158 | long long timeout_packets; 159 | 160 | /** 161 | * @brief Send/Recv data length 162 | * 163 | */ 164 | long long send_bytes; 165 | long long recv_bytes; 166 | 167 | /** 168 | * @brief Command send/recv time in millseconds 169 | * 170 | */ 171 | int send_time; 172 | int recv_time; 173 | /** 174 | * @brief send/recv speed in Byte/s 175 | * 176 | */ 177 | long long send_speed; 178 | long long min_send_speed; 179 | long long max_send_speed; 180 | 181 | long long recv_speed; 182 | long long min_recv_speed; 183 | long long max_recv_speed; 184 | 185 | /** 186 | * @brief send/recv packets per second 187 | * 188 | */ 189 | long long send_pps; 190 | long long recv_pps; 191 | 192 | /*******************************************************************/ 193 | /********** The below properties are arithmetic property. **********/ 194 | /** 195 | * @brief average recv speed 196 | * 197 | */ 198 | long long recv_avg_speed; 199 | long long send_avg_speed; 200 | int max_send_time; 201 | int min_send_time; 202 | int max_recv_time; 203 | int min_recv_time; 204 | 205 | /** 206 | * @brief the peers count when the command start 207 | * 208 | */ 209 | int peers_count; 210 | /** 211 | * @brief the peers count without the faled peers. 212 | * 213 | */ 214 | int peers_failed; 215 | 216 | std::string ToString() const 217 | { 218 | bool istty = isatty(fileno(stdout)); 219 | std::stringstream ss; 220 | #define W(p) \ 221 | if (!istty || p != 0) \ 222 | ss << #p " " << p << " " 223 | W(loss); 224 | W(send_speed); 225 | W(recv_speed); 226 | W(send_avg_speed); 227 | W(recv_avg_speed); 228 | W(max_send_speed); 229 | W(max_recv_speed); 230 | W(min_send_speed); 231 | W(min_recv_speed); 232 | W(send_packets); 233 | W(recv_packets); 234 | W(illegal_packets); 235 | W(reorder_packets); 236 | W(duplicate_packets); 237 | W(timeout_packets); 238 | W(send_pps); 239 | W(recv_pps); 240 | W(send_bytes); 241 | W(recv_bytes); 242 | W(send_time); 243 | W(recv_time); 244 | W(max_send_time); 245 | W(max_recv_time); 246 | W(min_send_time); 247 | W(min_recv_time); 248 | W(delay); 249 | W(min_delay); 250 | W(max_delay); 251 | W(jitter); 252 | W(jitter_std); 253 | W(peers_count); 254 | W(peers_failed); 255 | #undef W 256 | return ss.str(); 257 | } 258 | 259 | void FromCommandArgs(CommandArgs &args) 260 | { 261 | #define RI(p) p = atoi(args[#p].c_str()) 262 | #define RLL(p) p = atoll(args[#p].c_str()) 263 | #define RF(p) p = atof(args[#p].c_str()) 264 | 265 | RF(loss); 266 | RLL(send_speed); 267 | RLL(recv_speed); 268 | RLL(send_avg_speed); 269 | RLL(recv_avg_speed); 270 | RLL(max_send_speed); 271 | RLL(max_recv_speed); 272 | RLL(min_send_speed); 273 | RLL(min_recv_speed); 274 | RLL(send_packets); 275 | RLL(recv_packets); 276 | RLL(illegal_packets); 277 | RLL(reorder_packets); 278 | RLL(duplicate_packets); 279 | RLL(timeout_packets); 280 | RLL(send_pps); 281 | RLL(recv_pps); 282 | RLL(send_bytes); 283 | RLL(recv_bytes); 284 | RI(send_time); 285 | RI(recv_time); 286 | RI(max_send_time); 287 | RI(max_recv_time); 288 | RI(min_send_time); 289 | RI(min_recv_time); 290 | RI(delay); 291 | RI(min_delay); 292 | RI(max_delay); 293 | RI(jitter); 294 | RLL(jitter_std); 295 | RI(peers_count); 296 | RI(peers_failed); 297 | #undef RI 298 | #undef RLL 299 | #undef RF 300 | } 301 | 302 | // TODO: refactor the code to simplify the logic of 'arithmetic property'. 303 | NetStat &operator+=(const NetStat &stat) 304 | { 305 | #define INT(p) p = p + stat.p 306 | #define DOU(p) INT(p) 307 | #define MAX(p) p = std::max(p, stat.p) 308 | #define MIN(p) p = std::min(p, stat.p) 309 | DOU(loss); 310 | INT(send_speed); 311 | INT(recv_speed); 312 | INT(send_avg_speed); 313 | INT(recv_avg_speed); 314 | MAX(max_send_speed); 315 | MAX(max_recv_speed); 316 | MIN(min_send_speed); 317 | MIN(min_recv_speed); 318 | INT(send_packets); 319 | INT(recv_packets); 320 | INT(illegal_packets); 321 | INT(reorder_packets); 322 | INT(duplicate_packets); 323 | INT(timeout_packets); 324 | INT(send_pps); 325 | INT(recv_pps); 326 | INT(send_bytes); 327 | INT(recv_bytes); 328 | INT(delay); 329 | MIN(min_delay); 330 | MAX(max_delay); 331 | INT(jitter); 332 | INT(jitter_std); 333 | INT(send_time); 334 | INT(recv_time); 335 | MAX(max_send_time); 336 | MAX(max_recv_time); 337 | MIN(min_send_time); 338 | MIN(min_recv_time); 339 | INT(peers_count); 340 | INT(peers_failed); 341 | #undef INT 342 | #undef DOU 343 | #undef MAX 344 | #undef MIN 345 | return *this; 346 | } 347 | NetStat &operator/=(int num) 348 | { 349 | #define INT(p) p /= num 350 | #define DOU(p) INT(p) 351 | #define MAX(p) p = p * 1 352 | #define MIN(p) p = p * 1 353 | DOU(loss); 354 | INT(send_speed); 355 | INT(recv_speed); 356 | INT(send_avg_speed); 357 | INT(recv_avg_speed); 358 | MAX(max_send_speed); 359 | MAX(max_recv_speed); 360 | MIN(min_send_speed); 361 | MIN(min_recv_speed); 362 | INT(send_packets); 363 | INT(recv_packets); 364 | INT(illegal_packets); 365 | INT(reorder_packets); 366 | INT(duplicate_packets); 367 | INT(timeout_packets); 368 | INT(send_pps); 369 | INT(recv_pps); 370 | INT(send_bytes); 371 | INT(recv_bytes); 372 | INT(delay); 373 | MIN(min_delay); 374 | MAX(max_delay); 375 | INT(jitter); 376 | INT(jitter_std); 377 | INT(send_time); 378 | INT(recv_time); 379 | MAX(max_send_time); 380 | MAX(max_recv_time); 381 | MIN(min_send_time); 382 | MIN(min_recv_time); 383 | INT(peers_count); 384 | INT(peers_failed); 385 | #undef INT 386 | #undef DOU 387 | #undef MAX 388 | #undef MIN 389 | return *this; 390 | } 391 | }; 392 | 393 | #pragma endregion 394 | 395 | struct CommandChannel 396 | { 397 | std::shared_ptr command_; 398 | std::shared_ptr context_; 399 | std::shared_ptr control_sock_; 400 | std::shared_ptr data_sock_; 401 | }; 402 | 403 | /** 404 | * @brief A command stands for a type of network test. 405 | * 406 | */ 407 | class Command 408 | { 409 | public: 410 | // TODO: optimize command structure to simplify sub command. 411 | Command(std::string name, std::string cmd) 412 | : name(name), cmd(cmd), token('$'), 413 | is_private(false), is_multicast(false) 414 | { 415 | } 416 | void RegisterCallback(CommandCallback callback) 417 | { 418 | if (callback) 419 | callbacks_.push_back(callback); 420 | } 421 | 422 | void InvokeCallback(std::shared_ptr netstat) 423 | { 424 | for (auto &callback : callbacks_) 425 | { 426 | callback(this, netstat); 427 | } 428 | } 429 | 430 | virtual bool ResolveArgs(CommandArgs args) { return true; } 431 | virtual std::shared_ptr CreateCommandSender(std::shared_ptr channel) { return NULL; } 432 | virtual std::shared_ptr CreateCommandReceiver(std::shared_ptr channel) { return NULL; } 433 | 434 | virtual int GetWait() { return STOP_WAIT_TIME; } 435 | 436 | std::string GetCmd() const 437 | { 438 | return (cmd.find(" token") != std::string::npos || token == '$') ? cmd : cmd + " token " + token; 439 | } 440 | 441 | virtual std::string ToString() const 442 | { 443 | return GetCmd(); 444 | }; 445 | 446 | std::string name; 447 | bool is_private; 448 | bool is_multicast; 449 | 450 | char token; 451 | 452 | protected: 453 | void UpdateToken() 454 | { 455 | static unsigned char index = 0; 456 | token = VISIABLE_LATTERS[index++ % (sizeof(VISIABLE_LATTERS) - 1)]; 457 | } 458 | 459 | private: 460 | std::string cmd; 461 | std::vector callbacks_; 462 | 463 | DISALLOW_COPY_AND_ASSIGN(Command); 464 | }; 465 | 466 | struct DataHead 467 | { 468 | // time since epoch in nanoseconds 469 | int64_t timestamp : 64; 470 | // sequence number 471 | uint16_t sequence : 16; 472 | // data length 473 | uint16_t length : 16; 474 | // token used for data validation 475 | char token; 476 | }; 477 | 478 | #define ECHO_DEFAULT_COUNT 5 479 | #define ECHO_DEFAULT_INTERVAL 200*1000 480 | #define ECHO_DEFAULT_SIZE 32 481 | #define ECHO_DEFAULT_WAIT 500*1000 482 | #define ECHO_DEFAULT_TIMEOUT 100 // milliseconds 483 | #define ECHO_DEFAULT_SPEED 0 // KByte/s 484 | #define ECHO_DEFAULT_TIME 0*1000 // milliseconds 485 | /** 486 | * @brief a main command, server send to client and client should echo 487 | * 488 | */ 489 | class EchoCommand : public Command 490 | { 491 | public: 492 | // format: ping [count ] [interval ] [size ] 493 | // example: ping count 10 interval 100 494 | EchoCommand(std::string cmd) 495 | : count_(ECHO_DEFAULT_COUNT), 496 | interval_(ECHO_DEFAULT_INTERVAL), 497 | size_(ECHO_DEFAULT_SIZE), 498 | wait_(ECHO_DEFAULT_WAIT), 499 | Command("ping", cmd) 500 | { 501 | UpdateToken(); 502 | } 503 | bool ResolveArgs(CommandArgs args) override 504 | { 505 | // TODO: optimize these assign. 506 | count_ = args["count"].empty() ? ECHO_DEFAULT_COUNT : std::stoi(args["count"]); 507 | interval_ = args["interval"].empty() ? ECHO_DEFAULT_INTERVAL : std::stod(args["interval"]) * 1000; 508 | size_ = args["size"].empty() ? ECHO_DEFAULT_SIZE : std::stoi(args["size"]); 509 | wait_ = args["wait"].empty() ? ECHO_DEFAULT_WAIT : std::stoi(args["wait"])*1000; 510 | timeout_ = args["timeout"].empty() ? ECHO_DEFAULT_TIMEOUT : std::stoi(args["timeout"]); 511 | if (!args["token"].empty()) 512 | token = args["token"].at(0); 513 | 514 | auto speed = args["speed"].empty() ? ECHO_DEFAULT_SPEED : std::stoi(args["speed"]); 515 | auto time = args["time"].empty() ? ECHO_DEFAULT_TIME : std::stoi(args["time"]); 516 | if (speed > 0 && time > 0) 517 | { 518 | if(size_ == ECHO_DEFAULT_SIZE) size_ = 1472; 519 | count_ = ceil((speed * 1024) * (time / 1000.0) / size_); 520 | interval_ = 1000000/((1.0*speed*1024)/size_); 521 | } 522 | else if(interval_ > 0 && time > 0) 523 | { 524 | count_ = time * 1000.0 / interval_; 525 | } 526 | // echo can not have zero delay 527 | if (interval_ <= 0) 528 | interval_ = ECHO_DEFAULT_INTERVAL; 529 | return true; 530 | } 531 | 532 | std::shared_ptr CreateCommandSender(std::shared_ptr channel) override 533 | { 534 | return std::make_shared(channel); 535 | } 536 | std::shared_ptr CreateCommandReceiver(std::shared_ptr channel) override 537 | { 538 | return std::make_shared(channel); 539 | } 540 | 541 | std::string ToString() const override 542 | { 543 | std::stringstream out; 544 | out << name << " count " << count_ << " interval " << interval_/1000.0 << " size " << size_ << " wait " << wait_/1000.0 << " timeout " << timeout_; 545 | return out.str(); 546 | } 547 | 548 | int GetCount() { return count_; } 549 | int GetInterval() { return interval_; } 550 | int GetSize() { return size_; } 551 | int GetWait() override { return wait_; } 552 | int GetTimeout() { return timeout_; } 553 | 554 | private: 555 | int count_; 556 | int interval_; 557 | int size_; 558 | int wait_; 559 | int timeout_; 560 | 561 | DISALLOW_COPY_AND_ASSIGN(EchoCommand); 562 | }; 563 | 564 | #define SEND_DEFAULT_COUNT 100 565 | #define SEND_DEFAULT_INTERVAL 0*1000 // microseconds 566 | #define SEND_DEFAULT_SIZE 1472 567 | #define SEND_DEFAULT_WAIT 500*1000 // microseconds 568 | #define SEND_DEFAULT_TIMEOUT 100 // milliseconds 569 | #define SEND_DEFAULT_SPEED 0 // KByte/s 570 | #define SEND_DEFAULT_TIME 3000 // milliseconds 571 | /** 572 | * @brief a main command, server send data only and client recv only. 573 | * 574 | */ 575 | class SendCommand : public Command 576 | { 577 | public: 578 | SendCommand(std::string cmd) 579 | : count_(SEND_DEFAULT_COUNT), 580 | interval_(SEND_DEFAULT_INTERVAL), 581 | size_(SEND_DEFAULT_SIZE), 582 | wait_(SEND_DEFAULT_WAIT), 583 | timeout_(SEND_DEFAULT_TIMEOUT), 584 | is_finished(false), Command("send", cmd) 585 | { 586 | UpdateToken(); 587 | } 588 | 589 | bool ResolveArgs(CommandArgs args) override 590 | { 591 | // TODO: optimize these assign. 592 | count_ = args["count"].empty() ? SEND_DEFAULT_COUNT : std::stoi(args["count"]); 593 | interval_ = args["interval"].empty() ? SEND_DEFAULT_INTERVAL : std::stod(args["interval"]) * 1000; 594 | size_ = args["size"].empty() ? SEND_DEFAULT_SIZE : std::stoi(args["size"]); 595 | wait_ = args["wait"].empty() ? SEND_DEFAULT_WAIT : std::stoi(args["wait"]) * 1000; 596 | timeout_ = args["timeout"].empty() ? SEND_DEFAULT_TIMEOUT : std::stoi(args["timeout"]); 597 | if (!args["token"].empty()) 598 | token = args["token"].at(0); 599 | is_multicast = !args["multicast"].empty(); 600 | if (is_multicast) 601 | LOGDP("enable multicast."); 602 | ASSERT(size_>=sizeof(DataHead)); 603 | 604 | auto speed = args["speed"].empty() ? SEND_DEFAULT_SPEED : std::stoi(args["speed"]); 605 | auto time = args["time"].empty() ? SEND_DEFAULT_TIME : std::stoi(args["time"]); 606 | if (speed > 0 && time > 0) 607 | { 608 | count_ = ceil((speed * 1024) * (time / 1000.0) / size_); 609 | interval_ = 1000000/((1.0*speed*1024)/size_); 610 | } 611 | else if(interval_ > 0 && time > 0) 612 | { 613 | count_ = time * 1000.0 / interval_; 614 | } 615 | return true; 616 | } 617 | 618 | std::shared_ptr CreateCommandSender(std::shared_ptr channel) override 619 | { 620 | return std::make_shared(channel); 621 | } 622 | std::shared_ptr CreateCommandReceiver(std::shared_ptr channel) override 623 | { 624 | return std::make_shared(channel); 625 | } 626 | 627 | std::string ToString() const override 628 | { 629 | std::stringstream out; 630 | out << name << " count " << count_ << " interval " << interval_/1000.0 << " size " << size_ << " wait " << wait_/1000.0 << " timeout " << timeout_; 631 | return out.str(); 632 | } 633 | 634 | int GetCount() { return count_; } 635 | /** 636 | * @brief Get the Interval object in microseconds 637 | * 638 | * @return int 639 | */ 640 | int GetInterval() { return interval_; } 641 | int GetSize() { return size_; } 642 | /** 643 | * @brief Get the Wait object in microseconds 644 | * 645 | * @return int 646 | */ 647 | int GetWait() override { return wait_; } 648 | /** 649 | * @brief Get the Timeout object in milliseconds 650 | * 651 | * @return int 652 | */ 653 | int GetTimeout() { return timeout_; } 654 | 655 | bool is_finished; 656 | 657 | private: 658 | int count_; 659 | int interval_; 660 | int size_; 661 | int wait_; 662 | int timeout_; 663 | 664 | DISALLOW_COPY_AND_ASSIGN(SendCommand); 665 | }; 666 | 667 | // #define DEFINE_COMMAND(name,typename) \ 668 | // class typename : public Command \ 669 | // {\ 670 | // public:\ 671 | // typename(std::string cmd):Command(#name,cmd){}\ 672 | // } 673 | 674 | /** 675 | * @brief every client should response a ack command to a main command 676 | * 677 | */ 678 | class AckCommand : public Command 679 | { 680 | public: 681 | AckCommand() : AckCommand("ack") {} 682 | AckCommand(std::string cmd) : Command("ack", cmd) {} 683 | 684 | DISALLOW_COPY_AND_ASSIGN(AckCommand); 685 | }; 686 | 687 | /** 688 | * @brief notify client the command has finished. 689 | * 690 | */ 691 | class StopCommand : public Command 692 | { 693 | public: 694 | StopCommand() : StopCommand("stop") {} 695 | StopCommand(std::string cmd) : Command("stop", cmd) {} 696 | 697 | DISALLOW_COPY_AND_ASSIGN(StopCommand); 698 | }; 699 | 700 | /** 701 | * @brief send the test result to server 702 | * 703 | */ 704 | class ResultCommand : public Command 705 | { 706 | public: 707 | ResultCommand() : ResultCommand("result") {} 708 | ResultCommand(std::string cmd) : Command("result", cmd) {} 709 | bool ResolveArgs(CommandArgs args) override 710 | { 711 | netstat = std::make_shared(); 712 | netstat->FromCommandArgs(args); 713 | return true; 714 | } 715 | std::string Serialize(const NetStat &netstat) 716 | { 717 | return name + " " + netstat.ToString(); 718 | } 719 | 720 | std::shared_ptr netstat; 721 | 722 | DISALLOW_COPY_AND_ASSIGN(ResultCommand); 723 | }; 724 | 725 | class ModeCommand : public Command 726 | { 727 | public: 728 | enum class ModeType 729 | { 730 | None, 731 | UDP, 732 | Multicast //TODO: TCP 733 | }; 734 | 735 | ModeCommand() : ModeCommand("mode") {} 736 | ModeCommand(std::string cmd) : mode_(ModeType::None), Command("mode", cmd) {} 737 | bool ResolveArgs(CommandArgs args) override 738 | { 739 | mode_ = !args["udp"].empty() ? ModeType::UDP : !args["multicast"].empty() ? ModeType::Multicast : ModeType::None; 740 | return mode_ != ModeType::None; 741 | } 742 | ModeType GetModeType() 743 | { 744 | return mode_; 745 | } 746 | 747 | private: 748 | ModeType mode_; 749 | }; -------------------------------------------------------------------------------- /command_receiver.cc: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "command.h" 6 | #include "command_receiver.h" 7 | 8 | CommandReceiver::CommandReceiver(std::shared_ptr channel) 9 | : context_(channel->context_), control_sock_(channel->control_sock_), 10 | data_sock_(channel->data_sock_) 11 | { 12 | } 13 | 14 | int CommandReceiver::RecvPrivateCommand(std::shared_ptr command) 15 | { 16 | ASSERT_RETURN(0, -1, "CommandReceiver recv unexpected command: %s", command->GetCmd().c_str()); 17 | } 18 | 19 | EchoCommandReceiver::EchoCommandReceiver(std::shared_ptr channel) 20 | : send_count_(0), recv_count_(0), running_(false), is_stopping_(false), 21 | command_(std::dynamic_pointer_cast(channel->command_)), CommandReceiver(channel), 22 | illegal_packets_(0), token_(command_->token) 23 | { 24 | } 25 | 26 | int EchoCommandReceiver::Start() 27 | { 28 | LOGDP("EchoCommandReceiver start command."); 29 | ASSERT_RETURN(!running_, -1, "EchoCommandReceiver start unexpeted."); 30 | running_ = true; 31 | //context_->SetReadFd(data_sock_->GetFd()); 32 | return 0; 33 | } 34 | int EchoCommandReceiver::Stop() 35 | { 36 | LOGDP("EchoCommandReceiver stop command."); 37 | ASSERT_RETURN(running_, -1, "EchoCommandReceiver stop unexpeted."); 38 | //context_->ClrReadFd(data_sock_->GetFd()); 39 | //context_->ClrWriteFd(data_sock_->GetFd()); 40 | // allow send result 41 | context_->SetWriteFd(control_sock_->GetFd()); 42 | return 0; 43 | } 44 | int EchoCommandReceiver::Send() 45 | { 46 | LOGVP("EchoCommandReceiver send payload."); 47 | ASSERT_RETURN(running_, -1, "EchoCommandReceiver send unexpeted."); 48 | int result = 0; 49 | context_->ClrWriteFd(data_sock_->GetFd()); 50 | ASSERT(data_queue_.size() > 0); 51 | while (data_queue_.size() > 0) 52 | { 53 | auto buf = data_queue_.front(); 54 | // use sync method to send extra data. 55 | if ((result = data_sock_->Send(&buf[0], buf.length())) < 0) 56 | { 57 | return -1; 58 | } 59 | send_count_++; 60 | data_queue_.pop(); 61 | } 62 | 63 | return result; 64 | } 65 | int EchoCommandReceiver::Recv() 66 | { 67 | LOGVP("EchoCommandReceiver recv payload."); 68 | ASSERT_RETURN(running_, -1, "EchoCommandReceiver recv unexpeted."); 69 | std::string buf(MAX_UDP_LENGTH, 0); 70 | int result = data_sock_->Recv(&buf[0], buf.length()); 71 | buf.resize(std::max(result,0)); 72 | if (result < sizeof(DataHead)) 73 | { 74 | illegal_packets_++; 75 | LOGWP("recv illegal data(%d): length=%d, %s",data_sock_->GetFd(),result,Tools::GetDataSum(buf).c_str()); 76 | return result; 77 | } 78 | 79 | auto head = reinterpret_cast(&buf[0]); 80 | if (token_ != head->token||result!=head->length) 81 | { 82 | illegal_packets_++; 83 | LOGWP("recv illegal data(%d): length=%d, seq=%d, token=%c, expect %c",data_sock_->GetFd(),result,head->sequence, head->token, token_); 84 | return result; 85 | } 86 | 87 | data_queue_.push(buf); 88 | context_->SetWriteFd(data_sock_->GetFd()); 89 | // context_->ClrReadFd(data_sock_->GetFd()); 90 | recv_count_++; 91 | 92 | LOGDP("recv payload data: recv_count %ld seq %d timestamp %ld token %c",recv_count_,head->sequence,head->timestamp,head->token); 93 | return result; 94 | } 95 | 96 | int EchoCommandReceiver::SendPrivateCommand() 97 | { 98 | LOGDP("EchoCommandReceiver send stop"); 99 | int result; 100 | if (data_queue_.size() > 0) 101 | { 102 | LOGWP("final send %ld data.", data_queue_.size()); 103 | //TODO: optimize the logic 104 | Send(); 105 | } 106 | context_->ClrWriteFd(control_sock_->GetFd()); 107 | context_->ClrWriteFd(data_sock_->GetFd()); 108 | running_ = false; 109 | 110 | auto command = std::make_shared(); 111 | auto stat = std::make_shared(); 112 | stat->recv_packets = recv_count_; 113 | stat->send_packets = send_count_; 114 | stat->illegal_packets = illegal_packets_ + out_of_command_packets_; 115 | auto cmd = command->Serialize(*stat); 116 | LOGDP("command finish: %s || %s", command_->GetCmd().c_str(),cmd.c_str()); 117 | if (OnStopped) 118 | OnStopped(command_, stat); 119 | if ((result = control_sock_->Send(cmd.c_str(), cmd.length())) < 0) 120 | { 121 | return -1; 122 | } 123 | return result; 124 | } 125 | 126 | SendCommandReceiver::SendCommandReceiver(std::shared_ptr channel) 127 | : command_(std::dynamic_pointer_cast(channel->command_)), 128 | buf_(MAX_UDP_LENGTH,'\0'),recv_count_(0), recv_bytes_(0), speed_(0), min_speed_(-1), max_speed_(0), 129 | running_(false), is_stopping_(false), latest_recv_bytes_(0), 130 | illegal_packets_(0), reorder_packets_(0), duplicate_packets_(0), timeout_packets_(0), sequence_(0), 131 | token_(command_->token), 132 | CommandReceiver(channel) {} 133 | 134 | int SendCommandReceiver::Start() 135 | { 136 | LOGDP("SendCommandReceiver start command."); 137 | ASSERT_RETURN(!running_, -1, "SendCommandReceiver start unexpeted."); 138 | running_ = true; 139 | //context_->SetReadFd(data_sock_->GetFd()); 140 | return 0; 141 | } 142 | int SendCommandReceiver::Stop() 143 | { 144 | LOGDP("SendCommandReceiver stop command."); 145 | ASSERT_RETURN(running_, -1, "SendCommandReceiver stop unexpeted."); 146 | //context_->ClrReadFd(data_sock_->GetFd()); 147 | //context_->ClrWriteFd(data_sock_->GetFd()); 148 | //context_->ClrReadFd(control_sock_->GetFd()); 149 | // allow to send stop command. 150 | context_->SetWriteFd(control_sock_->GetFd()); 151 | return 0; 152 | } 153 | int SendCommandReceiver::Recv() 154 | { 155 | LOGVP("SendCommandReceiver recv payload."); 156 | ASSERT_RETURN(running_, -1, "SendCommandReceiver recv unexpeted."); 157 | if (recv_count_ == 0) 158 | { 159 | start_ = high_resolution_clock::now(); 160 | begin_ = high_resolution_clock::now(); 161 | } 162 | int result = data_sock_->Recv(&buf_[0], buf_.length()); 163 | buf_.resize(std::max(result,0)); 164 | if (result < sizeof(DataHead)) 165 | { 166 | illegal_packets_++; 167 | LOGWP("recv illegal data(%d): length=%d, %s",data_sock_->GetFd(),result,Tools::GetDataSum(buf_).c_str()); 168 | return result; 169 | } 170 | 171 | auto head = reinterpret_cast(&buf_[0]); 172 | if (token_ != head->token||result!=head->length) 173 | { 174 | illegal_packets_++; 175 | LOGWP("recv illegal data(%d): length=%d, seq=%d, token=%c, expect %c",data_sock_->GetFd(),result,head->sequence, head->token, token_); 176 | return result; 177 | } 178 | 179 | if(packets_.test(head->sequence)) 180 | { 181 | duplicate_packets_++; 182 | LOGWP("recv duplicate data: seq=%d token %c",head->sequence,head->token); 183 | return result; 184 | } 185 | 186 | end_ = high_resolution_clock::now(); 187 | stop_ = high_resolution_clock::now(); 188 | 189 | recv_bytes_ += result; 190 | recv_count_++; 191 | latest_recv_bytes_ += result; 192 | auto time_delay = end_.time_since_epoch().count() - head->timestamp; 193 | 194 | LOGDP("recv payload data: recv_count %ld seq %d expect_seq %d timestamp %ld token %c delay %ld",recv_count_,head->sequence,sequence_,head->timestamp,head->token,time_delay); 195 | 196 | if(head->sequence!=sequence_) 197 | { 198 | reorder_packets_++; 199 | LOGWP("recv reorder data: seq=%d, expect %d",head->sequence,sequence_); 200 | } 201 | 202 | auto jump_count = int(head->sequence) - sequence_; 203 | if((jump_count>0 && jump_countsequence) 207 | { 208 | packets_.reset((sequence_+MAX_SEQ/2)%MAX_SEQ); 209 | //LOGVP("reset %ld",(sequence_+MAX_SEQ/2)%MAX_SEQ); 210 | sequence_++; 211 | } 212 | } 213 | packets_.set(head->sequence); 214 | // cycle reset to reuse sequence 215 | packets_.reset((head->sequence+MAX_SEQ/2)%MAX_SEQ); 216 | //LOGVP("reset %ld",(head->sequence+MAX_SEQ/2)%MAX_SEQ); 217 | 218 | sequence_ = head->sequence+1; 219 | while (packets_.test(sequence_)) 220 | { 221 | sequence_++; 222 | } 223 | 224 | if(recv_count_ == 1) 225 | { 226 | head_avg_delay_ = avg_delay_ = max_delay_ = min_delay_ = time_delay; 227 | //LOGDP("time_gap= %ld",time_delay); 228 | } 229 | 230 | if(time_delay - min_delay_ > command_->GetTimeout()*1000*1000) 231 | { 232 | timeout_packets_++; 233 | } 234 | 235 | max_delay_ = std::max(max_delay_,time_delay); 236 | min_delay_ = std::min(min_delay_,time_delay); 237 | avg_delay_ += (time_delay - avg_delay_)/recv_count_; 238 | 239 | // The first 100 packets' delay may be more pure than all packets'. 240 | if(recv_count_<=100) 241 | { 242 | head_avg_delay_ = avg_delay_; 243 | } 244 | 245 | varn_delay_ = varn_delay_ + (time_delay - head_avg_delay_)*(time_delay- head_avg_delay_); 246 | std_delay_ = std::sqrt(varn_delay_/recv_count_); 247 | 248 | auto seconds = duration_cast>(end_ - begin_).count(); 249 | if (seconds >= 1) 250 | { 251 | int64_t speed = latest_recv_bytes_ / seconds; 252 | min_speed_ = min_speed_ == -1 ? speed : std::min(min_speed_, speed); 253 | max_speed_ = std::max(max_speed_, speed); 254 | LOGIP("latest recv speed: recv_speed %ld recv_bytes %ld recv_time %d", speed, latest_recv_bytes_, int(seconds * 1000)); 255 | latest_recv_bytes_ = 0; 256 | begin_ = high_resolution_clock::now(); 257 | } 258 | #define D(x) (x-min_delay_)/1000/1000 259 | LOGDP("latest recv delay: recv_count %ld delay %ld head_avg_delay %ld avg_delay %ld std_delay %ld max_delay %ld baseline %ld",recv_count_,D(time_delay),D(head_avg_delay_),D(avg_delay_),std_delay_,D(max_delay_),-D(0)); 260 | #undef D 261 | 262 | return result; 263 | } 264 | 265 | int SendCommandReceiver::SendPrivateCommand() 266 | { 267 | LOGDP("SendCommandReceiver send stop"); 268 | int result; 269 | context_->ClrWriteFd(control_sock_->GetFd()); 270 | running_ = false; 271 | 272 | auto stat = std::make_shared(); 273 | stat->recv_bytes = recv_bytes_; 274 | stat->recv_packets = recv_count_; 275 | stat->illegal_packets = illegal_packets_ + out_of_command_packets_; 276 | stat->reorder_packets = reorder_packets_; 277 | stat->duplicate_packets = duplicate_packets_; 278 | stat->timeout_packets = timeout_packets_; 279 | // use the min delay as time gap 280 | stat->delay = (avg_delay_-min_delay_)/1000/1000; 281 | stat->max_delay = (max_delay_-min_delay_)/1000/1000; 282 | stat->min_delay = 0; 283 | // use the head_avg_delay as jitter, because min_delay is always zero 284 | stat->jitter = (head_avg_delay_-min_delay_)/1000/1000; 285 | stat->jitter_std = std_delay_/1000/1000; 286 | auto seconds = duration_cast>(stop_ - start_).count(); 287 | if (seconds >= 0.001) 288 | { 289 | stat->recv_time = seconds * 1000; 290 | stat->recv_speed = recv_bytes_ / seconds; 291 | stat->recv_pps = recv_count_ / seconds; 292 | stat->max_recv_speed = max_speed_; 293 | if (min_speed_ > 0) 294 | stat->min_recv_speed = min_speed_; 295 | } 296 | 297 | auto command = std::make_shared(); 298 | auto cmd = command->Serialize(*stat); 299 | LOGDP("command finish: %s || %s", command_->GetCmd().c_str(),cmd.c_str()); 300 | if (OnStopped) 301 | OnStopped(command_, stat); 302 | if ((result = control_sock_->Send(cmd.c_str(), cmd.length())) < 0) 303 | { 304 | return -1; 305 | } 306 | return 0; 307 | } 308 | -------------------------------------------------------------------------------- /command_receiver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "context2.h" 8 | #include "sock.h" 9 | 10 | using namespace std::chrono; 11 | 12 | class Command; 13 | class CommandChannel; 14 | class EchoCommand; 15 | class SendCommand; 16 | class NetStat; 17 | 18 | class CommandReceiver 19 | { 20 | public: 21 | CommandReceiver(std::shared_ptr channel); 22 | 23 | virtual int Start() = 0; 24 | virtual int Stop() = 0; 25 | virtual int Send() 26 | { 27 | return -1; 28 | } 29 | virtual int Recv() 30 | { 31 | return -1; 32 | } 33 | virtual int RecvPrivateCommand(std::shared_ptr private_command); 34 | virtual int SendPrivateCommand() { return 0; } 35 | 36 | std::function, std::shared_ptr)> OnStopped; 37 | 38 | int GetDataFd() { return data_sock_ ? data_sock_->GetFd() : -1; } 39 | 40 | // TODO: optimize 41 | ssize_t out_of_command_packets_ = 0; 42 | protected: 43 | std::string argv_; 44 | std::shared_ptr context_; 45 | std::shared_ptr control_sock_; 46 | std::shared_ptr data_sock_; 47 | }; 48 | 49 | class EchoCommandReceiver : public CommandReceiver 50 | { 51 | public: 52 | EchoCommandReceiver(std::shared_ptr channel); 53 | 54 | int Start() override; 55 | int Stop() override; 56 | int Send() override; 57 | int Recv() override; 58 | int SendPrivateCommand() override; 59 | 60 | private: 61 | ssize_t recv_count_; 62 | ssize_t send_count_; 63 | bool running_; 64 | bool is_stopping_; 65 | std::shared_ptr command_; 66 | std::queue data_queue_; 67 | 68 | ssize_t illegal_packets_; 69 | //ssize_t reorder_packets_; 70 | // TODO: support duplicate packets stat 71 | //ssize_t duplicate_packets_; 72 | //ssize_t timeout_packets_; 73 | char token_; 74 | //uint16_t sequence_; 75 | //std::bitset packets_; 76 | }; 77 | 78 | class SendCommandReceiver : public CommandReceiver 79 | { 80 | public: 81 | SendCommandReceiver(std::shared_ptr channel); 82 | 83 | int Start() override; 84 | int Stop() override; 85 | int Recv() override; 86 | int SendPrivateCommand() override; 87 | 88 | private: 89 | std::string buf_; 90 | bool running_; 91 | bool is_stopping_; 92 | std::shared_ptr command_; 93 | 94 | high_resolution_clock::time_point start_; 95 | high_resolution_clock::time_point stop_; 96 | high_resolution_clock::time_point begin_; 97 | high_resolution_clock::time_point end_; 98 | 99 | ssize_t recv_count_; 100 | int64_t recv_bytes_; 101 | int64_t speed_; 102 | int64_t max_speed_; 103 | int64_t min_speed_; 104 | ssize_t illegal_packets_; 105 | ssize_t reorder_packets_; 106 | ssize_t duplicate_packets_; 107 | ssize_t timeout_packets_; 108 | 109 | ssize_t latest_recv_bytes_; 110 | char token_; 111 | uint16_t sequence_; 112 | std::bitset packets_; 113 | 114 | int64_t avg_delay_ = 0; 115 | int64_t max_delay_ = 0; 116 | int64_t min_delay_ = 0; 117 | int64_t head_avg_delay_ = 0;; 118 | // var_delay_ = varn_delay_/n is variance 119 | uint64_t varn_delay_ = 0; 120 | // std_delay_ is standard deviation 121 | uint64_t std_delay_ = 0; 122 | }; 123 | -------------------------------------------------------------------------------- /command_sender.cc: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "command.h" 8 | #include "netsnoop.h" 9 | #include "udp.h" 10 | #include "tcp.h" 11 | #include "context2.h" 12 | #include "peer.h" 13 | #include "command_sender.h" 14 | 15 | CommandSender::CommandSender(std::shared_ptr channel) 16 | : timeout_(-1), control_sock_(channel->control_sock_), data_sock_(channel->data_sock_), 17 | context_(channel->context_), command_(channel->command_), 18 | is_stopping_(false), is_stopped_(false), is_waiting_result_(false), 19 | is_starting_(false), is_started_(false),is_waiting_ack_(false) 20 | { 21 | } 22 | 23 | int CommandSender::Start() 24 | { 25 | LOGDP("CommandSender start command."); 26 | is_starting_ = true; 27 | // allow control channel send command 28 | context_->SetWriteFd(control_sock_->GetFd()); 29 | return 0; 30 | } 31 | 32 | int CommandSender::OnStart() 33 | { 34 | return 0; 35 | } 36 | /** 37 | * @brief Stop data channel and start stop 38 | * 39 | * @return int 40 | */ 41 | int CommandSender::Stop() 42 | { 43 | LOGDP("CommandSender stop command."); 44 | ASSERT(!is_stopping_); 45 | is_stopping_ = true; 46 | // allow to send stop command 47 | //context_->SetWriteFd(control_sock_->GetFd()); 48 | //timeout_ = -1; 49 | ASSERT(command_); 50 | // wait to allow client receive all data 51 | SetTimeout(command_->GetWait()); 52 | return 0; 53 | } 54 | 55 | int CommandSender::OnStop(std::shared_ptr netstat) 56 | { 57 | if(OnStopped) OnStopped(netstat); 58 | return 0; 59 | } 60 | 61 | int CommandSender::SendCommand() 62 | { 63 | int result; 64 | // stop control channel write 65 | context_->ClrWriteFd(control_sock_->GetFd()); 66 | ASSERT_RETURN(!is_stopped_,-1,"CommandSender has already stopped."); 67 | if (is_starting_) 68 | { 69 | is_starting_ = false; 70 | is_waiting_ack_ = true; 71 | LOGDP("CommandSender send command: %s", command_->GetCmd().c_str()); 72 | if ((result = control_sock_->Send(command_->GetCmd().c_str(), command_->GetCmd().length())) < 0) 73 | { 74 | LOGEP("CommandSender send command error."); 75 | return -1; 76 | } 77 | return result; 78 | } 79 | if (is_stopping_) 80 | { 81 | ASSERT(is_started_); 82 | ASSERT(!is_waiting_result_); 83 | is_stopping_ = false; 84 | is_waiting_result_ = true; 85 | LOGDP("CommandSender send stop for: %s", command_->GetCmd().c_str()); 86 | auto stop_command = std::make_shared(); 87 | result = control_sock_->Send(stop_command->GetCmd().c_str(), stop_command->GetCmd().length()); 88 | if(result <= 0) return -1; 89 | return result; 90 | } 91 | return OnSendCommand(); 92 | } 93 | 94 | int CommandSender::OnSendCommand() 95 | { 96 | ASSERT_RETURN(0,-1,"CommandSender has no command to send."); 97 | } 98 | 99 | int CommandSender::RecvCommand() 100 | { 101 | int result; 102 | char buf[MAX_CMD_LENGTH] = {0}; 103 | result = control_sock_->Recv(buf, sizeof(buf)); 104 | // client disconnected. 105 | if(result<=0) return -1; 106 | auto command = CommandFactory::New(buf); 107 | ASSERT_RETURN(command,-1); 108 | if (is_waiting_result_) 109 | { 110 | is_waiting_result_ = false; 111 | is_stopped_ = true; 112 | auto result_command = std::dynamic_pointer_cast(command); 113 | ASSERT_RETURN(result_command, -1, "CommandSender expect recv result command: %s", command->GetCmd().c_str()); 114 | LOGDP("CommandSender recv result: %s",result_command->netstat->ToString().c_str()); 115 | // should not clear control sock,keep control sock readable for detecting client disconnect 116 | //context_->ClrReadFd(control_sock_->GetFd()); 117 | return OnStop(result_command->netstat); 118 | } 119 | 120 | if(is_waiting_ack_) 121 | { 122 | is_waiting_ack_ = false; 123 | is_started_ = true; 124 | auto ack_command = std::dynamic_pointer_cast(command); 125 | ASSERT_RETURN(ack_command, -1, "CommandSender expect recv ack command: %s", command->GetCmd().c_str()); 126 | return OnStart(); 127 | } 128 | 129 | LOGDP("CommandSender recv private command."); 130 | return OnRecvCommand(command); 131 | } 132 | 133 | int CommandSender::OnRecvCommand(std::shared_ptr command) 134 | { 135 | ASSERT_RETURN(0,-1,"CommandSender recv unexpected command: %s",command?command->GetCmd().c_str():"NULL"); 136 | } 137 | 138 | int CommandSender::Timeout(int timeout) 139 | { 140 | ASSERT(timeout > 0); 141 | timeout_ -= timeout; 142 | if (timeout_ <= 0) 143 | { 144 | if(is_stopping_) 145 | { 146 | // allow to send stop command 147 | context_->SetWriteFd(control_sock_->GetFd()); 148 | } 149 | else return OnTimeout(); 150 | } 151 | return 0; 152 | } 153 | 154 | #pragma region EchoCommandSender 155 | 156 | EchoCommandSender::EchoCommandSender(std::shared_ptr channel) 157 | : command_(std::dynamic_pointer_cast(channel->command_)), 158 | data_buf_(command_->GetSize(), command_->token), 159 | delay_(0), max_delay_(0), min_delay_(0), 160 | send_packets_(0), recv_packets_(0),illegal_packets_(0), 161 | CommandSender(channel) 162 | { 163 | if(data_buf_.size()SetWriteFd(data_sock_->GetFd()); 171 | //context_->ClrReadFd(data_sock_->GetFd()); 172 | //context_->SetReadFd(data_sock_->GetFd()); 173 | SetTimeout(command_->GetInterval()); 174 | return 0; 175 | } 176 | 177 | int EchoCommandSender::SendData() 178 | { 179 | send_packets_++; 180 | //context_->SetReadFd(data_sock_->GetFd()); 181 | context_->ClrWriteFd(data_sock_->GetFd()); 182 | begin_ = high_resolution_clock::now(); 183 | auto head = (DataHead*)&data_buf_[0]; 184 | auto timestamp = begin_.time_since_epoch().count(); 185 | // write timestamp to data 186 | head->timestamp = timestamp; 187 | head->sequence = send_packets_-1; 188 | head->length = data_buf_.length(); 189 | head->token = command_->token; 190 | int result = data_sock_->Send(data_buf_.c_str(), data_buf_.length()); 191 | if(result<0) 192 | { 193 | LOGEP("send payload error(%d).",data_sock_->GetFd()); 194 | } 195 | else 196 | { 197 | LOGDP("send payload data: send_packets %ld seq %ld timestamp %ld token %c",send_packets_,head->sequence,head->timestamp,head->token); 198 | } 199 | return result; 200 | } 201 | 202 | int EchoCommandSender::RecvData() 203 | { 204 | end_ = high_resolution_clock::now(); 205 | int result = data_sock_->Recv(&data_buf_[0], data_buf_.length()); 206 | auto head = (DataHead*)&data_buf_[0]; 207 | if(resulttoken!=command_->token||result!=head->length) 208 | { 209 | LOGWP("recv illegal data(%d): length=%d, %s",data_sock_->GetFd(),result,Tools::GetDataSum(data_buf_.substr(0,result>0?std::min(result,64):0)).c_str()); 210 | illegal_packets_++; 211 | return result; 212 | } 213 | 214 | recv_packets_++; 215 | auto delay = end_.time_since_epoch().count() - head->timestamp; 216 | if(recv_packets_ == 1) 217 | { 218 | max_delay_ = min_delay_ = delay; 219 | } 220 | if(delay>command_->GetTimeout()*1000*1000) 221 | { 222 | timeout_packets_++; 223 | } 224 | 225 | max_delay_ = std::max(max_delay_, delay); 226 | min_delay_ = std::min(min_delay_, delay); 227 | auto old_delay = delay_; 228 | delay_ = delay_ + (delay - delay_)/recv_packets_; 229 | varn_delay_ = varn_delay_ + (delay - old_delay)*(delay-delay_); 230 | std_delay_ = std::sqrt(varn_delay_/recv_packets_); 231 | 232 | LOGDP("recv payload data: recv_packets %ld seq %ld timestamp %ld token %c",recv_packets_,head->sequence,head->timestamp,head->token); 233 | LOGIP("ping delay %.02f",delay/1000.0/1000); 234 | 235 | return result; 236 | } 237 | 238 | int EchoCommandSender::OnTimeout() 239 | { 240 | if (send_packets_ >= command_->GetCount()) 241 | { 242 | stop_ = high_resolution_clock::now(); 243 | // stop data channel 244 | // context_->ClrWriteFd(data_sock_->GetFd()); 245 | return Stop(); 246 | } 247 | context_->SetWriteFd(data_sock_->GetFd()); 248 | SetTimeout(command_->GetInterval()); 249 | return 0; 250 | } 251 | 252 | int EchoCommandSender::OnStop(std::shared_ptr netstat) 253 | { 254 | LOGDP("EchoCommandSender stop payload."); 255 | //context_->ClrReadFd(data_sock_->GetFd()); 256 | if (!OnStopped) 257 | return 0; 258 | 259 | auto stat = netstat;//std::make_shared(); 260 | stat->delay = delay_/(1000*1000); 261 | stat->max_delay = max_delay_/(1000*1000); 262 | stat->min_delay = min_delay_/(1000*1000); 263 | stat->jitter = stat->max_delay - stat->min_delay; 264 | stat->jitter_std = std_delay_/(1000*1000); 265 | stat->send_bytes = send_packets_ * data_buf_.size(); 266 | stat->recv_bytes = recv_packets_ * data_buf_.size(); 267 | stat->send_packets = send_packets_; 268 | stat->recv_packets = recv_packets_; 269 | stat->illegal_packets += illegal_packets_; 270 | stat->timeout_packets = timeout_packets_; 271 | stat->loss = 1 - 1.0 * recv_packets_ / send_packets_; 272 | stat->send_time = duration_cast(stop_ - start_).count(); 273 | auto seconds = duration_cast>(stop_ - start_).count(); 274 | if(stat->send_time>=1) 275 | { 276 | stat->send_pps = send_packets_/ seconds; 277 | stat->recv_pps = recv_packets_/ seconds; 278 | stat->send_speed = stat->send_bytes / seconds; 279 | stat->recv_speed = stat->recv_bytes / seconds; 280 | } 281 | OnStopped(stat); 282 | return 0; 283 | } 284 | 285 | #pragma endregion 286 | 287 | SendCommandSender::SendCommandSender(std::shared_ptr channel) 288 | : command_(std::dynamic_pointer_cast(channel->command_)), 289 | data_buf_(command_->GetSize(), command_->token), 290 | send_packets_(0), send_bytes_(0),is_stoping_(false), 291 | CommandSender(channel) 292 | { 293 | if(data_buf_.size()ClrReadFd(data_sock_->GetFd()); 300 | if(!command_->is_multicast) 301 | context_->SetWriteFd(data_sock_->GetFd()); 302 | //SetTimeout(command_->GetInterval()); 303 | return 0; 304 | } 305 | 306 | int SendCommandSender::SendData() 307 | { 308 | if(command_->is_multicast && is_stoping_) return 0; 309 | if (TryStop()) 310 | { 311 | LOGDP("SendCommandSender stop from send data."); 312 | is_stoping_ = true; 313 | // stop data channel 314 | context_->ClrWriteFd(data_sock_->GetFd()); 315 | return Stop(); 316 | } 317 | LOGDP("SendCommandSender send payload data."); 318 | if(start_.time_since_epoch().count() == 0) 319 | { 320 | start_ = high_resolution_clock::now(); 321 | SetTimeout(command_->GetInterval()); 322 | } 323 | if (command_->GetInterval() > 0) 324 | context_->ClrWriteFd(data_sock_->GetFd()); 325 | 326 | DataHead* head = (DataHead*)&data_buf_[0]; 327 | head->timestamp = high_resolution_clock::now().time_since_epoch().count(); 328 | head->sequence = send_packets_; 329 | head->length = data_buf_.length(); 330 | head->token = command_->token; 331 | int result = data_sock_->Send(data_buf_.c_str(), data_buf_.length()); 332 | if(result<0) 333 | { 334 | LOGEP("send payload error(%d).",data_sock_->GetFd()); 335 | } 336 | else if(result>0) 337 | { 338 | send_packets_++; 339 | send_bytes_+=result; 340 | stop_ = high_resolution_clock::now(); 341 | } 342 | return result; 343 | } 344 | int SendCommandSender::RecvData() 345 | { 346 | // we don't expect recv any data 347 | std::string buf(MAX_UDP_LENGTH,'\0'); 348 | int result = data_sock_->Recv(&buf[0], buf.length()); 349 | buf.resize(std::max(result,0)); 350 | LOGWP("recv illegal data(%d): length=%d, %s",data_sock_->GetFd(),result,Tools::GetDataSum(buf).c_str()); 351 | return result; 352 | } 353 | int SendCommandSender::OnTimeout() 354 | { 355 | if(is_stoping_) return 0; 356 | if (TryStop()) 357 | { 358 | LOGDP("SendCommandSender stop from timeout."); 359 | is_stoping_ = true; 360 | // stop data channel 361 | context_->ClrWriteFd(data_sock_->GetFd()); 362 | return Stop(); 363 | } 364 | 365 | context_->SetWriteFd(data_sock_->GetFd()); 366 | SetTimeout(command_->GetInterval()); 367 | 368 | return 0; 369 | } 370 | bool SendCommandSender::TryStop() 371 | { 372 | if (send_packets_ >= command_->GetCount()||command_->is_finished) 373 | { 374 | return true; 375 | } 376 | return false; 377 | } 378 | int SendCommandSender::OnStop(std::shared_ptr netstat) 379 | { 380 | LOGDP("SendCommandSender stop payload."); 381 | if (!OnStopped) 382 | return 0; 383 | 384 | auto stat = netstat; 385 | if(send_packets_>0) 386 | { 387 | stat->send_bytes = send_bytes_; 388 | stat->send_packets = send_packets_; 389 | stat->send_time = duration_cast(stop_ - start_).count(); 390 | auto seconds = duration_cast>(stop_ - start_).count(); 391 | if (seconds > 0.001) 392 | { 393 | stat->send_pps = send_packets_/ seconds; 394 | stat->send_speed = stat->send_bytes / seconds; 395 | } 396 | stat->loss = 1 - 1.0 * stat->recv_packets / send_packets_; 397 | } 398 | 399 | OnStopped(stat); 400 | return 0; 401 | } 402 | -------------------------------------------------------------------------------- /command_sender.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sock.h" 8 | #include "context2.h" 9 | 10 | using namespace std::chrono; 11 | 12 | class Peer; 13 | class Command; 14 | class CommandChannel; 15 | class EchoCommand; 16 | class SendCommand; 17 | class NetStat; 18 | 19 | using SendCommandClazz = class SendCommand; 20 | 21 | class CommandSender 22 | { 23 | public: 24 | CommandSender(std::shared_ptr channel); 25 | 26 | int Start(); 27 | int Stop(); 28 | int SendCommand(); 29 | int RecvCommand(); 30 | virtual int SendData() { return 0; }; 31 | virtual int RecvData() { return 0; }; 32 | 33 | int Timeout(int timeout); 34 | 35 | void SetTimeout(int timeout) { timeout_ = timeout>0?timeout:-1; }; 36 | int GetTimeout() { return timeout_; }; 37 | 38 | std::function)> OnStopped; 39 | 40 | protected: 41 | virtual int OnSendCommand(); 42 | virtual int OnRecvCommand(std::shared_ptr command); 43 | virtual int OnStart(); 44 | virtual int OnStop(std::shared_ptr result_command); 45 | virtual int OnTimeout() { return 0; }; 46 | std::shared_ptr control_sock_; 47 | std::shared_ptr data_sock_; 48 | std::shared_ptr context_; 49 | 50 | private: 51 | int timeout_; 52 | std::shared_ptr command_; 53 | bool is_stopping_; 54 | bool is_stopped_; 55 | bool is_starting_; 56 | bool is_started_; 57 | bool is_waiting_result_; 58 | bool is_waiting_ack_; 59 | bool can_start_payload_; 60 | 61 | friend class Peer; 62 | 63 | DISALLOW_COPY_AND_ASSIGN(CommandSender); 64 | }; 65 | 66 | class EchoCommandSender : public CommandSender 67 | { 68 | public: 69 | EchoCommandSender(std::shared_ptr channel); 70 | 71 | int SendData() override; 72 | int RecvData() override; 73 | int OnTimeout() override; 74 | 75 | private: 76 | int OnStart() override; 77 | int OnStop(std::shared_ptr netstat) override; 78 | 79 | std::shared_ptr command_; 80 | 81 | high_resolution_clock::time_point start_; 82 | high_resolution_clock::time_point stop_; 83 | high_resolution_clock::time_point begin_; 84 | high_resolution_clock::time_point end_; 85 | 86 | int64_t delay_; 87 | int64_t min_delay_; 88 | int64_t max_delay_; 89 | ssize_t send_packets_; 90 | ssize_t recv_packets_; 91 | std::string data_buf_; 92 | long long illegal_packets_=0; 93 | long long timeout_packets_=0; 94 | 95 | uint64_t varn_delay_ = 0; 96 | uint64_t std_delay_ = 0; 97 | }; 98 | 99 | class SendCommandSender : public CommandSender 100 | { 101 | public: 102 | SendCommandSender(std::shared_ptr channel); 103 | 104 | int SendData() override; 105 | int RecvData() override; 106 | int OnTimeout() override; 107 | 108 | private: 109 | int OnStart() override; 110 | int OnStop(std::shared_ptr netstat) override; 111 | inline bool TryStop(); 112 | 113 | std::shared_ptr command_; 114 | bool is_stoping_; 115 | 116 | high_resolution_clock::time_point start_; 117 | high_resolution_clock::time_point stop_; 118 | high_resolution_clock::time_point begin_; 119 | high_resolution_clock::time_point end_; 120 | 121 | ssize_t send_packets_; 122 | ssize_t send_bytes_; 123 | std::string data_buf_; 124 | }; 125 | -------------------------------------------------------------------------------- /context2.cc: -------------------------------------------------------------------------------- 1 | 2 | #include "context2.h" 3 | 4 | Context::Context() : max_fd(-1), control_fd(-1), data_fd(-1) 5 | { 6 | FD_ZERO(&read_fds); 7 | FD_ZERO(&write_fds); 8 | } 9 | 10 | void Context::SetReadFd(int fd) 11 | { 12 | FD_SET(fd, &read_fds); 13 | max_fd = std::max(max_fd, fd); 14 | } 15 | 16 | void Context::SetWriteFd(int fd) 17 | { 18 | FD_SET(fd, &write_fds); 19 | max_fd = std::max(max_fd, fd); 20 | } 21 | 22 | void Context::ClrReadFd(int fd) 23 | { 24 | FD_CLR(fd, &read_fds); 25 | } 26 | 27 | void Context::ClrWriteFd(int fd) 28 | { 29 | FD_CLR(fd, &write_fds); 30 | } 31 | -------------------------------------------------------------------------------- /context2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sock.h" 8 | 9 | class Peer; 10 | 11 | struct Context 12 | { 13 | Context(); 14 | 15 | void SetReadFd(int fd); 16 | void SetWriteFd(int fd); 17 | void ClrReadFd(int fd); 18 | void ClrWriteFd(int fd); 19 | 20 | int control_fd; 21 | int data_fd; 22 | fd_set read_fds; 23 | fd_set write_fds; 24 | int max_fd; 25 | //std::vector> peers; 26 | }; -------------------------------------------------------------------------------- /net_snoop_client.cc: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "context2.h" 4 | #include "command.h" 5 | #include "net_snoop_client.h" 6 | 7 | int NetSnoopClient::Run() 8 | { 9 | int result; 10 | char buf[100] = {0}; 11 | fd_set read_fds, write_fds; 12 | FD_ZERO(&read_fds); 13 | FD_ZERO(&write_fds); 14 | 15 | context_ = std::make_shared(); 16 | auto context = context_; 17 | 18 | if ((result = Connect()) != 0) 19 | return result; 20 | 21 | while (true) 22 | { 23 | memcpy(&read_fds, &context->read_fds, sizeof(read_fds)); 24 | memcpy(&write_fds, &context->write_fds, sizeof(write_fds)); 25 | 26 | #ifdef _DEBUG 27 | for (int i = 0; i < context_->max_fd + 1; i++) 28 | { 29 | if (FD_ISSET(i, &read_fds)) 30 | { 31 | LOGVP("want read: %d", i); 32 | } 33 | if (FD_ISSET(i, &write_fds)) 34 | { 35 | LOGVP("want write: %d", i); 36 | } 37 | } 38 | #endif 39 | LOGVP("client[%d] selecting",control_sock_->GetFd()); 40 | result = select(context->max_fd + 1, &read_fds, &write_fds, NULL, NULL); 41 | LOGVP("client[%d] selected",control_sock_->GetFd()); 42 | #ifdef _DEBUG 43 | for (int i = 0; i < context_->max_fd + 1; i++) 44 | { 45 | if (FD_ISSET(i, &read_fds)) 46 | { 47 | LOGVP("can read: %d", i); 48 | } 49 | if (FD_ISSET(i, &write_fds)) 50 | { 51 | LOGVP("can write: %d", i); 52 | } 53 | } 54 | #endif 55 | if (result <= 0) 56 | { 57 | PSOCKETERROR("select error"); 58 | return -1; 59 | } 60 | if (FD_ISSET(data_sock_->GetFd(), &write_fds)) 61 | { 62 | result = SendData(); 63 | ASSERT_RETURN(result>=0,-1); 64 | } 65 | if (FD_ISSET(data_sock_->GetFd(), &read_fds)) 66 | { 67 | result = RecvData(data_sock_); 68 | if(result<=0) LOGWP("data sock recv error."); 69 | // in a long delay network(>100ms),we may recv a port unreachable ICMP packet. 70 | //ASSERT_RETURN(result>0,-1); 71 | } 72 | if (FD_ISSET(multicast_sock_->GetFd(), &read_fds)) 73 | { 74 | result = RecvData(multicast_sock_); 75 | ASSERT_RETURN(result>0,-1); 76 | } 77 | if (FD_ISSET(control_sock_->GetFd(), &write_fds)) 78 | { 79 | if ((result = SendCommand()) < 0) 80 | { 81 | LOGEP("client send cmd error."); 82 | break; 83 | } 84 | } 85 | if (FD_ISSET(control_sock_->GetFd(), &read_fds)) 86 | { 87 | if ((result = RecvCommand()) == ERR_DEFAULT) 88 | { 89 | LOGEP("client recv cmd error."); 90 | break; 91 | } 92 | else if(result == ERR_SOCKET_CLOSED) 93 | { 94 | LOGWP("client control socket closed."); 95 | break; 96 | } 97 | } 98 | } 99 | 100 | return -1; 101 | } 102 | 103 | int NetSnoopClient::Connect() 104 | { 105 | int result; 106 | control_sock_ = std::make_shared(); 107 | result = control_sock_->Initialize(); 108 | ASSERT(result > 0); 109 | 110 | #ifndef WIN32 111 | struct timeval timeout; 112 | // wait up to 10 senconds for connect() 113 | timeout.tv_sec = 10; 114 | timeout.tv_usec = 0; 115 | result = setsockopt(control_sock_->GetFd(), SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); 116 | ASSERT(result>=0); 117 | #endif // WIN32 118 | result = control_sock_->Connect(option_->ip_remote, option_->port); 119 | if(result<0) 120 | { 121 | LOGEP("connect to %s:%d error.",option_->ip_remote,option_->port); 122 | return -1; 123 | } 124 | #ifndef WIN32 125 | timeout.tv_sec = 0; 126 | timeout.tv_usec = 0; 127 | result = setsockopt(control_sock_->GetFd(), SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); 128 | ASSERT(result>=0); 129 | #endif // WIN32 130 | 131 | data_sock_ = std::make_shared(); 132 | result = data_sock_->Initialize(); 133 | result = data_sock_->Connect(option_->ip_remote, option_->port); 134 | ASSERT_RETURN(result >= 0,-1,"data socket connect server error."); 135 | 136 | std::string ip_local; 137 | int port_local; 138 | result = data_sock_->GetLocalAddress(ip_local, port_local); 139 | ASSERT(result >= 0); 140 | 141 | multicast_sock_ = std::make_shared(); 142 | result = multicast_sock_->Initialize(); 143 | result = multicast_sock_->Bind("0.0.0.0",option_->port); 144 | ASSERT_RETURN(result >= 0,-1,"multicast socket bind error: %s:%d",ip_local.c_str(),option_->port); 145 | result = multicast_sock_->JoinMUlticastGroup(option_->ip_multicast,ip_local); 146 | ASSERT_RETURN(result>=0,-1); 147 | 148 | // only recv the target's multicast packets,windows can not do this 149 | // result = multicast_sock_->Connect(option_->ip_remote, option_->port); 150 | // ASSERT_RETURN(result >= 0,-1,"multicast socket connect server error."); 151 | 152 | cookie_ = "cookie:" + ip_local + ":" + std::to_string(port_local); 153 | result = control_sock_->Send(cookie_.c_str(), cookie_.length()); 154 | ASSERT_RETURN(result >= 0,-1); 155 | // TODO: optimize this code, wait 100 millseconds for server creating the data sock 156 | usleep(500*1000); 157 | // to make a hole in the firewall. 158 | result = data_sock_->Send(cookie_.c_str(), cookie_.length()); 159 | ASSERT_RETURN(result >= 0,-1); 160 | 161 | context_->control_fd = control_sock_->GetFd(); 162 | context_->data_fd = data_sock_->GetFd(); 163 | 164 | context_->SetReadFd(control_sock_->GetFd()); 165 | context_->SetReadFd(data_sock_->GetFd()); 166 | context_->SetReadFd(multicast_sock_->GetFd()); 167 | 168 | if(OnConnected) OnConnected(); 169 | 170 | return 0; 171 | } 172 | 173 | int NetSnoopClient::RecvCommand() 174 | { 175 | int result; 176 | std::string cmd(MAX_UDP_LENGTH, '\0'); 177 | if ((result = control_sock_->Recv(&cmd[0], cmd.length())) < 0) 178 | { 179 | return ERR_DEFAULT; 180 | } 181 | if(result == 0) 182 | { 183 | // socket closed. 184 | return ERR_SOCKET_CLOSED; 185 | } 186 | cmd.resize(result); 187 | 188 | auto command = CommandFactory::New(cmd); 189 | if (!command) 190 | return ERR_ILLEGAL_DATA; 191 | LOGDP("recv new command: %s",command->GetCmd().c_str()); 192 | if(receiver_ && command->is_private) 193 | { 194 | auto stop_command = std::dynamic_pointer_cast(command); 195 | if(stop_command) 196 | { 197 | return receiver_->Stop(); 198 | } 199 | return receiver_->RecvPrivateCommand(command); 200 | } 201 | 202 | #ifndef WIN32 203 | // clear data socket data. 204 | // std::string buf(MAX_UDP_LENGTH,0); 205 | // while ((result = recv(data_sock_->GetFd(),&buf[0],buf.length(),MSG_DONTWAIT))>0) 206 | // { 207 | // LOGDP("illegal data recved(%d).",result); 208 | // } 209 | #endif // !WIN32 210 | if(!command->is_multicast) 211 | { 212 | // to make a hole in the firewall. 213 | result = data_sock_->Send(cookie_.c_str(), cookie_.length()); 214 | ASSERT_RETURN(result >= 0,ERR_DEFAULT,"send cookie error."); 215 | } 216 | 217 | auto ack_command = std::make_shared(); 218 | result = control_sock_->Send(ack_command->GetCmd().c_str(),ack_command->GetCmd().length()); 219 | ASSERT_RETURN(result>0,ERR_DEFAULT,"send ack command error."); 220 | 221 | auto channel = std::shared_ptr(new CommandChannel{ 222 | command,context_,control_sock_,command->is_multicast?multicast_sock_:data_sock_ 223 | }); 224 | receiver_ = command->CreateCommandReceiver(channel); 225 | ASSERT(receiver_); 226 | receiver_->OnStopped = OnStopped; 227 | return receiver_->Start(); 228 | } 229 | 230 | int NetSnoopClient::SendCommand() 231 | { 232 | ASSERT(receiver_); 233 | receiver_->out_of_command_packets_ = illegal_packets_; 234 | int result = receiver_->SendPrivateCommand(); 235 | receiver_ = NULL; 236 | illegal_packets_ = 0; 237 | return result; 238 | } 239 | 240 | int NetSnoopClient::RecvData(std::shared_ptr data_sock) 241 | { 242 | int result; 243 | if(!receiver_ || receiver_->GetDataFd() != data_sock->GetFd()) 244 | { 245 | std::string buf(MAX_UDP_LENGTH,'\0'); 246 | result = data_sock->Recv(&buf[0],buf.length()); 247 | if(result<=0) 248 | { 249 | LOGWP("recv data error(%d).",data_sock->GetFd()); 250 | return result; 251 | } 252 | buf.resize(result); 253 | illegal_packets_++; 254 | LOGWP("recv out of command data(%d): %s",data_sock->GetFd(),Tools::GetDataSum(buf).c_str()); 255 | return result; 256 | } 257 | result = receiver_->Recv(); 258 | return result; 259 | } 260 | 261 | int NetSnoopClient::SendData() 262 | { 263 | if(!receiver_) 264 | { 265 | context_->ClrWriteFd(data_sock_->GetFd()); 266 | LOGWP("send out of command data(%d).",data_sock_->GetFd()); 267 | return 0; 268 | } 269 | int result = receiver_->Send(); 270 | return result; 271 | } 272 | -------------------------------------------------------------------------------- /net_snoop_client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "tcp.h" 7 | #include "udp.h" 8 | #include "command_receiver.h" 9 | 10 | class NetSnoopClient 11 | { 12 | public: 13 | NetSnoopClient(std::shared_ptr