├── PHPServer └── swoole.php ├── QtClient ├── Demo.pro ├── Demo.pro.user ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui └── source │ ├── dataprocessor.cpp │ ├── dataprocessor.h │ ├── handshakerequest.cpp │ ├── handshakerequest.h │ ├── handshakeresponse.cpp │ ├── handshakeresponse.h │ ├── websocket.cpp │ ├── websocket.h │ ├── websocket.pri │ ├── websocketprotocol.cpp │ ├── websocketprotocol.h │ ├── websocketserver.cpp │ └── websocketserver.h └── README.md /PHPServer/swoole.php: -------------------------------------------------------------------------------- 1 | server = new swoole_websocket_server("192.168.150.138", 9501); 8 | $this->server->on('open', function (swoole_websocket_server $server, $request) { 9 | //建立uid和fd的关系 10 | $this->uid2fd[$request->get['uid']] = $request->fd; 11 | echo "用户 {$request->get['uid']}({$request->fd}) 已上线 \n"; 12 | }); 13 | 14 | $this->server->on('message', function (swoole_websocket_server $server, $frame) { 15 | $uid = array_search($frame->fd, $this->uid2fd); 16 | echo "收到来自 用户 {$uid}({$frame->fd})的数据 :{$frame->data} \n"; 17 | $server->push($frame->fd, "this is server"); 18 | }); 19 | 20 | $this->server->on('close', function ($ser, $fd) { 21 | //关闭连接时,释放对应键 22 | $uid = array_search($fd, $this->uid2fd); 23 | if($uid) 24 | { 25 | unset($this->uid2fd[$uid]); 26 | } 27 | echo "用户{$uid}({$fd})已下线 \n"; 28 | 29 | }); 30 | 31 | $this->server->on('request', function ($request, $response) { 32 | 33 | //获取通知列表 34 | $userArr = json_decode($request->post['user']); 35 | $userArr = is_array($userArr) ? $userArr : array(); 36 | 37 | //遍历发送消息 38 | foreach ($userArr as $key => $uid) 39 | { 40 | if(array_key_exists($uid, $this->uid2fd)) 41 | { 42 | @$this->server->push($this->uid2fd[$uid], $request->post['msg']); 43 | echo "已对用户{$uid}({$this->uid2fd[$uid]})发送通知 \n"; 44 | } 45 | else 46 | { 47 | echo "用户 {$uid} 未上线!\n"; 48 | } 49 | } 50 | // 接收http请求从get获取message参数的值,给用户推送 51 | // foreach ($this->server->connections as $fd) { 52 | // @$this->server->push($fd, $request->get['msg']); 53 | // } 54 | }); 55 | 56 | $this->server->start(); 57 | } 58 | } 59 | new WebsocketTest(); 60 | 61 | 62 | 63 | ?> -------------------------------------------------------------------------------- /QtClient/Demo.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2018-05-11T10:53:49 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | include(source/websocket.pri) 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = Demo 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | 26 | SOURCES += \ 27 | main.cpp \ 28 | mainwindow.cpp 29 | 30 | HEADERS += \ 31 | mainwindow.h 32 | 33 | FORMS += \ 34 | mainwindow.ui 35 | -------------------------------------------------------------------------------- /QtClient/Demo.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {54f2f23a-17a1-486f-a1d1-ad5e42e9f8b4} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | 60 | ProjectExplorer.Project.Target.0 61 | 62 | Desktop Qt 5.10.0 MSVC2015 64bit 63 | Desktop Qt 5.10.0 MSVC2015 64bit 64 | qt.qt5.5100.win64_msvc2015_64_kit 65 | 0 66 | 0 67 | 0 68 | 69 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_64bit-Debug 70 | 71 | 72 | true 73 | qmake 74 | 75 | QtProjectManager.QMakeBuildStep 76 | true 77 | 78 | false 79 | false 80 | false 81 | 82 | 83 | true 84 | Make 85 | 86 | Qt4ProjectManager.MakeStep 87 | 88 | false 89 | 90 | 91 | 92 | 2 93 | 构建 94 | 95 | ProjectExplorer.BuildSteps.Build 96 | 97 | 98 | 99 | true 100 | Make 101 | 102 | Qt4ProjectManager.MakeStep 103 | 104 | true 105 | clean 106 | 107 | 108 | 1 109 | 清理 110 | 111 | ProjectExplorer.BuildSteps.Clean 112 | 113 | 2 114 | false 115 | 116 | Debug 117 | 118 | Qt4ProjectManager.Qt4BuildConfiguration 119 | 2 120 | true 121 | 122 | 123 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_64bit-Release 124 | 125 | 126 | true 127 | qmake 128 | 129 | QtProjectManager.QMakeBuildStep 130 | false 131 | 132 | false 133 | false 134 | false 135 | 136 | 137 | true 138 | Make 139 | 140 | Qt4ProjectManager.MakeStep 141 | 142 | false 143 | 144 | 145 | 146 | 2 147 | 构建 148 | 149 | ProjectExplorer.BuildSteps.Build 150 | 151 | 152 | 153 | true 154 | Make 155 | 156 | Qt4ProjectManager.MakeStep 157 | 158 | true 159 | clean 160 | 161 | 162 | 1 163 | 清理 164 | 165 | ProjectExplorer.BuildSteps.Clean 166 | 167 | 2 168 | false 169 | 170 | Release 171 | 172 | Qt4ProjectManager.Qt4BuildConfiguration 173 | 0 174 | true 175 | 176 | 177 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_64bit-Profile 178 | 179 | 180 | true 181 | qmake 182 | 183 | QtProjectManager.QMakeBuildStep 184 | true 185 | 186 | false 187 | true 188 | false 189 | 190 | 191 | true 192 | Make 193 | 194 | Qt4ProjectManager.MakeStep 195 | 196 | false 197 | 198 | 199 | 200 | 2 201 | 构建 202 | 203 | ProjectExplorer.BuildSteps.Build 204 | 205 | 206 | 207 | true 208 | Make 209 | 210 | Qt4ProjectManager.MakeStep 211 | 212 | true 213 | clean 214 | 215 | 216 | 1 217 | 清理 218 | 219 | ProjectExplorer.BuildSteps.Clean 220 | 221 | 2 222 | false 223 | 224 | Profile 225 | 226 | Qt4ProjectManager.Qt4BuildConfiguration 227 | 0 228 | true 229 | 230 | 3 231 | 232 | 233 | 0 234 | 部署 235 | 236 | ProjectExplorer.BuildSteps.Deploy 237 | 238 | 1 239 | 在本地部署 240 | 241 | ProjectExplorer.DefaultDeployConfiguration 242 | 243 | 1 244 | 245 | 246 | false 247 | false 248 | 1000 249 | 250 | true 251 | 252 | false 253 | false 254 | false 255 | false 256 | true 257 | 0.01 258 | 10 259 | true 260 | 1 261 | 25 262 | 263 | 1 264 | true 265 | false 266 | true 267 | valgrind 268 | 269 | 0 270 | 1 271 | 2 272 | 3 273 | 4 274 | 5 275 | 6 276 | 7 277 | 8 278 | 9 279 | 10 280 | 11 281 | 12 282 | 13 283 | 14 284 | 285 | 2 286 | 287 | Demo 288 | 289 | Qt4ProjectManager.Qt4RunConfiguration:C:/Users/Frider/Desktop/Demo/Demo.pro 290 | true 291 | 292 | Demo.pro 293 | false 294 | 295 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_64bit-Debug 296 | 3768 297 | false 298 | true 299 | false 300 | false 301 | true 302 | 303 | 1 304 | 305 | 306 | 307 | ProjectExplorer.Project.Target.1 308 | 309 | Desktop Qt 5.10.0 MSVC2015 32bit 310 | Desktop Qt 5.10.0 MSVC2015 32bit 311 | qt.qt5.5100.win32_msvc2015_kit 312 | 0 313 | 0 314 | 0 315 | 316 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_32bit-Debug 317 | 318 | 319 | true 320 | qmake 321 | 322 | QtProjectManager.QMakeBuildStep 323 | true 324 | 325 | false 326 | false 327 | false 328 | 329 | 330 | true 331 | Make 332 | 333 | Qt4ProjectManager.MakeStep 334 | 335 | false 336 | 337 | 338 | 339 | 2 340 | 构建 341 | 342 | ProjectExplorer.BuildSteps.Build 343 | 344 | 345 | 346 | true 347 | Make 348 | 349 | Qt4ProjectManager.MakeStep 350 | 351 | true 352 | clean 353 | 354 | 355 | 1 356 | 清理 357 | 358 | ProjectExplorer.BuildSteps.Clean 359 | 360 | 2 361 | false 362 | 363 | Debug 364 | 365 | Qt4ProjectManager.Qt4BuildConfiguration 366 | 2 367 | true 368 | 369 | 370 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_32bit-Release 371 | 372 | 373 | true 374 | qmake 375 | 376 | QtProjectManager.QMakeBuildStep 377 | false 378 | 379 | false 380 | false 381 | false 382 | 383 | 384 | true 385 | Make 386 | 387 | Qt4ProjectManager.MakeStep 388 | 389 | false 390 | 391 | 392 | 393 | 2 394 | 构建 395 | 396 | ProjectExplorer.BuildSteps.Build 397 | 398 | 399 | 400 | true 401 | Make 402 | 403 | Qt4ProjectManager.MakeStep 404 | 405 | true 406 | clean 407 | 408 | 409 | 1 410 | 清理 411 | 412 | ProjectExplorer.BuildSteps.Clean 413 | 414 | 2 415 | false 416 | 417 | Release 418 | 419 | Qt4ProjectManager.Qt4BuildConfiguration 420 | 0 421 | true 422 | 423 | 424 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_32bit-Profile 425 | 426 | 427 | true 428 | qmake 429 | 430 | QtProjectManager.QMakeBuildStep 431 | true 432 | 433 | false 434 | true 435 | false 436 | 437 | 438 | true 439 | Make 440 | 441 | Qt4ProjectManager.MakeStep 442 | 443 | false 444 | 445 | 446 | 447 | 2 448 | 构建 449 | 450 | ProjectExplorer.BuildSteps.Build 451 | 452 | 453 | 454 | true 455 | Make 456 | 457 | Qt4ProjectManager.MakeStep 458 | 459 | true 460 | clean 461 | 462 | 463 | 1 464 | 清理 465 | 466 | ProjectExplorer.BuildSteps.Clean 467 | 468 | 2 469 | false 470 | 471 | Profile 472 | 473 | Qt4ProjectManager.Qt4BuildConfiguration 474 | 0 475 | true 476 | 477 | 3 478 | 479 | 480 | 0 481 | 部署 482 | 483 | ProjectExplorer.BuildSteps.Deploy 484 | 485 | 1 486 | 在本地部署 487 | 488 | ProjectExplorer.DefaultDeployConfiguration 489 | 490 | 1 491 | 492 | 493 | false 494 | false 495 | 1000 496 | 497 | true 498 | 499 | false 500 | false 501 | false 502 | false 503 | true 504 | 0.01 505 | 10 506 | true 507 | 1 508 | 25 509 | 510 | 1 511 | true 512 | false 513 | true 514 | valgrind 515 | 516 | 0 517 | 1 518 | 2 519 | 3 520 | 4 521 | 5 522 | 6 523 | 7 524 | 8 525 | 9 526 | 10 527 | 11 528 | 12 529 | 13 530 | 14 531 | 532 | 2 533 | 534 | Demo 535 | 536 | Qt4ProjectManager.Qt4RunConfiguration:C:/Users/Frider/Desktop/Demo/Demo.pro 537 | true 538 | 539 | Demo.pro 540 | false 541 | 542 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2015_64bit-Debug 543 | 3768 544 | false 545 | true 546 | false 547 | false 548 | true 549 | 550 | 1 551 | 552 | 553 | 554 | ProjectExplorer.Project.Target.2 555 | 556 | Desktop Qt 5.10.0 MSVC2013 64bit 557 | Desktop Qt 5.10.0 MSVC2013 64bit 558 | qt.qt5.5100.win64_msvc2013_64_kit 559 | 0 560 | 0 561 | 0 562 | 563 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2013_64bit-Debug 564 | 565 | 566 | true 567 | qmake 568 | 569 | QtProjectManager.QMakeBuildStep 570 | true 571 | 572 | true 573 | false 574 | false 575 | 576 | 577 | true 578 | Make 579 | 580 | Qt4ProjectManager.MakeStep 581 | 582 | false 583 | 584 | 585 | 586 | 2 587 | 构建 588 | 589 | ProjectExplorer.BuildSteps.Build 590 | 591 | 592 | 593 | true 594 | Make 595 | 596 | Qt4ProjectManager.MakeStep 597 | 598 | true 599 | clean 600 | 601 | 602 | 1 603 | 清理 604 | 605 | ProjectExplorer.BuildSteps.Clean 606 | 607 | 2 608 | false 609 | 610 | Debug 611 | 612 | Qt4ProjectManager.Qt4BuildConfiguration 613 | 2 614 | true 615 | 616 | 617 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2013_64bit-Release 618 | 619 | 620 | true 621 | qmake 622 | 623 | QtProjectManager.QMakeBuildStep 624 | false 625 | 626 | false 627 | false 628 | false 629 | 630 | 631 | true 632 | Make 633 | 634 | Qt4ProjectManager.MakeStep 635 | 636 | false 637 | 638 | 639 | 640 | 2 641 | 构建 642 | 643 | ProjectExplorer.BuildSteps.Build 644 | 645 | 646 | 647 | true 648 | Make 649 | 650 | Qt4ProjectManager.MakeStep 651 | 652 | true 653 | clean 654 | 655 | 656 | 1 657 | 清理 658 | 659 | ProjectExplorer.BuildSteps.Clean 660 | 661 | 2 662 | false 663 | 664 | Release 665 | 666 | Qt4ProjectManager.Qt4BuildConfiguration 667 | 0 668 | true 669 | 670 | 671 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2013_64bit-Profile 672 | 673 | 674 | true 675 | qmake 676 | 677 | QtProjectManager.QMakeBuildStep 678 | true 679 | 680 | false 681 | true 682 | false 683 | 684 | 685 | true 686 | Make 687 | 688 | Qt4ProjectManager.MakeStep 689 | 690 | false 691 | 692 | 693 | 694 | 2 695 | 构建 696 | 697 | ProjectExplorer.BuildSteps.Build 698 | 699 | 700 | 701 | true 702 | Make 703 | 704 | Qt4ProjectManager.MakeStep 705 | 706 | true 707 | clean 708 | 709 | 710 | 1 711 | 清理 712 | 713 | ProjectExplorer.BuildSteps.Clean 714 | 715 | 2 716 | false 717 | 718 | Profile 719 | 720 | Qt4ProjectManager.Qt4BuildConfiguration 721 | 0 722 | true 723 | 724 | 3 725 | 726 | 727 | 0 728 | 部署 729 | 730 | ProjectExplorer.BuildSteps.Deploy 731 | 732 | 1 733 | 在本地部署 734 | 735 | ProjectExplorer.DefaultDeployConfiguration 736 | 737 | 1 738 | 739 | 740 | false 741 | false 742 | 1000 743 | 744 | true 745 | 746 | false 747 | false 748 | false 749 | false 750 | true 751 | 0.01 752 | 10 753 | true 754 | 1 755 | 25 756 | 757 | 1 758 | true 759 | false 760 | true 761 | valgrind 762 | 763 | 0 764 | 1 765 | 2 766 | 3 767 | 4 768 | 5 769 | 6 770 | 7 771 | 8 772 | 9 773 | 10 774 | 11 775 | 12 776 | 13 777 | 14 778 | 779 | 2 780 | 781 | 782 | 783 | %{buildDir} 784 | Custom Executable 785 | 786 | ProjectExplorer.CustomExecutableRunConfiguration 787 | 3768 788 | false 789 | true 790 | false 791 | false 792 | true 793 | 794 | 1 795 | 796 | 797 | 798 | ProjectExplorer.Project.Target.3 799 | 800 | Desktop Qt 5.10.0 MSVC2017 64bit 801 | Desktop Qt 5.10.0 MSVC2017 64bit 802 | qt.qt5.5100.win64_msvc2017_64_kit 803 | 0 804 | 0 805 | 0 806 | 807 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2017_64bit-Debug 808 | 809 | 810 | true 811 | qmake 812 | 813 | QtProjectManager.QMakeBuildStep 814 | true 815 | 816 | false 817 | false 818 | false 819 | 820 | 821 | true 822 | Make 823 | 824 | Qt4ProjectManager.MakeStep 825 | 826 | false 827 | 828 | 829 | 830 | 2 831 | 构建 832 | 833 | ProjectExplorer.BuildSteps.Build 834 | 835 | 836 | 837 | true 838 | Make 839 | 840 | Qt4ProjectManager.MakeStep 841 | 842 | true 843 | clean 844 | 845 | 846 | 1 847 | 清理 848 | 849 | ProjectExplorer.BuildSteps.Clean 850 | 851 | 2 852 | false 853 | 854 | Debug 855 | 856 | Qt4ProjectManager.Qt4BuildConfiguration 857 | 2 858 | true 859 | 860 | 861 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2017_64bit-Release 862 | 863 | 864 | true 865 | qmake 866 | 867 | QtProjectManager.QMakeBuildStep 868 | false 869 | 870 | false 871 | false 872 | false 873 | 874 | 875 | true 876 | Make 877 | 878 | Qt4ProjectManager.MakeStep 879 | 880 | false 881 | 882 | 883 | 884 | 2 885 | 构建 886 | 887 | ProjectExplorer.BuildSteps.Build 888 | 889 | 890 | 891 | true 892 | Make 893 | 894 | Qt4ProjectManager.MakeStep 895 | 896 | true 897 | clean 898 | 899 | 900 | 1 901 | 清理 902 | 903 | ProjectExplorer.BuildSteps.Clean 904 | 905 | 2 906 | false 907 | 908 | Release 909 | 910 | Qt4ProjectManager.Qt4BuildConfiguration 911 | 0 912 | true 913 | 914 | 915 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MSVC2017_64bit-Profile 916 | 917 | 918 | true 919 | qmake 920 | 921 | QtProjectManager.QMakeBuildStep 922 | true 923 | 924 | false 925 | true 926 | false 927 | 928 | 929 | true 930 | Make 931 | 932 | Qt4ProjectManager.MakeStep 933 | 934 | false 935 | 936 | 937 | 938 | 2 939 | 构建 940 | 941 | ProjectExplorer.BuildSteps.Build 942 | 943 | 944 | 945 | true 946 | Make 947 | 948 | Qt4ProjectManager.MakeStep 949 | 950 | true 951 | clean 952 | 953 | 954 | 1 955 | 清理 956 | 957 | ProjectExplorer.BuildSteps.Clean 958 | 959 | 2 960 | false 961 | 962 | Profile 963 | 964 | Qt4ProjectManager.Qt4BuildConfiguration 965 | 0 966 | true 967 | 968 | 3 969 | 970 | 971 | 0 972 | 部署 973 | 974 | ProjectExplorer.BuildSteps.Deploy 975 | 976 | 1 977 | 在本地部署 978 | 979 | ProjectExplorer.DefaultDeployConfiguration 980 | 981 | 1 982 | 983 | 984 | false 985 | false 986 | 1000 987 | 988 | true 989 | 990 | false 991 | false 992 | false 993 | false 994 | true 995 | 0.01 996 | 10 997 | true 998 | 1 999 | 25 1000 | 1001 | 1 1002 | true 1003 | false 1004 | true 1005 | valgrind 1006 | 1007 | 0 1008 | 1 1009 | 2 1010 | 3 1011 | 4 1012 | 5 1013 | 6 1014 | 7 1015 | 8 1016 | 9 1017 | 10 1018 | 11 1019 | 12 1020 | 13 1021 | 14 1022 | 1023 | 2 1024 | 1025 | 1026 | 1027 | %{buildDir} 1028 | Custom Executable 1029 | 1030 | ProjectExplorer.CustomExecutableRunConfiguration 1031 | 3768 1032 | false 1033 | true 1034 | false 1035 | false 1036 | true 1037 | 1038 | 1 1039 | 1040 | 1041 | 1042 | ProjectExplorer.Project.Target.4 1043 | 1044 | Desktop Qt 5.10.0 MinGW 32bit 1045 | Desktop Qt 5.10.0 MinGW 32bit 1046 | qt.qt5.5100.win32_mingw53_kit 1047 | 0 1048 | 0 1049 | 0 1050 | 1051 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MinGW_32bit-Debug 1052 | 1053 | 1054 | true 1055 | qmake 1056 | 1057 | QtProjectManager.QMakeBuildStep 1058 | true 1059 | 1060 | false 1061 | false 1062 | false 1063 | 1064 | 1065 | true 1066 | Make 1067 | 1068 | Qt4ProjectManager.MakeStep 1069 | 1070 | false 1071 | 1072 | 1073 | 1074 | 2 1075 | 构建 1076 | 1077 | ProjectExplorer.BuildSteps.Build 1078 | 1079 | 1080 | 1081 | true 1082 | Make 1083 | 1084 | Qt4ProjectManager.MakeStep 1085 | 1086 | true 1087 | clean 1088 | 1089 | 1090 | 1 1091 | 清理 1092 | 1093 | ProjectExplorer.BuildSteps.Clean 1094 | 1095 | 2 1096 | false 1097 | 1098 | Debug 1099 | 1100 | Qt4ProjectManager.Qt4BuildConfiguration 1101 | 2 1102 | true 1103 | 1104 | 1105 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MinGW_32bit-Release 1106 | 1107 | 1108 | true 1109 | qmake 1110 | 1111 | QtProjectManager.QMakeBuildStep 1112 | false 1113 | 1114 | false 1115 | false 1116 | false 1117 | 1118 | 1119 | true 1120 | Make 1121 | 1122 | Qt4ProjectManager.MakeStep 1123 | 1124 | false 1125 | 1126 | 1127 | 1128 | 2 1129 | 构建 1130 | 1131 | ProjectExplorer.BuildSteps.Build 1132 | 1133 | 1134 | 1135 | true 1136 | Make 1137 | 1138 | Qt4ProjectManager.MakeStep 1139 | 1140 | true 1141 | clean 1142 | 1143 | 1144 | 1 1145 | 清理 1146 | 1147 | ProjectExplorer.BuildSteps.Clean 1148 | 1149 | 2 1150 | false 1151 | 1152 | Release 1153 | 1154 | Qt4ProjectManager.Qt4BuildConfiguration 1155 | 0 1156 | true 1157 | 1158 | 1159 | C:/Users/Frider/Desktop/build-Demo-Desktop_Qt_5_10_0_MinGW_32bit-Profile 1160 | 1161 | 1162 | true 1163 | qmake 1164 | 1165 | QtProjectManager.QMakeBuildStep 1166 | true 1167 | 1168 | false 1169 | true 1170 | false 1171 | 1172 | 1173 | true 1174 | Make 1175 | 1176 | Qt4ProjectManager.MakeStep 1177 | 1178 | false 1179 | 1180 | 1181 | 1182 | 2 1183 | 构建 1184 | 1185 | ProjectExplorer.BuildSteps.Build 1186 | 1187 | 1188 | 1189 | true 1190 | Make 1191 | 1192 | Qt4ProjectManager.MakeStep 1193 | 1194 | true 1195 | clean 1196 | 1197 | 1198 | 1 1199 | 清理 1200 | 1201 | ProjectExplorer.BuildSteps.Clean 1202 | 1203 | 2 1204 | false 1205 | 1206 | Profile 1207 | 1208 | Qt4ProjectManager.Qt4BuildConfiguration 1209 | 0 1210 | true 1211 | 1212 | 3 1213 | 1214 | 1215 | 0 1216 | 部署 1217 | 1218 | ProjectExplorer.BuildSteps.Deploy 1219 | 1220 | 1 1221 | 在本地部署 1222 | 1223 | ProjectExplorer.DefaultDeployConfiguration 1224 | 1225 | 1 1226 | 1227 | 1228 | false 1229 | false 1230 | 1000 1231 | 1232 | true 1233 | 1234 | false 1235 | false 1236 | false 1237 | false 1238 | true 1239 | 0.01 1240 | 10 1241 | true 1242 | 1 1243 | 25 1244 | 1245 | 1 1246 | true 1247 | false 1248 | true 1249 | valgrind 1250 | 1251 | 0 1252 | 1 1253 | 2 1254 | 3 1255 | 4 1256 | 5 1257 | 6 1258 | 7 1259 | 8 1260 | 9 1261 | 10 1262 | 11 1263 | 12 1264 | 13 1265 | 14 1266 | 1267 | -1 1268 | 1269 | 1270 | 1271 | %{buildDir} 1272 | Custom Executable 1273 | 1274 | ProjectExplorer.CustomExecutableRunConfiguration 1275 | 3768 1276 | false 1277 | true 1278 | false 1279 | false 1280 | true 1281 | 1282 | 1 1283 | 1284 | 1285 | 1286 | ProjectExplorer.Project.TargetCount 1287 | 5 1288 | 1289 | 1290 | ProjectExplorer.Project.Updater.FileVersion 1291 | 18 1292 | 1293 | 1294 | Version 1295 | 18 1296 | 1297 | 1298 | -------------------------------------------------------------------------------- /QtClient/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /QtClient/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | MainWindow::MainWindow(QWidget *parent) : 5 | QMainWindow(parent), 6 | ui(new Ui::MainWindow) 7 | { 8 | ui->setupUi(this); 9 | ui->location->setEnabled(true); 10 | 11 | 12 | connect(&m_webSocket, SIGNAL(connected()), this, SLOT(onOpened())); 13 | connect(&m_webSocket, SIGNAL(disconnected()), this, SLOT(onClosed())); 14 | connect(&m_webSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(onMessage(QString))); 15 | } 16 | 17 | MainWindow::~MainWindow() 18 | { 19 | delete ui; 20 | } 21 | 22 | void MainWindow::on_connectButton_clicked() 23 | { 24 | 25 | QUrl url(ui->location->text()); 26 | QUrlQuery query; 27 | query.addQueryItem("uid", "fr"); 28 | url.setQuery(query); 29 | m_webSocket.open(url); 30 | 31 | 32 | } 33 | 34 | void MainWindow::on_disconnectButton_clicked() 35 | { 36 | m_webSocket.close(); 37 | } 38 | 39 | void MainWindow::on_sendButton_clicked() 40 | { 41 | m_webSocket.send(ui->message->text()); 42 | } 43 | 44 | void MainWindow::on_clearButton_clicked() 45 | { 46 | ui->log->clear(); 47 | } 48 | 49 | 50 | 51 | void MainWindow::onOpened() 52 | { 53 | ui->log->append(QString("connect success")); 54 | ui->disconnectButton->setEnabled(true); 55 | ui->sendButton->setEnabled(true); 56 | ui->connectButton->setEnabled(false); 57 | ui->message->setEnabled(true); 58 | ui->location->setEnabled(false); 59 | //m_webSocket.send("uid=1222"); 60 | 61 | 62 | } 63 | 64 | void MainWindow::onClosed() 65 | { 66 | ui->log->append(QString("connect closed")); 67 | ui->connectButton->setEnabled(true); 68 | ui->disconnectButton->setEnabled(false); 69 | ui->sendButton->setEnabled(false); 70 | ui->location->setEnabled(true); 71 | ui->message->setEnabled(false); 72 | } 73 | 74 | void MainWindow::onMessage(const QString &msg) 75 | { 76 | ui->log->append(QString("server notify: %1").arg(msg)); 77 | } 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /QtClient/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include "websocket.h" 7 | namespace Ui { 8 | class MainWindow; 9 | } 10 | 11 | class MainWindow : public QMainWindow 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit MainWindow(QWidget *parent = 0); 17 | ~MainWindow(); 18 | 19 | private slots: 20 | void on_connectButton_clicked(); 21 | void on_disconnectButton_clicked(); 22 | void on_sendButton_clicked(); 23 | void on_clearButton_clicked(); 24 | 25 | 26 | void onOpened(); 27 | void onClosed(); 28 | void onMessage(const QString &msg); 29 | 30 | 31 | private: 32 | Ui::MainWindow *ui; 33 | WebSocket m_webSocket; 34 | }; 35 | 36 | #endif // MAINWINDOW_H 37 | -------------------------------------------------------------------------------- /QtClient/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 615 10 | 332 11 | 12 | 13 | 14 | Qwebsocket 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Location: 24 | 25 | 26 | 27 | 28 | 29 | 30 | ws://192.168.150.138:9501/ 31 | 32 | 33 | 34 | 35 | 36 | 37 | 6 38 | 39 | 40 | 0 41 | 42 | 43 | 44 | 45 | Connect 46 | 47 | 48 | 49 | 50 | 51 | 52 | false 53 | 54 | 55 | Disconnect 56 | 57 | 58 | 59 | 60 | 61 | 62 | Qt::Horizontal 63 | 64 | 65 | 66 | 40 67 | 20 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Message: 78 | 79 | 80 | 81 | 82 | 83 | 84 | false 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | false 94 | 95 | 96 | Send 97 | 98 | 99 | 100 | 101 | 102 | 103 | Qt::Horizontal 104 | 105 | 106 | 107 | 40 108 | 20 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | Qt::Vertical 119 | 120 | 121 | 122 | 20 123 | 40 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | Log: 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 360 144 | 240 145 | 146 | 147 | 148 | true 149 | 150 | 151 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 152 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 153 | p, li { white-space: pre-wrap; } 154 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 155 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Lucida Grande'; font-size:13pt;"><br /></p></body></html> 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Clear log 165 | 166 | 167 | 168 | 169 | 170 | 171 | Qt::Horizontal 172 | 173 | 174 | 175 | 40 176 | 20 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /QtClient/source/dataprocessor.cpp: -------------------------------------------------------------------------------- 1 | #include "dataprocessor.h" 2 | #include "websocketprotocol.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | const quint64 MAX_FRAME_SIZE_IN_BYTES = INT_MAX - 1; 10 | const quint64 MAX_MESSAGE_SIZE_IN_BYTES = INT_MAX - 1; 11 | 12 | class Frame 13 | { 14 | public: 15 | Frame(); 16 | Frame(const Frame &other); 17 | 18 | const Frame &operator =(const Frame &other); 19 | 20 | WebSocketProtocol::CloseCode getCloseCode() const; 21 | QString getCloseReason() const; 22 | bool isFinalFrame() const; 23 | bool isControlFrame() const; 24 | bool isDataFrame() const; 25 | bool isContinuationFrame() const; 26 | bool hasMask() const; 27 | quint32 getMask() const; //returns 0 if no mask 28 | int getRsv1() const; 29 | int getRsv2() const; 30 | int getRsv3() const; 31 | WebSocketProtocol::OpCode getOpCode() const; 32 | QByteArray getPayload() const; 33 | 34 | void clear(); //resets all member variables, and invalidates the object 35 | 36 | bool isValid() const; 37 | 38 | static Frame readFrame(QTcpSocket *pSocket); 39 | 40 | private: 41 | WebSocketProtocol::CloseCode m_closeCode; 42 | QString m_closeReason; 43 | bool m_isFinalFrame; 44 | quint32 m_mask; 45 | int m_rsv1; //reserved field 1 46 | int m_rsv2; //reserved field 2 47 | int m_rsv3; //reserved field 3 48 | WebSocketProtocol::OpCode m_opCode; 49 | 50 | quint8 m_length; //length field as read from the header; this is 1 byte, which when 126 or 127, indicates a large payload 51 | QByteArray m_payload; 52 | 53 | bool m_isValid; 54 | 55 | enum ProcessingState 56 | { 57 | PS_READ_HEADER, 58 | PS_READ_PAYLOAD_LENGTH, 59 | PS_READ_BIG_PAYLOAD_LENGTH, 60 | PS_READ_MASK, 61 | PS_READ_PAYLOAD, 62 | PS_DISPATCH_RESULT, 63 | PS_WAIT_FOR_MORE_DATA 64 | }; 65 | 66 | void setError(WebSocketProtocol::CloseCode code, QString closeReason); 67 | bool checkValidity(); 68 | }; 69 | 70 | Frame::Frame() : 71 | m_closeCode(WebSocketProtocol::CC_NORMAL), 72 | m_closeReason(), 73 | m_isFinalFrame(true), 74 | m_mask(0), 75 | m_rsv1(0), 76 | m_rsv2(0), 77 | m_rsv3(0), 78 | m_opCode(WebSocketProtocol::OC_RESERVED_V), 79 | m_length(0), 80 | m_payload(), 81 | m_isValid(false) 82 | { 83 | } 84 | 85 | Frame::Frame(const Frame &other) : 86 | m_closeCode(other.m_closeCode), 87 | m_closeReason(other.m_closeReason), 88 | m_isFinalFrame(other.m_isFinalFrame), 89 | m_mask(other.m_mask), 90 | m_rsv1(other.m_rsv1), 91 | m_rsv2(other.m_rsv2), 92 | m_rsv3(other.m_rsv3), 93 | m_opCode(other.m_opCode), 94 | m_length(other.m_length), 95 | m_payload(other.m_payload), 96 | m_isValid(other.m_isValid) 97 | { 98 | } 99 | 100 | const Frame &Frame::operator =(const Frame &other) 101 | { 102 | m_closeCode = other.m_closeCode; 103 | m_closeReason = other.m_closeReason; 104 | m_isFinalFrame = other.m_isFinalFrame; 105 | m_mask = other.m_mask; 106 | m_rsv1 = other.m_rsv1; 107 | m_rsv2 = other.m_rsv2; 108 | m_rsv3 = other.m_rsv2; 109 | m_opCode = other.m_opCode; 110 | m_length = other.m_length; 111 | m_payload = other.m_payload; 112 | m_isValid = other.m_isValid; 113 | 114 | return *this; 115 | } 116 | 117 | WebSocketProtocol::CloseCode Frame::getCloseCode() const 118 | { 119 | return m_closeCode; 120 | } 121 | 122 | QString Frame::getCloseReason() const 123 | { 124 | return m_closeReason; 125 | } 126 | 127 | bool Frame::isFinalFrame() const 128 | { 129 | return m_isFinalFrame; 130 | } 131 | 132 | bool Frame::isControlFrame() const 133 | { 134 | return (m_opCode & 0x08) == 0x08; 135 | } 136 | 137 | bool Frame::isDataFrame() const 138 | { 139 | return !isControlFrame(); 140 | } 141 | 142 | bool Frame::isContinuationFrame() const 143 | { 144 | return isDataFrame() && (m_opCode == WebSocketProtocol::OC_CONTINUE); 145 | } 146 | 147 | bool Frame::hasMask() const 148 | { 149 | return m_mask != 0; 150 | } 151 | 152 | quint32 Frame::getMask() const 153 | { 154 | return m_mask; 155 | } 156 | 157 | int Frame::getRsv1() const 158 | { 159 | return m_rsv1; 160 | } 161 | 162 | int Frame::getRsv2() const 163 | { 164 | return m_rsv2; 165 | } 166 | 167 | int Frame::getRsv3() const 168 | { 169 | return m_rsv3; 170 | } 171 | 172 | WebSocketProtocol::OpCode Frame::getOpCode() const 173 | { 174 | return m_opCode; 175 | } 176 | 177 | QByteArray Frame::getPayload() const 178 | { 179 | return m_payload; 180 | } 181 | 182 | void Frame::clear() 183 | { 184 | m_closeCode = WebSocketProtocol::CC_NORMAL; 185 | m_closeReason.clear(); 186 | m_isFinalFrame = true; 187 | m_mask = 0; 188 | m_rsv1 = 0; 189 | m_rsv2 =0; 190 | m_rsv3 = 0; 191 | m_opCode = WebSocketProtocol::OC_RESERVED_V; 192 | m_length = 0; 193 | m_payload.clear(); 194 | m_isValid = false; 195 | } 196 | 197 | bool Frame::isValid() const 198 | { 199 | return m_isValid; 200 | } 201 | 202 | #define WAIT_FOR_MORE_DATA(dataSizeInBytes) { returnState = processingState; processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; } 203 | 204 | Frame Frame::readFrame(QTcpSocket *pSocket) 205 | { 206 | bool isDone = false; 207 | qint64 bytesRead = 0; 208 | Frame frame; 209 | quint64 dataWaitSize = 0; 210 | ProcessingState processingState = PS_READ_HEADER; 211 | ProcessingState returnState = PS_READ_HEADER; 212 | bool hasMask = false; 213 | quint64 payloadLength = 0; 214 | 215 | while (!isDone) 216 | { 217 | switch (processingState) 218 | { 219 | case PS_WAIT_FOR_MORE_DATA: 220 | { 221 | bool ok = pSocket->waitForReadyRead(5000); 222 | if (!ok) 223 | { 224 | frame.setError(WebSocketProtocol::CC_GOING_AWAY, "Timeout when reading data from socket."); 225 | isDone = true; 226 | } 227 | else 228 | { 229 | processingState = returnState; 230 | } 231 | break; 232 | } 233 | case PS_READ_HEADER: 234 | { 235 | if (pSocket->bytesAvailable() >= 2) 236 | { 237 | //FIN, RSV1-3, Opcode 238 | char header[2] = {0}; 239 | bytesRead = pSocket->read(header, 2); 240 | frame.m_isFinalFrame = (header[0] & 0x80) != 0; 241 | frame.m_rsv1 = (header[0] & 0x40); 242 | frame.m_rsv2 = (header[0] & 0x20); 243 | frame.m_rsv3 = (header[0] & 0x10); 244 | frame.m_opCode = static_cast(header[0] & 0x0F); 245 | 246 | //Mask, PayloadLength 247 | hasMask = (header[1] & 0x80) != 0; 248 | frame.m_length = (header[1] & 0x7F); 249 | 250 | switch (frame.m_length) 251 | { 252 | case 126: 253 | { 254 | processingState = PS_READ_PAYLOAD_LENGTH; 255 | break; 256 | } 257 | case 127: 258 | { 259 | processingState = PS_READ_BIG_PAYLOAD_LENGTH; 260 | break; 261 | } 262 | default: 263 | { 264 | payloadLength = frame.m_length; 265 | processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; 266 | break; 267 | } 268 | } 269 | if (!frame.checkValidity()) 270 | { 271 | isDone = true; 272 | } 273 | } 274 | else 275 | { 276 | WAIT_FOR_MORE_DATA(2); 277 | } 278 | break; 279 | } 280 | 281 | case PS_READ_PAYLOAD_LENGTH: 282 | { 283 | if (pSocket->bytesAvailable() >= 2) 284 | { 285 | uchar length[2] = {0}; 286 | //TODO: Handle return value 287 | bytesRead = pSocket->read(reinterpret_cast(length), 2); 288 | payloadLength = qFromBigEndian(reinterpret_cast(length)); 289 | processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; 290 | } 291 | else 292 | { 293 | WAIT_FOR_MORE_DATA(2); 294 | } 295 | break; 296 | } 297 | 298 | case PS_READ_BIG_PAYLOAD_LENGTH: 299 | { 300 | if (pSocket->bytesAvailable() >= 8) 301 | { 302 | uchar length[8] = {0}; 303 | //TODO: Handle return value 304 | bytesRead = pSocket->read(reinterpret_cast(length), 8); 305 | //Most significant bit must be set to 0 as per http://tools.ietf.org/html/rfc6455#section-5.2 306 | //TODO: Do we check for that? 307 | payloadLength = qFromBigEndian(length) & ~(1ULL << 63); 308 | processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD; 309 | } 310 | else 311 | { 312 | WAIT_FOR_MORE_DATA(8); 313 | } 314 | 315 | break; 316 | } 317 | 318 | case PS_READ_MASK: 319 | { 320 | if (pSocket->bytesAvailable() >= 4) 321 | { 322 | //TODO: Handle return value 323 | bytesRead = pSocket->read(reinterpret_cast(&frame.m_mask), sizeof(frame.m_mask)); 324 | processingState = PS_READ_PAYLOAD; 325 | } 326 | else 327 | { 328 | WAIT_FOR_MORE_DATA(4); 329 | } 330 | break; 331 | } 332 | 333 | case PS_READ_PAYLOAD: 334 | { 335 | if (!payloadLength) 336 | { 337 | processingState = PS_DISPATCH_RESULT; 338 | } 339 | else if (payloadLength > MAX_FRAME_SIZE_IN_BYTES) 340 | { 341 | frame.setError(WebSocketProtocol::CC_TOO_MUCH_DATA, "Maximum framesize exceeded."); 342 | processingState = PS_DISPATCH_RESULT; 343 | } 344 | else 345 | { 346 | quint64 bytesAvailable = static_cast(pSocket->bytesAvailable()); 347 | if (bytesAvailable >= payloadLength) 348 | { 349 | frame.m_payload = pSocket->read(payloadLength); 350 | if (hasMask) 351 | { 352 | WebSocketProtocol::mask(&frame.m_payload, frame.m_mask); 353 | } 354 | processingState = PS_DISPATCH_RESULT; 355 | } 356 | else 357 | { 358 | WAIT_FOR_MORE_DATA(payloadLength); 359 | } 360 | } 361 | break; 362 | } 363 | 364 | case PS_DISPATCH_RESULT: 365 | { 366 | processingState = PS_READ_HEADER; 367 | isDone = true; 368 | break; 369 | } 370 | 371 | default: 372 | { 373 | //should not come here 374 | qDebug() << "DataProcessor::process: Found invalid state. This should not happen!"; 375 | frame.clear(); 376 | isDone = true; 377 | break; 378 | } 379 | } //end switch 380 | } 381 | 382 | return frame; 383 | } 384 | 385 | void Frame::setError(WebSocketProtocol::CloseCode code, QString closeReason) 386 | { 387 | clear(); 388 | m_closeCode = code; 389 | m_closeReason = closeReason; 390 | m_isValid = false; 391 | } 392 | 393 | bool Frame::checkValidity() 394 | { 395 | if (!isValid()) 396 | { 397 | if (m_rsv1 || m_rsv2 || m_rsv3) 398 | { 399 | setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Rsv field is non-zero"); 400 | } 401 | else if (WebSocketProtocol::isOpCodeReserved(m_opCode)) 402 | { 403 | setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Used reserved opcode"); 404 | } 405 | else if (isControlFrame()) 406 | { 407 | if (m_length > 125) 408 | { 409 | setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Controle frame is larger than 125 bytes"); 410 | } 411 | else if (!m_isFinalFrame) 412 | { 413 | setError(WebSocketProtocol::CC_PROTOCOL_ERROR, "Controle frames cannot be fragmented"); 414 | } 415 | else 416 | { 417 | m_isValid = true; 418 | } 419 | } 420 | else 421 | { 422 | m_isValid = true; 423 | } 424 | } 425 | return m_isValid; 426 | } 427 | 428 | DataProcessor::DataProcessor(QObject *parent) : 429 | QObject(parent), 430 | m_processingState(PS_READ_HEADER), 431 | m_isFinalFrame(false), 432 | m_isFragmented(false), 433 | m_opCode(WebSocketProtocol::OC_CLOSE), 434 | m_isControlFrame(false), 435 | m_hasMask(false), 436 | m_mask(0), 437 | m_binaryMessage(), 438 | m_textMessage(), 439 | m_payloadLength(0), 440 | m_pConverterState(0), 441 | m_pTextCodec(QTextCodec::codecForName("UTF-8")) 442 | { 443 | clear(); 444 | } 445 | 446 | DataProcessor::~DataProcessor() 447 | { 448 | clear(); 449 | if (m_pConverterState) 450 | { 451 | delete m_pConverterState; 452 | m_pConverterState = 0; 453 | } 454 | } 455 | 456 | void DataProcessor::process(QTcpSocket *pSocket) 457 | { 458 | bool isDone = false; 459 | 460 | while (!isDone) 461 | { 462 | Frame frame = Frame::readFrame(pSocket); 463 | if (frame.isValid()) 464 | { 465 | if (frame.isControlFrame()) 466 | { 467 | Q_EMIT controlFrameReceived(frame.getOpCode(), frame.getPayload()); 468 | isDone = true; //exit the loop after a control frame, so we can get a chance to close the socket if necessary 469 | } 470 | else //we have a dataframe; opcode can be OC_CONTINUE, OC_TEXT or OC_BINARY 471 | { 472 | if (!m_isFragmented && frame.isContinuationFrame()) 473 | { 474 | clear(); 475 | Q_EMIT errorEncountered(WebSocketProtocol::CC_PROTOCOL_ERROR, "Received Continuation frame /*with FIN=true*/, while there is nothing to continue."); 476 | return; 477 | } 478 | if (m_isFragmented && frame.isDataFrame() && !frame.isContinuationFrame()) 479 | { 480 | clear(); 481 | Q_EMIT errorEncountered(WebSocketProtocol::CC_PROTOCOL_ERROR, "All data frames after the initial data frame must have opcode 0 (continuation)."); 482 | return; 483 | } 484 | if (!frame.isContinuationFrame()) 485 | { 486 | m_opCode = frame.getOpCode(); 487 | m_isFragmented = !frame.isFinalFrame(); 488 | } 489 | quint64 messageLength = (quint64)(m_opCode == WebSocketProtocol::OC_TEXT) ? m_textMessage.length() : m_binaryMessage.length(); 490 | if ((messageLength + quint64(frame.getPayload().length())) > MAX_MESSAGE_SIZE_IN_BYTES) 491 | { 492 | clear(); 493 | Q_EMIT errorEncountered(WebSocketProtocol::CC_TOO_MUCH_DATA, "Received message is too big."); 494 | return; 495 | } 496 | 497 | if (m_opCode == WebSocketProtocol::OC_TEXT) 498 | { 499 | QString frameTxt = m_pTextCodec->toUnicode(frame.getPayload().constData(), frame.getPayload().size(), m_pConverterState); 500 | bool failed = (m_pConverterState->invalidChars != 0) || (frame.isFinalFrame() && (m_pConverterState->remainingChars != 0)); 501 | if (failed) 502 | { 503 | clear(); 504 | Q_EMIT errorEncountered(WebSocketProtocol::CC_WRONG_DATATYPE, "Invalid UTF-8 code encountered."); 505 | return; 506 | } 507 | else 508 | { 509 | m_textMessage.append(frameTxt); 510 | Q_EMIT textFrameReceived(frameTxt, frame.isFinalFrame()); 511 | } 512 | } 513 | else 514 | { 515 | m_binaryMessage.append(frame.getPayload()); 516 | Q_EMIT binaryFrameReceived(frame.getPayload(), frame.isFinalFrame()); 517 | } 518 | 519 | if (frame.isFinalFrame()) 520 | { 521 | if (m_opCode == WebSocketProtocol::OC_TEXT) 522 | { 523 | Q_EMIT textMessageReceived(m_textMessage); 524 | } 525 | else 526 | { 527 | Q_EMIT binaryMessageReceived(m_binaryMessage); 528 | } 529 | clear(); 530 | isDone = true; 531 | } 532 | } 533 | } 534 | else 535 | { 536 | Q_EMIT errorEncountered(frame.getCloseCode(), frame.getCloseReason()); 537 | clear(); 538 | isDone = true; 539 | } 540 | } 541 | } 542 | 543 | void DataProcessor::clear() 544 | { 545 | m_processingState = PS_READ_HEADER; 546 | m_isFinalFrame = false; 547 | m_isFragmented = false; 548 | m_opCode = WebSocketProtocol::OC_CLOSE; 549 | m_hasMask = false; 550 | m_mask = 0; 551 | m_binaryMessage.clear(); 552 | m_textMessage.clear(); 553 | m_payloadLength = 0; 554 | if (m_pConverterState) 555 | { 556 | if ((m_pConverterState->remainingChars != 0) || (m_pConverterState->invalidChars != 0)) 557 | { 558 | delete m_pConverterState; 559 | m_pConverterState = 0; 560 | } 561 | } 562 | if (!m_pConverterState) 563 | { 564 | m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull | QTextCodec::IgnoreHeader); 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /QtClient/source/dataprocessor.h: -------------------------------------------------------------------------------- 1 | #ifndef DATAPROCESSOR_H 2 | #define DATAPROCESSOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "websocketprotocol.h" 9 | 10 | class QTcpSocket; 11 | 12 | /** 13 | * @internal 14 | * @brief The DataProcessor class 15 | */ 16 | class DataProcessor: public QObject 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit DataProcessor(QObject *parent = 0); 21 | virtual ~DataProcessor(); 22 | 23 | Q_SIGNALS: 24 | void controlFrameReceived(WebSocketProtocol::OpCode opCode, QByteArray frame); 25 | void textFrameReceived(QString frame, bool lastFrame); 26 | void binaryFrameReceived(QByteArray frame, bool lastFrame); 27 | void textMessageReceived(QString message); 28 | void binaryMessageReceived(QByteArray message); 29 | void errorEncountered(WebSocketProtocol::CloseCode code, QString description); 30 | 31 | public Q_SLOTS: 32 | void process(QTcpSocket *pSocket); 33 | void clear(); 34 | 35 | private: 36 | Q_DISABLE_COPY(DataProcessor) 37 | enum 38 | { 39 | PS_READ_HEADER, 40 | PS_READ_PAYLOAD_LENGTH, 41 | PS_READ_BIG_PAYLOAD_LENGTH, 42 | PS_READ_MASK, 43 | PS_READ_PAYLOAD, 44 | PS_DISPATCH_RESULT 45 | } m_processingState; 46 | 47 | bool m_isFinalFrame; 48 | bool m_isFragmented; 49 | WebSocketProtocol::OpCode m_opCode; 50 | bool m_isControlFrame; 51 | bool m_hasMask; 52 | quint32 m_mask; 53 | QByteArray m_binaryMessage; 54 | QString m_textMessage; 55 | quint64 m_payloadLength; 56 | QTextCodec::ConverterState *m_pConverterState; 57 | QTextCodec *m_pTextCodec; 58 | }; 59 | 60 | #endif // DATAPROCESSOR_H 61 | -------------------------------------------------------------------------------- /QtClient/source/handshakerequest.cpp: -------------------------------------------------------------------------------- 1 | #include "handshakerequest.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "websocketprotocol.h" 9 | 10 | HandshakeRequest::HandshakeRequest(int port, bool isSecure) : 11 | m_port(port), 12 | m_isSecure(isSecure), 13 | m_isValid(false), 14 | m_headers(), 15 | m_versions(), 16 | m_key(), 17 | m_origin(), 18 | m_protocols(), 19 | m_extensions(), 20 | m_requestUrl() 21 | { 22 | } 23 | 24 | HandshakeRequest::~HandshakeRequest() 25 | { 26 | } 27 | 28 | void HandshakeRequest::clear() 29 | { 30 | m_port = -1; 31 | m_isSecure = false; 32 | m_isValid = false; 33 | m_headers.clear(); 34 | m_versions.clear(); 35 | m_key.clear(); 36 | m_origin.clear(); 37 | m_protocols.clear(); 38 | m_extensions.clear(); 39 | m_requestUrl.clear(); 40 | } 41 | 42 | int HandshakeRequest::getPort() const 43 | { 44 | return m_requestUrl.port(m_port); 45 | } 46 | 47 | bool HandshakeRequest::isSecure() const 48 | { 49 | return m_isSecure; 50 | } 51 | 52 | bool HandshakeRequest::isValid() const 53 | { 54 | return m_isValid; 55 | } 56 | 57 | QMap HandshakeRequest::getHeaders() const 58 | { 59 | return m_headers; 60 | } 61 | 62 | QList HandshakeRequest::getVersions() const 63 | { 64 | return m_versions; 65 | } 66 | 67 | QString HandshakeRequest::getResourceName() const 68 | { 69 | return m_requestUrl.path(); 70 | } 71 | 72 | QString HandshakeRequest::getKey() const 73 | { 74 | return m_key; 75 | } 76 | 77 | QString HandshakeRequest::getHost() const 78 | { 79 | return m_requestUrl.host(); 80 | } 81 | 82 | QString HandshakeRequest::getOrigin() const 83 | { 84 | return m_origin; 85 | } 86 | 87 | QList HandshakeRequest::getProtocols() const 88 | { 89 | return m_protocols; 90 | } 91 | 92 | QList HandshakeRequest::getExtensions() const 93 | { 94 | return m_extensions; 95 | } 96 | 97 | QUrl HandshakeRequest::getRequestUrl() const 98 | { 99 | return m_requestUrl; 100 | } 101 | 102 | QTextStream &HandshakeRequest::readFromStream(QTextStream &textStream) 103 | { 104 | m_isValid = false; 105 | clear(); 106 | if (textStream.status() == QTextStream::Ok) 107 | { 108 | QString requestLine = textStream.readLine(); 109 | QStringList tokens = requestLine.split(' ', QString::SkipEmptyParts); 110 | QString verb = tokens[0]; 111 | QString resourceName = tokens[1]; 112 | QString httpProtocol = tokens[2]; 113 | 114 | QString headerLine = textStream.readLine(); 115 | m_headers.clear(); 116 | while (!headerLine.isEmpty()) 117 | { 118 | QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); 119 | m_headers.insertMulti(headerField[0], headerField[1]); 120 | headerLine = textStream.readLine(); 121 | } 122 | 123 | QString host = m_headers.value("Host", ""); 124 | m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1()); 125 | if (m_requestUrl.isRelative()) 126 | { 127 | m_requestUrl.setHost(host); 128 | } 129 | if (m_requestUrl.scheme().isEmpty()) 130 | { 131 | QString scheme = isSecure() ? "wss://" : "ws://"; 132 | m_requestUrl.setScheme(scheme); 133 | } 134 | 135 | QStringList versionLines = m_headers.values("Sec-WebSocket-Version"); 136 | Q_FOREACH(QString versionLine, versionLines) 137 | { 138 | QStringList versions = versionLine.split(",", QString::SkipEmptyParts); 139 | Q_FOREACH(QString version, versions) 140 | { 141 | WebSocketProtocol::Version ver = WebSocketProtocol::versionFromString(version.trimmed()); 142 | m_versions << ver; 143 | } 144 | } 145 | qStableSort(m_versions.begin(), m_versions.end(), qGreater()); //sort in descending order 146 | m_key = m_headers.value("Sec-WebSocket-Key", ""); 147 | QString upgrade = m_headers.value("Upgrade", ""); //must be equal to "websocket", case-insensitive 148 | QString connection = m_headers.value("Connection", ""); //must contain "Upgrade", case-insensitive 149 | QStringList connectionLine = connection.split(",", QString::SkipEmptyParts); 150 | QStringList connectionValues; 151 | Q_FOREACH(QString connection, connectionLine) 152 | { 153 | connectionValues << connection.trimmed(); 154 | } 155 | 156 | //optional headers 157 | m_origin = m_headers.value("Sec-WebSocket-Origin", ""); 158 | QStringList protocolLines = m_headers.values("Sec-WebSocket-Protocol"); 159 | Q_FOREACH(QString protocolLine, protocolLines) 160 | { 161 | QStringList protocols = protocolLine.split(",", QString::SkipEmptyParts); 162 | Q_FOREACH(QString protocol, protocols) 163 | { 164 | m_protocols << protocol.trimmed(); 165 | } 166 | } 167 | QStringList extensionLines = m_headers.values("Sec-WebSocket-Extensions"); 168 | Q_FOREACH(QString extensionLine, extensionLines) 169 | { 170 | QStringList extensions = extensionLine.split(",", QString::SkipEmptyParts); 171 | Q_FOREACH(QString extension, extensions) 172 | { 173 | m_extensions << extension.trimmed(); 174 | } 175 | } 176 | //TODO: authentication field 177 | 178 | m_isValid = !(host.isEmpty() || 179 | resourceName.isEmpty() || 180 | m_versions.isEmpty() || 181 | m_key.isEmpty() || 182 | (verb != "GET") || 183 | (httpProtocol != "HTTP/1.1") || 184 | (upgrade.toLower() != "websocket") || 185 | (!connectionValues.contains("upgrade", Qt::CaseInsensitive))); 186 | //(connection.toLower() != "upgrade")); 187 | } 188 | return textStream; 189 | } 190 | 191 | QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request) 192 | { 193 | return request.readFromStream(stream); 194 | } 195 | -------------------------------------------------------------------------------- /QtClient/source/handshakerequest.h: -------------------------------------------------------------------------------- 1 | #ifndef HANDSHAKEREQUEST_H 2 | #define HANDSHAKEREQUEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "websocketprotocol.h" 11 | 12 | class QTextStream; 13 | 14 | class HandshakeRequest 15 | { 16 | public: 17 | HandshakeRequest(int port, bool isSecure); 18 | virtual ~HandshakeRequest(); 19 | 20 | void clear(); 21 | 22 | int getPort() const; 23 | bool isSecure() const; 24 | bool isValid() const; 25 | QMap getHeaders() const; 26 | QList getVersions() const; 27 | QString getKey() const; 28 | QString getOrigin() const; 29 | QList getProtocols() const; 30 | QList getExtensions() const; 31 | QUrl getRequestUrl() const; 32 | QString getResourceName() const; 33 | QString getHost() const; 34 | 35 | private: 36 | Q_DISABLE_COPY(HandshakeRequest) 37 | QTextStream &readFromStream(QTextStream &textStream); 38 | friend QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request); 39 | 40 | int m_port; 41 | bool m_isSecure; 42 | bool m_isValid; 43 | QMap m_headers; 44 | QList m_versions; 45 | QString m_key; 46 | QString m_origin; 47 | QList m_protocols; 48 | QList m_extensions; 49 | QUrl m_requestUrl; 50 | }; 51 | 52 | QTextStream &operator >>(QTextStream &stream, HandshakeRequest &request); 53 | 54 | #endif // HANDSHAKEREQUEST_H 55 | -------------------------------------------------------------------------------- /QtClient/source/handshakeresponse.cpp: -------------------------------------------------------------------------------- 1 | #include "handshakeresponse.h" 2 | #include "handshakerequest.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | HandshakeResponse::HandshakeResponse(const HandshakeRequest &request, 13 | const QList &supportedVersions, 14 | const QList &supportedProtocols, 15 | const QList &supportedExtensions) : 16 | m_isValid(false), 17 | m_canUpgrade(false), 18 | m_response(), 19 | m_acceptedProtocol(), 20 | m_acceptedExtension(), 21 | m_acceptedVersion(WebSocketProtocol::V_Unknow) 22 | { 23 | m_response = getHandshakeResponse(request, supportedVersions, supportedProtocols, supportedExtensions); 24 | m_isValid = true; 25 | } 26 | 27 | HandshakeResponse::~HandshakeResponse() 28 | { 29 | } 30 | 31 | bool HandshakeResponse::isValid() const 32 | { 33 | return m_isValid; 34 | } 35 | 36 | bool HandshakeResponse::canUpgrade() const 37 | { 38 | return m_isValid && m_canUpgrade; 39 | } 40 | 41 | QString HandshakeResponse::getAcceptedProtocol() const 42 | { 43 | return m_acceptedProtocol; 44 | } 45 | 46 | QString HandshakeResponse::calculateAcceptKey(const QString &key) const 47 | { 48 | QString tmpKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 49 | QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); 50 | return QString(hash.toBase64()); 51 | } 52 | 53 | QString HandshakeResponse::getHandshakeResponse(const HandshakeRequest &request, 54 | const QList &supportedVersions, 55 | const QList &supportedProtocols, 56 | const QList &supportedExtensions) 57 | { 58 | QStringList response; 59 | m_canUpgrade = false; 60 | if (request.isValid()) 61 | { 62 | QString acceptKey = calculateAcceptKey(request.getKey()); 63 | QList matchingProtocols = supportedProtocols.toSet().intersect(request.getProtocols().toSet()).toList(); 64 | QList matchingExtensions = supportedExtensions.toSet().intersect(request.getExtensions().toSet()).toList(); 65 | QList matchingVersions = request.getVersions().toSet().intersect(supportedVersions.toSet()).toList(); 66 | qStableSort(matchingVersions.begin(), matchingVersions.end(), qGreater()); //sort in descending order 67 | 68 | if (matchingVersions.isEmpty()) 69 | { 70 | m_canUpgrade = false; 71 | } 72 | else 73 | { 74 | response << "HTTP/1.1 101 Switching Protocols" << 75 | "Upgrade: websocket" << 76 | "Connection: Upgrade" << 77 | "Sec-WebSocket-Accept: " + acceptKey; 78 | if (!matchingProtocols.isEmpty()) 79 | { 80 | m_acceptedProtocol = matchingProtocols.first(); 81 | response << "Sec-WebSocket-Protocol: " + m_acceptedProtocol; 82 | } 83 | if (!matchingExtensions.isEmpty()) 84 | { 85 | m_acceptedExtension = matchingExtensions.first(); 86 | response << "Sec-WebSocket-Extensions: " + m_acceptedExtension; 87 | } 88 | QString origin = request.getOrigin().trimmed(); 89 | if (origin.isEmpty()) 90 | { 91 | origin = "*"; 92 | } 93 | response << "Server: Imagine Delivery Server" << 94 | "Access-Control-Allow-Credentials: true" << 95 | "Access-Control-Allow-Headers: content-type" << 96 | "Access-Control-Allow-Origin: " + origin << 97 | "Date: " + QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'"); 98 | 99 | m_acceptedVersion = WebSocketProtocol::V_13; 100 | m_canUpgrade = true; 101 | } 102 | } 103 | else 104 | { 105 | m_canUpgrade = false; 106 | } 107 | if (!m_canUpgrade) 108 | { 109 | response << "HTTP/1.1 400 Bad Request"; 110 | QStringList versions; 111 | Q_FOREACH(WebSocketProtocol::Version version, supportedVersions) 112 | { 113 | versions << QString::number(static_cast(version)); 114 | } 115 | response << "Sec-WebSocket-Version: " + versions.join(", "); 116 | } 117 | response << "\r\n"; //append empty line at end of header 118 | return response.join("\r\n"); 119 | } 120 | 121 | QTextStream &HandshakeResponse::writeToStream(QTextStream &textStream) const 122 | { 123 | if (!m_response.isEmpty()) 124 | { 125 | textStream << m_response.toLatin1().constData(); 126 | } 127 | else 128 | { 129 | textStream.setStatus(QTextStream::WriteFailed); 130 | } 131 | return textStream; 132 | } 133 | 134 | QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response) 135 | { 136 | return response.writeToStream(stream); 137 | } 138 | 139 | WebSocketProtocol::Version HandshakeResponse::getAcceptedVersion() const 140 | { 141 | return m_acceptedVersion; 142 | } 143 | 144 | QString HandshakeResponse::getAcceptedExtension() const 145 | { 146 | return m_acceptedExtension; 147 | } 148 | -------------------------------------------------------------------------------- /QtClient/source/handshakeresponse.h: -------------------------------------------------------------------------------- 1 | #ifndef HANDSHAKERESPONSE_H 2 | #define HANDSHAKERESPONSE_H 3 | 4 | #include 5 | #include "websocketprotocol.h" 6 | 7 | class HandshakeRequest; 8 | class QString; 9 | class QTextStream; 10 | 11 | class HandshakeResponse:public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | HandshakeResponse(const HandshakeRequest &request, 16 | const QList &supportedVersions, 17 | const QList &supportedProtocols, 18 | const QList &supportedExtensions); 19 | 20 | virtual ~HandshakeResponse(); 21 | 22 | bool isValid() const; 23 | bool canUpgrade() const; 24 | QString getAcceptedProtocol() const; 25 | QString getAcceptedExtension() const; 26 | WebSocketProtocol::Version getAcceptedVersion() const; 27 | 28 | public Q_SLOTS: 29 | 30 | Q_SIGNALS: 31 | 32 | private: 33 | Q_DISABLE_COPY(HandshakeResponse) 34 | bool m_isValid; 35 | bool m_canUpgrade; 36 | QString m_response; 37 | QString m_acceptedProtocol; 38 | QString m_acceptedExtension; 39 | WebSocketProtocol::Version m_acceptedVersion; 40 | 41 | QString calculateAcceptKey(const QString &key) const; 42 | QString getHandshakeResponse(const HandshakeRequest &request, 43 | const QList &supportedVersions, 44 | const QList &supportedProtocols, 45 | const QList &supportedExtensions); 46 | 47 | QTextStream &writeToStream(QTextStream &textStream) const; 48 | friend QTextStream &operator <<(QTextStream &stream, const HandshakeResponse &response); 49 | }; 50 | 51 | #endif // HANDSHAKERESPONSE_H 52 | -------------------------------------------------------------------------------- /QtClient/source/websocket.cpp: -------------------------------------------------------------------------------- 1 | #include "websocket.h" 2 | #include "handshakerequest.h" 3 | #include "handshakeresponse.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | 18 | /*! 19 | \class WebSocket 20 | \brief The class WebSocket implements a TCP socket that talks the websocket protocol. 21 | 22 | WebSockets is a web technology providing full-duplex communications channels over a single TCP connection. 23 | The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011 (see http://tools.ietf.org/html/rfc6455). 24 | It can both be used in a client application and server application. 25 | 26 | This class was modeled after QAbstractSocket. 27 | 28 | \ref echoclient 29 | 30 | \author Kurt Pattyn (pattyn.kurt@gmail.com) 31 | */ 32 | /*! 33 | \page echoclient WebSocket client example 34 | \brief A sample websocket client that sends a message and displays the message that it receives back. 35 | 36 | \section Description 37 | The EchoClient example implements a web socket client that sends a message to a websocket server and dumps the answer that it gets back. 38 | This example should ideally be used with the EchoServer example. 39 | \section Code 40 | We start by connecting to the `connected()` signal. 41 | \snippet echoclient.cpp constructor 42 | After the connection, we open the socket to the given \a url. 43 | 44 | \snippet echoclient.cpp onConnected 45 | When the client is connected successfully, we connect to the `onTextMessageReceived()` signal, and send out "Hello, world!". 46 | If connected with the EchoServer, we will receive the same message back. 47 | 48 | \snippet echoclient.cpp onTextMessageReceived 49 | Whenever a message is received, we write it out. 50 | */ 51 | 52 | /*! 53 | \fn void WebSocket::connected() 54 | \brief Emitted when a connection is successfully established. 55 | \sa open(), disconnected() 56 | */ 57 | /*! 58 | \fn void WebSocket::disconnected() 59 | \brief Emitted when the socket is disconnected. 60 | \sa close(), connected() 61 | */ 62 | /*! 63 | \fn void WebSocket::aboutToClose() 64 | 65 | This signal is emitted when the socket is about to close. 66 | Connect this signal if you have operations that need to be performed before the socket closes 67 | (e.g., if you have data in a separate buffer that needs to be written to the device). 68 | 69 | \sa close() 70 | */ 71 | /*! 72 | \fn void WebSocket::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) 73 | 74 | This signal can be emitted when a \a proxy that requires 75 | authentication is used. The \a authenticator object can then be 76 | filled in with the required details to allow authentication and 77 | continue the connection. 78 | 79 | \note It is not possible to use a QueuedConnection to connect to 80 | this signal, as the connection will fail if the authenticator has 81 | not been filled in with new information when the signal returns. 82 | 83 | \sa QAuthenticator, QNetworkProxy 84 | */ 85 | /*! 86 | \fn void WebSocket::stateChanged(QAbstractSocket::SocketState state); 87 | 88 | This signal is emitted whenever WebSocket's state changes. 89 | The \a socketState parameter is the new state. 90 | 91 | QAbstractSocket::SocketState is not a registered metatype, so for queued 92 | connections, you will have to register it with Q_REGISTER_METATYPE() and 93 | qRegisterMetaType(). 94 | 95 | \sa state() 96 | */ 97 | /*! 98 | \fn void WebSocket::readChannelFinished() 99 | 100 | This signal is emitted when the input (reading) stream is closed in this device. It is emitted as soon as the closing is detected. 101 | 102 | \sa close() 103 | */ 104 | 105 | /*! 106 | \fn void WebSocket::textFrameReceived(QString frame, bool isLastFrame); 107 | 108 | This signal is emitted whenever a text frame is received. The \a frame contains the data and 109 | \a isLastFrame indicates whether this is the last frame of the complete message. 110 | 111 | This signal can be used to process large messages frame by frame, instead of waiting for the complete 112 | message to arrive. 113 | 114 | \sa binaryFrameReceived(QByteArray, bool), textMessageReceived(QString) 115 | */ 116 | /*! 117 | \fn void WebSocket::binaryFrameReceived(QByteArray frame, bool isLastFrame); 118 | 119 | This signal is emitted whenever a binary frame is received. The \a frame contains the data and 120 | \a isLastFrame indicates whether this is the last frame of the complete message. 121 | 122 | This signal can be used to process large messages frame by frame, instead of waiting for the complete 123 | message to arrive. 124 | 125 | \sa textFrameReceived(QString, bool), binaryMessageReceived(QByteArray) 126 | */ 127 | /*! 128 | \fn void WebSocket::textMessageReceived(QString message); 129 | 130 | This signal is emitted whenever a text message is received. The \a message contains the received text. 131 | 132 | \sa textFrameReceived(QString, bool), binaryMessageReceived(QByteArray) 133 | */ 134 | /*! 135 | \fn void WebSocket::binaryMessageReceived(QByteArray message); 136 | 137 | This signal is emitted whenever a binary message is received. The \a message contains the received bytes. 138 | 139 | \sa binaryFrameReceived(QByteArray, bool), textMessageReceived(QString) 140 | */ 141 | /*! 142 | \fn void WebSocket::error(QAbstractSocket::SocketError error); 143 | 144 | This signal is emitted after an error occurred. The \a socketError 145 | parameter describes the type of error that occurred. 146 | 147 | QAbstractSocket::SocketError is not a registered metatype, so for queued 148 | connections, you will have to register it with Q_DECLARE_METATYPE() and 149 | qRegisterMetaType(). 150 | 151 | \sa error(), errorString() 152 | */ 153 | /*! 154 | \fn void WebSocket::pong(quint64 elapsedTime) 155 | 156 | Emitted when a pong message is received in reply to a previous ping. 157 | 158 | \sa ping() 159 | */ 160 | 161 | const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message 162 | 163 | /*! 164 | * \brief Creates a new WebSocket with the given \a origin, the \a version of the protocol to use and \a parent. 165 | * 166 | * The \a origin of the client is as specified in http://tools.ietf.org/html/rfc6454. 167 | * (The \a origin is not required for non-web browser clients (see RFC 6455)). 168 | * \note Currently only V13 (RFC 6455) is supported 169 | */ 170 | WebSocket::WebSocket(QString origin, WebSocketProtocol::Version version, QObject *parent) : 171 | QObject(parent), 172 | m_pSocket(new QTcpSocket(this)), 173 | m_errorString(), 174 | m_version(version), 175 | m_resourceName(), 176 | m_requestUrl(), 177 | m_origin(origin), 178 | m_protocol(""), 179 | m_extension(""), 180 | m_socketState(QAbstractSocket::UnconnectedState), 181 | m_key(), 182 | m_mustMask(true), 183 | m_isClosingHandshakeSent(false), 184 | m_isClosingHandshakeReceived(false), 185 | m_pingTimer(), 186 | m_dataProcessor() 187 | { 188 | makeConnections(m_pSocket); 189 | qsrand(static_cast(QDateTime::currentMSecsSinceEpoch())); 190 | } 191 | 192 | //only called by upgradeFrom 193 | /*! 194 | \internal 195 | Constructor used for the server implementation. Should never be called directly. 196 | 197 | pTcpSocket The tcp socket to use for this websocket 198 | version The version of the protocol to speak (currently only V13 is supported) 199 | parent The parent object of the WebSocket object 200 | */ 201 | WebSocket::WebSocket(QTcpSocket *pTcpSocket, WebSocketProtocol::Version version, QObject *parent) : 202 | QObject(parent), 203 | m_pSocket(pTcpSocket), 204 | m_errorString(pTcpSocket->errorString()), 205 | m_version(version), 206 | m_resourceName(), 207 | m_requestUrl(), 208 | m_origin(), 209 | m_protocol(), 210 | m_extension(), 211 | m_socketState(pTcpSocket->state()), 212 | m_key(), 213 | m_mustMask(true), 214 | m_isClosingHandshakeSent(false), 215 | m_isClosingHandshakeReceived(false), 216 | m_pingTimer(), 217 | m_dataProcessor() 218 | { 219 | makeConnections(m_pSocket); 220 | } 221 | 222 | /*! 223 | * \brief Destroys the WebSocket. Closes the socket if it is still open, and releases any used resources. 224 | */ 225 | WebSocket::~WebSocket() 226 | { 227 | if (state() == QAbstractSocket::ConnectedState) 228 | { 229 | close(WebSocketProtocol::CC_GOING_AWAY, "Connection closed"); 230 | } 231 | releaseConnections(m_pSocket); 232 | m_pSocket->deleteLater(); 233 | m_pSocket = 0; 234 | } 235 | 236 | /*! 237 | * \brief Aborts the current socket and resets the socket. Unlike close(), this function immediately closes the socket, discarding any pending data in the write buffer. 238 | */ 239 | void WebSocket::abort() 240 | { 241 | m_pSocket->abort(); 242 | } 243 | 244 | /*! 245 | * Returns the type of error that last occurred 246 | * \sa errorString() 247 | */ 248 | QAbstractSocket::SocketError WebSocket::error() const 249 | { 250 | return m_pSocket->error(); 251 | } 252 | 253 | /*! 254 | * Returns a human-readable description of the last error that occurred 255 | * 256 | * \sa error() 257 | */ 258 | QString WebSocket::errorString() const 259 | { 260 | if (!m_errorString.isEmpty()) 261 | { 262 | return m_errorString; 263 | } 264 | else 265 | { 266 | return m_pSocket->errorString(); 267 | } 268 | } 269 | 270 | /*! 271 | This function writes as much as possible from the internal write buffer to the underlying network socket, without blocking. 272 | If any data was written, this function returns true; otherwise false is returned. 273 | Call this function if you need WebSocket to start sending buffered data immediately. 274 | The number of bytes successfully written depends on the operating system. 275 | In most cases, you do not need to call this function, because WebSocket will start sending data automatically once control goes back to the event loop. 276 | In the absence of an event loop, call waitForBytesWritten() instead. 277 | 278 | \sa send() and waitForBytesWritten(). 279 | */ 280 | bool WebSocket::flush() 281 | { 282 | return m_pSocket->flush(); 283 | } 284 | 285 | /*! 286 | * Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. 287 | * \param message Text message to be sent. Must be '\0' terminated. 288 | * \return The number of bytes actually sent. 289 | * \sa send(const QString &message) 290 | */ 291 | qint64 WebSocket::send(const char *message) 292 | { 293 | return send(QString::fromUtf8(message)); 294 | } 295 | 296 | /** 297 | * @brief Sends the given \a message over the socket as a text message and returns the number of bytes actually sent. 298 | * @param message The message to be sent 299 | * @return The number of bytes actually sent. 300 | */ 301 | qint64 WebSocket::send(const QString &message) 302 | { 303 | return doWriteData(message.toUtf8(), false); 304 | } 305 | 306 | /** 307 | * @brief Sends the given \a data over the socket as a binary message and returns the number of bytes actually sent. 308 | * @param data The binary data to be sent. 309 | * @return The number of bytes actually sent. 310 | */ 311 | qint64 WebSocket::send(const QByteArray &data) 312 | { 313 | return doWriteData(data, true); 314 | } 315 | 316 | /*! 317 | \internal 318 | */ 319 | WebSocket *WebSocket::upgradeFrom(QTcpSocket *pTcpSocket, 320 | const HandshakeRequest &request, 321 | const HandshakeResponse &response, 322 | QObject *parent) 323 | { 324 | WebSocket *pWebSocket = new WebSocket(pTcpSocket, response.getAcceptedVersion(), parent); 325 | pWebSocket->setExtension(response.getAcceptedExtension()); 326 | pWebSocket->setOrigin(request.getOrigin()); 327 | pWebSocket->setRequestUrl(request.getRequestUrl()); 328 | pWebSocket->setProtocol(response.getAcceptedProtocol()); 329 | pWebSocket->setResourceName(request.getRequestUrl().toString(QUrl::RemoveUserInfo)); 330 | pWebSocket->enableMasking(false); //a server should not send masked frames 331 | 332 | return pWebSocket; 333 | } 334 | 335 | /*! 336 | * \brief Gracefully closes the socket with the given \a closeCode and \a reason. Any data in the write buffer is flushed before the socket is closed. 337 | * \param closeCode The WebSocketProtocol::CloseCode indicating the reason to close. 338 | * \param reason A string describing the error more in detail 339 | */ 340 | void WebSocket::close(WebSocketProtocol::CloseCode closeCode, QString reason) 341 | { 342 | if (!m_isClosingHandshakeSent) 343 | { 344 | quint32 maskingKey = 0; 345 | if (m_mustMask) 346 | { 347 | maskingKey = generateMaskingKey(); 348 | } 349 | quint16 code = qToBigEndian(closeCode); 350 | QByteArray payload; 351 | payload.append(static_cast(static_cast(&code)), 2); 352 | if (!reason.isEmpty()) 353 | { 354 | payload.append(reason.toUtf8()); 355 | } 356 | if (m_mustMask) 357 | { 358 | WebSocketProtocol::mask(payload.data(), payload.size(), maskingKey); 359 | } 360 | QByteArray frame = getFrameHeader(WebSocketProtocol::OC_CLOSE, payload.size(), maskingKey, true); 361 | frame.append(payload); 362 | m_pSocket->write(frame); 363 | m_pSocket->flush(); 364 | 365 | m_isClosingHandshakeSent = true; 366 | 367 | Q_EMIT aboutToClose(); 368 | } 369 | m_pSocket->close(); 370 | } 371 | 372 | /*! 373 | * \brief Opens a websocket connection using the given \a url. 374 | * If \a mask is true, all frames will be masked; this is only necessary for client side sockets; servers should never mask 375 | * \param url The url to connect to 376 | * \param mask When true, all frames are masked 377 | * \note A client socket must *always* mask its frames; servers may *never* mask its frames 378 | */ 379 | void WebSocket::open(const QUrl &url, bool mask) 380 | { 381 | m_dataProcessor.clear(); 382 | m_isClosingHandshakeReceived = false; 383 | m_isClosingHandshakeSent = false; 384 | 385 | setRequestUrl(url); 386 | 387 | QString resourceName = url.path(QUrl::FullyEncoded); 388 | if (!url.query().isEmpty()) 389 | { 390 | if (!resourceName.endsWith(QChar::fromLatin1('?'))) 391 | { 392 | resourceName.append(QChar::fromLatin1('?')); 393 | } 394 | resourceName.append(url.query(QUrl::FullyEncoded)); 395 | } 396 | 397 | if (resourceName.isEmpty()) 398 | { 399 | resourceName = QStringLiteral("/"); 400 | } 401 | 402 | 403 | setResourceName(resourceName); 404 | enableMasking(mask); 405 | 406 | setSocketState(QAbstractSocket::ConnectingState); 407 | 408 | m_pSocket->connectToHost(url.host(), url.port(80)); 409 | } 410 | 411 | /*! 412 | * \brief Pings the server to indicate that the connection is still alive. 413 | * 414 | * \sa pong() 415 | */ 416 | void WebSocket::ping() 417 | { 418 | m_pingTimer.restart(); 419 | QByteArray pingFrame = getFrameHeader(WebSocketProtocol::OC_PING, 0, 0, true); 420 | writeFrame(pingFrame); 421 | } 422 | 423 | /*! 424 | \internal 425 | Sets the version to use for the websocket protocol; this must be set before the socket is opened. 426 | */ 427 | void WebSocket::setVersion(WebSocketProtocol::Version version) 428 | { 429 | m_version = version; 430 | } 431 | 432 | /*! 433 | \internal 434 | Sets the resource name of the connection; must be set before the socket is openend 435 | */ 436 | void WebSocket::setResourceName(QString resourceName) 437 | { 438 | m_resourceName = resourceName; 439 | } 440 | 441 | /*! 442 | \internal 443 | */ 444 | void WebSocket::setRequestUrl(QUrl requestUrl) 445 | { 446 | m_requestUrl = requestUrl; 447 | } 448 | 449 | /*! 450 | \internal 451 | */ 452 | void WebSocket::setOrigin(QString origin) 453 | { 454 | m_origin = origin; 455 | } 456 | 457 | /*! 458 | \internal 459 | */ 460 | void WebSocket::setProtocol(QString protocol) 461 | { 462 | m_protocol = protocol; 463 | } 464 | 465 | /*! 466 | \internal 467 | */ 468 | void WebSocket::setExtension(QString extension) 469 | { 470 | m_extension = extension; 471 | } 472 | 473 | /*! 474 | \internal 475 | */ 476 | void WebSocket::enableMasking(bool enable) 477 | { 478 | m_mustMask = enable; 479 | } 480 | 481 | /*! 482 | * \internal 483 | */ 484 | qint64 WebSocket::doWriteData(const QByteArray &data, bool isBinary) 485 | { 486 | return doWriteFrames(data, isBinary); 487 | } 488 | 489 | /*! 490 | * \internal 491 | */ 492 | void WebSocket::makeConnections(const QTcpSocket *pTcpSocket) 493 | { 494 | //pass through signals 495 | connect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); 496 | connect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); 497 | connect(pTcpSocket, SIGNAL(readChannelFinished()), this, SIGNAL(readChannelFinished())); 498 | //connect(pTcpSocket, SIGNAL(aboutToClose()), this, SIGNAL(aboutToClose())); 499 | //connect(pTcpSocket, SIGNAL(bytesWritten(qint64)), this, SIGNAL(bytesWritten(qint64))); 500 | 501 | //catch signals 502 | connect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); 503 | connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); 504 | 505 | connect(&m_dataProcessor, SIGNAL(controlFrameReceived(WebSocketProtocol::OpCode, QByteArray)), this, SLOT(processControlFrame(WebSocketProtocol::OpCode, QByteArray))); 506 | connect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), this, SIGNAL(textFrameReceived(QString,bool))); 507 | connect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), this, SIGNAL(binaryFrameReceived(QByteArray,bool))); 508 | connect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), this, SIGNAL(binaryMessageReceived(QByteArray))); 509 | connect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); 510 | connect(&m_dataProcessor, SIGNAL(errorEncountered(WebSocketProtocol::CloseCode,QString)), this, SLOT(close(WebSocketProtocol::CloseCode,QString))); 511 | } 512 | 513 | /*! 514 | * \internal 515 | */ 516 | void WebSocket::releaseConnections(const QTcpSocket *pTcpSocket) 517 | { 518 | if (pTcpSocket) 519 | { 520 | //pass through signals 521 | disconnect(pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); 522 | disconnect(pTcpSocket, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)), this, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *))); 523 | disconnect(pTcpSocket, SIGNAL(readChannelFinished()), this, SIGNAL(readChannelFinished())); 524 | //disconnect(pTcpSocket, SIGNAL(aboutToClose()), this, SIGNAL(aboutToClose())); 525 | //disconnect(pTcpSocket, SIGNAL(bytesWritten(qint64)), this, SIGNAL(bytesWritten(qint64))); 526 | 527 | //catched signals 528 | disconnect(pTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(processStateChanged(QAbstractSocket::SocketState))); 529 | disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(processData())); 530 | } 531 | disconnect(&m_dataProcessor, SIGNAL(controlFrameReceived(WebSocketProtocol::OpCode,QByteArray)), this, SLOT(processControlFrame(WebSocketProtocol::OpCode,QByteArray))); 532 | disconnect(&m_dataProcessor, SIGNAL(textFrameReceived(QString,bool)), this, SIGNAL(textFrameReceived(QString,bool))); 533 | disconnect(&m_dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)), this, SIGNAL(binaryFrameReceived(QByteArray,bool))); 534 | disconnect(&m_dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)), this, SIGNAL(binaryMessageReceived(QByteArray))); 535 | disconnect(&m_dataProcessor, SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); 536 | disconnect(&m_dataProcessor, SIGNAL(errorEncountered(WebSocketProtocol::CloseCode,QString)), this, SLOT(close(WebSocketProtocol::CloseCode,QString))); 537 | } 538 | 539 | /*! 540 | * \brief Returns the version the socket is currently using 541 | */ 542 | WebSocketProtocol::Version WebSocket::getVersion() 543 | { 544 | return m_version; 545 | } 546 | 547 | /** 548 | * @brief Returns the name of the resource currently accessed. 549 | */ 550 | QString WebSocket::getResourceName() 551 | { 552 | return m_resourceName; 553 | } 554 | 555 | /*! 556 | * \brief Returns the url the socket is connected to or will connect to. 557 | */ 558 | QUrl WebSocket::getRequestUrl() 559 | { 560 | return m_requestUrl; 561 | } 562 | 563 | /*! 564 | Returns the current origin 565 | */ 566 | QString WebSocket::getOrigin() 567 | { 568 | return m_origin; 569 | } 570 | 571 | /*! 572 | Returns the currently used protocol. 573 | */ 574 | QString WebSocket::getProtocol() 575 | { 576 | return m_protocol; 577 | } 578 | 579 | /*! 580 | Returns the currently used extension. 581 | */ 582 | QString WebSocket::getExtension() 583 | { 584 | return m_extension; 585 | } 586 | 587 | /*! 588 | * \internal 589 | */ 590 | QByteArray WebSocket::getFrameHeader(WebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const 591 | { 592 | QByteArray header; 593 | quint8 byte = 0x00; 594 | bool ok = payloadLength <= 0x7FFFFFFFFFFFFFFFULL; 595 | 596 | if (ok) 597 | { 598 | //FIN, RSV1-3, opcode 599 | byte = static_cast((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00)); //FIN, opcode 600 | //RSV-1, RSV-2 and RSV-3 are zero 601 | header.append(static_cast(byte)); 602 | 603 | //Now write the masking bit and the payload length byte 604 | byte = 0x00; 605 | if (maskingKey != 0) 606 | { 607 | byte |= 0x80; 608 | } 609 | if (payloadLength <= 125) 610 | { 611 | byte |= static_cast(payloadLength); 612 | header.append(static_cast(byte)); 613 | } 614 | else if (payloadLength <= 0xFFFFU) 615 | { 616 | byte |= 126; 617 | header.append(static_cast(byte)); 618 | quint16 swapped = qToBigEndian(static_cast(payloadLength)); 619 | header.append(static_cast(static_cast(&swapped)), 2); 620 | } 621 | else if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL) 622 | { 623 | byte |= 127; 624 | header.append(static_cast(byte)); 625 | quint64 swapped = qToBigEndian(payloadLength); 626 | header.append(static_cast(static_cast(&swapped)), 8); 627 | } 628 | 629 | //Write mask 630 | if (maskingKey != 0) 631 | { 632 | header.append(static_cast(static_cast(&maskingKey)), sizeof(quint32)); 633 | } 634 | } 635 | else 636 | { 637 | //setErrorString("WebSocket::getHeader: payload too big!"); 638 | //Q_EMIT error(QAbstractSocket::DatagramTooLargeError); 639 | qDebug() << "WebSocket::getHeader: payload too big!"; 640 | } 641 | 642 | return header; 643 | } 644 | 645 | /*! 646 | * \internal 647 | */ 648 | qint64 WebSocket::doWriteFrames(const QByteArray &data, bool isBinary) 649 | { 650 | const WebSocketProtocol::OpCode firstOpCode = isBinary ? WebSocketProtocol::OC_BINARY : WebSocketProtocol::OC_TEXT; 651 | 652 | int numFrames = data.size() / FRAME_SIZE_IN_BYTES; 653 | QByteArray tmpData(data); 654 | tmpData.detach(); 655 | char *payload = tmpData.data(); 656 | quint64 sizeLeft = static_cast(data.size()) % FRAME_SIZE_IN_BYTES; 657 | if (sizeLeft) 658 | { 659 | ++numFrames; 660 | } 661 | if (numFrames == 0) //catch the case where the payload is zero bytes; in that case, we still need to send a frame 662 | { 663 | numFrames = 1; 664 | } 665 | quint64 currentPosition = 0; 666 | qint64 bytesWritten = 0; 667 | qint64 payloadWritten = 0; 668 | quint64 bytesLeft = data.size(); 669 | 670 | for (int i = 0; i < numFrames; ++i) 671 | { 672 | quint32 maskingKey = 0; 673 | if (m_mustMask) 674 | { 675 | maskingKey = generateMaskingKey(); 676 | } 677 | 678 | bool isLastFrame = (i == (numFrames - 1)); 679 | bool isFirstFrame = (i == 0); 680 | 681 | quint64 size = qMin(bytesLeft, FRAME_SIZE_IN_BYTES); 682 | WebSocketProtocol::OpCode opcode = isFirstFrame ? firstOpCode : WebSocketProtocol::OC_CONTINUE; 683 | 684 | //write header 685 | bytesWritten += m_pSocket->write(getFrameHeader(opcode, size, maskingKey, isLastFrame)); 686 | 687 | //write payload 688 | if (size > 0) 689 | { 690 | char *currentData = payload + currentPosition; 691 | if (m_mustMask) 692 | { 693 | WebSocketProtocol::mask(currentData, size, maskingKey); 694 | } 695 | qint64 written = m_pSocket->write(currentData, static_cast(size)); 696 | if (written > 0) 697 | { 698 | bytesWritten += written; 699 | payloadWritten += written; 700 | } 701 | else 702 | { 703 | setErrorString("WebSocket::doWriteFrames: Error writing bytes to socket: " + m_pSocket->errorString()); 704 | qDebug() << errorString(); 705 | m_pSocket->flush(); 706 | Q_EMIT error(QAbstractSocket::NetworkError); 707 | break; 708 | } 709 | } 710 | currentPosition += size; 711 | bytesLeft -= size; 712 | } 713 | if (payloadWritten != data.size()) 714 | { 715 | setErrorString("Bytes written " + QString::number(payloadWritten) + " != " + QString::number(data.size())); 716 | qDebug() << errorString(); 717 | Q_EMIT error(QAbstractSocket::NetworkError); 718 | } 719 | return payloadWritten; 720 | } 721 | 722 | /*! 723 | * \internal 724 | */ 725 | quint32 WebSocket::generateRandomNumber() const 726 | { 727 | return static_cast((static_cast(qrand()) / RAND_MAX) * std::numeric_limits::max()); 728 | } 729 | 730 | /*! 731 | \internal 732 | */ 733 | quint32 WebSocket::generateMaskingKey() const 734 | { 735 | return generateRandomNumber(); 736 | } 737 | 738 | /*! 739 | \internal 740 | */ 741 | QByteArray WebSocket::generateKey() const 742 | { 743 | QByteArray key; 744 | 745 | for (int i = 0; i < 4; ++i) 746 | { 747 | quint32 tmp = generateRandomNumber(); 748 | key.append(static_cast(static_cast(&tmp)), sizeof(quint32)); 749 | } 750 | 751 | return key.toBase64(); 752 | } 753 | 754 | 755 | /*! 756 | \internal 757 | */ 758 | QString WebSocket::calculateAcceptKey(const QString &key) const 759 | { 760 | QString tmpKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 761 | QByteArray hash = QCryptographicHash::hash(tmpKey.toLatin1(), QCryptographicHash::Sha1); 762 | return QString(hash.toBase64()); 763 | } 764 | 765 | /*! 766 | \internal 767 | */ 768 | qint64 WebSocket::writeFrames(const QList &frames) 769 | { 770 | qint64 written = 0; 771 | for (int i = 0; i < frames.size(); ++i) 772 | { 773 | written += writeFrame(frames[i]); 774 | } 775 | return written; 776 | } 777 | 778 | /*! 779 | \internal 780 | */ 781 | qint64 WebSocket::writeFrame(const QByteArray &frame) 782 | { 783 | return m_pSocket->write(frame); 784 | } 785 | 786 | /*! 787 | \internal 788 | */ 789 | QString readLine(QTcpSocket *pSocket) 790 | { 791 | QString line; 792 | char c; 793 | while (pSocket->getChar(&c)) 794 | { 795 | if (c == '\r') 796 | { 797 | pSocket->getChar(&c); 798 | break; 799 | } 800 | else 801 | { 802 | line.append(QChar(c)); 803 | } 804 | } 805 | return line; 806 | } 807 | 808 | //called for a server handshake response 809 | /*! 810 | \internal 811 | */ 812 | void WebSocket::processHandshake(QTcpSocket *pSocket) 813 | { 814 | if (pSocket == 0) 815 | { 816 | return; 817 | } 818 | 819 | bool ok = false; 820 | QString errorDescription; 821 | 822 | const QString regExpStatusLine("^(HTTP/1.1)\\s([0-9]+)\\s(.*)"); 823 | const QRegExp regExp(regExpStatusLine); 824 | QString statusLine = readLine(pSocket); 825 | QString httpProtocol; 826 | int httpStatusCode; 827 | QString httpStatusMessage; 828 | if (regExp.indexIn(statusLine) != -1) 829 | { 830 | QStringList tokens = regExp.capturedTexts(); 831 | tokens.removeFirst(); //remove the search string 832 | if (tokens.length() == 3) 833 | { 834 | httpProtocol = tokens[0]; 835 | httpStatusCode = tokens[1].toInt(); 836 | httpStatusMessage = tokens[2].trimmed(); 837 | ok = true; 838 | } 839 | } 840 | if (!ok) 841 | { 842 | errorDescription = "WebSocket::processHandshake: Invalid statusline in response: " + statusLine; 843 | } 844 | else 845 | { 846 | QString headerLine = readLine(pSocket); 847 | QMap headers; 848 | while (!headerLine.isEmpty()) 849 | { 850 | QStringList headerField = headerLine.split(QString(": "), QString::SkipEmptyParts); 851 | headers.insertMulti(headerField[0], headerField[1]); 852 | headerLine = readLine(pSocket); 853 | } 854 | 855 | QString acceptKey = headers.value("Sec-WebSocket-Accept", ""); 856 | QString upgrade = headers.value("Upgrade", ""); 857 | QString connection = headers.value("Connection", ""); 858 | QString extensions = headers.value("Sec-WebSocket-Extensions", ""); 859 | QString protocol = headers.value("Sec-WebSocket-Protocol", ""); 860 | QString version = headers.value("Sec-WebSocket-Version", ""); 861 | 862 | if (httpStatusCode == 101) //HTTP/1.1 101 Switching Protocols 863 | { 864 | //TODO: do not check the httpStatusText right now 865 | ok = !(acceptKey.isEmpty() || 866 | (httpProtocol.toLower() != "http/1.1") || 867 | (upgrade.toLower() != "websocket") || 868 | (connection.toLower() != "upgrade")); 869 | if (ok) 870 | { 871 | QString accept = calculateAcceptKey(m_key); 872 | ok = (accept == acceptKey); 873 | if (!ok) 874 | { 875 | errorDescription = "WebSocket::processHandshake: Accept-Key received from server " + acceptKey + " does not match the client key " + accept; 876 | } 877 | } 878 | else 879 | { 880 | errorDescription = "WebSocket::processHandshake: Invalid statusline in response: " + statusLine; 881 | } 882 | } 883 | else if (httpStatusCode == 400) //HTTP/1.1 400 Bad Request 884 | { 885 | if (!version.isEmpty()) 886 | { 887 | QStringList versions = version.split(", ", QString::SkipEmptyParts); 888 | if (!versions.contains("13")) 889 | { 890 | //if needed to switch protocol version, then we are finished here 891 | //because we cannot handle other protocols than the RFC one (v13) 892 | errorDescription = "WebSocket::processHandshake: Server requests a version that we don't support: " + versions.join(", "); 893 | ok = false; 894 | } 895 | else 896 | { 897 | //we tried v13, but something different went wrong 898 | errorDescription = "WebSocket::processHandshake: Unknown error condition encountered. Aborting connection."; 899 | ok = false; 900 | } 901 | } 902 | } 903 | else 904 | { 905 | errorDescription = "WebSocket::processHandshake: Unhandled http status code " + QString::number(httpStatusCode); 906 | ok = false; 907 | } 908 | 909 | if (!ok) 910 | { 911 | qDebug() << errorDescription; 912 | setErrorString(errorDescription); 913 | Q_EMIT error(QAbstractSocket::ConnectionRefusedError); 914 | } 915 | else 916 | { 917 | //handshake succeeded 918 | setSocketState(QAbstractSocket::ConnectedState); 919 | Q_EMIT connected(); 920 | } 921 | } 922 | } 923 | 924 | /*! 925 | \internal 926 | */ 927 | void WebSocket::processStateChanged(QAbstractSocket::SocketState socketState) 928 | { 929 | QAbstractSocket::SocketState webSocketState = this->state(); 930 | switch (socketState) 931 | { 932 | case QAbstractSocket::ConnectedState: 933 | { 934 | if (webSocketState == QAbstractSocket::ConnectingState) 935 | { 936 | m_key = generateKey(); 937 | QString handshake = createHandShakeRequest(m_resourceName, m_requestUrl.host() + ":" + QString::number(m_requestUrl.port(80)), getOrigin(), "", "", m_key); 938 | m_pSocket->write(handshake.toLatin1()); 939 | } 940 | break; 941 | } 942 | case QAbstractSocket::ClosingState: 943 | { 944 | if (webSocketState == QAbstractSocket::ConnectedState) 945 | { 946 | setSocketState(QAbstractSocket::ClosingState); 947 | } 948 | break; 949 | } 950 | case QAbstractSocket::UnconnectedState: 951 | { 952 | if (webSocketState != QAbstractSocket::UnconnectedState) 953 | { 954 | setSocketState(QAbstractSocket::UnconnectedState); 955 | Q_EMIT disconnected(); 956 | } 957 | break; 958 | } 959 | case QAbstractSocket::HostLookupState: 960 | case QAbstractSocket::ConnectingState: 961 | case QAbstractSocket::BoundState: 962 | case QAbstractSocket::ListeningState: 963 | { 964 | //do nothing 965 | //to make C++ compiler happy; 966 | break; 967 | } 968 | default: 969 | { 970 | break; 971 | } 972 | } 973 | } 974 | 975 | //order of events: 976 | //connectToHost is called 977 | //our socket state is set to "connecting", and tcpSocket->connectToHost is called 978 | //the tcpsocket is opened, a handshake message is sent; a readyRead signal is thrown 979 | //this signal is catched by processData 980 | //when OUR socket state is in the "connecting state", this means that 981 | //we have received data from the server (response to handshake), and that we 982 | //should "upgrade" our socket to a websocket (connected state) 983 | //if our socket was already upgraded, then we need to process websocket data 984 | /*! 985 | \internal 986 | */ 987 | void WebSocket::processData() 988 | { 989 | while (m_pSocket->bytesAvailable()) 990 | { 991 | if (state() == QAbstractSocket::ConnectingState) 992 | { 993 | processHandshake(m_pSocket); 994 | } 995 | else 996 | { 997 | m_dataProcessor.process(m_pSocket); 998 | } 999 | } 1000 | } 1001 | 1002 | /*! 1003 | \internal 1004 | */ 1005 | void WebSocket::processControlFrame(WebSocketProtocol::OpCode opCode, QByteArray frame) 1006 | { 1007 | switch (opCode) 1008 | { 1009 | case WebSocketProtocol::OC_PING: 1010 | { 1011 | quint32 maskingKey = 0; 1012 | if (m_mustMask) 1013 | { 1014 | maskingKey = generateMaskingKey(); 1015 | } 1016 | m_pSocket->write(getFrameHeader(WebSocketProtocol::OC_PONG, frame.size(), maskingKey, true)); 1017 | if (frame.size() > 0) 1018 | { 1019 | if (m_mustMask) 1020 | { 1021 | WebSocketProtocol::mask(&frame, maskingKey); 1022 | } 1023 | m_pSocket->write(frame); 1024 | } 1025 | break; 1026 | } 1027 | case WebSocketProtocol::OC_PONG: 1028 | { 1029 | Q_EMIT pong(static_cast(m_pingTimer.elapsed())); 1030 | break; 1031 | } 1032 | case WebSocketProtocol::OC_CLOSE: 1033 | { 1034 | quint16 closeCode = WebSocketProtocol::CC_NORMAL; 1035 | QString closeReason; 1036 | if (frame.size() > 0) //close frame can have a close code and reason 1037 | { 1038 | closeCode = qFromBigEndian(reinterpret_cast(frame.constData())); 1039 | if (!WebSocketProtocol::isCloseCodeValid(closeCode)) 1040 | { 1041 | closeCode = WebSocketProtocol::CC_PROTOCOL_ERROR; 1042 | closeReason = QString("Invalid close code %1 detected").arg(closeCode); 1043 | } 1044 | else 1045 | { 1046 | if (frame.size() > 2) 1047 | { 1048 | QTextCodec *tc = QTextCodec::codecForName("UTF-8"); 1049 | QTextCodec::ConverterState state(QTextCodec::ConvertInvalidToNull); 1050 | closeReason = tc->toUnicode(frame.constData() + 2, frame.size() - 2, &state); 1051 | bool failed = (state.invalidChars != 0) || (state.remainingChars != 0); 1052 | if (failed) 1053 | { 1054 | closeCode = WebSocketProtocol::CC_WRONG_DATATYPE; 1055 | closeReason = "Invalid UTF-8 code encountered."; 1056 | } 1057 | } 1058 | } 1059 | } 1060 | m_isClosingHandshakeReceived = true; 1061 | close(static_cast(closeCode), closeReason); 1062 | break; 1063 | } 1064 | case WebSocketProtocol::OC_CONTINUE: 1065 | case WebSocketProtocol::OC_BINARY: 1066 | case WebSocketProtocol::OC_TEXT: 1067 | case WebSocketProtocol::OC_RESERVED_3: 1068 | case WebSocketProtocol::OC_RESERVED_4: 1069 | case WebSocketProtocol::OC_RESERVED_5: 1070 | case WebSocketProtocol::OC_RESERVED_6: 1071 | case WebSocketProtocol::OC_RESERVED_7: 1072 | case WebSocketProtocol::OC_RESERVED_B: 1073 | case WebSocketProtocol::OC_RESERVED_D: 1074 | case WebSocketProtocol::OC_RESERVED_E: 1075 | case WebSocketProtocol::OC_RESERVED_F: 1076 | case WebSocketProtocol::OC_RESERVED_V: 1077 | { 1078 | //do nothing 1079 | //case added to make C++ compiler happy 1080 | break; 1081 | } 1082 | default: 1083 | { 1084 | qDebug() << "WebSocket::processData: Invalid opcode detected:" << static_cast(opCode); 1085 | //Do nothing 1086 | break; 1087 | } 1088 | } 1089 | } 1090 | 1091 | /*! 1092 | \internal 1093 | */ 1094 | QString WebSocket::createHandShakeRequest(QString resourceName, 1095 | QString host, 1096 | QString origin, 1097 | QString extensions, 1098 | QString protocols, 1099 | QByteArray key) 1100 | { 1101 | QStringList handshakeRequest; 1102 | 1103 | handshakeRequest << "GET " + resourceName + " HTTP/1.1" << 1104 | "Host: " + host << 1105 | "Upgrade: websocket" << 1106 | "Connection: Upgrade" << 1107 | "Sec-WebSocket-Key: " + QString(key); 1108 | if (!origin.isEmpty()) 1109 | { 1110 | handshakeRequest << "Origin: " + origin; 1111 | } 1112 | handshakeRequest << "Sec-WebSocket-Version: " + QString::number(WebSocketProtocol::getCurrentVersion()); 1113 | if (extensions.length() > 0) 1114 | { 1115 | handshakeRequest << "Sec-WebSocket-Extensions: " + extensions; 1116 | } 1117 | if (protocols.length() > 0) 1118 | { 1119 | handshakeRequest << "Sec-WebSocket-Protocol: " + protocols; 1120 | } 1121 | handshakeRequest << "\r\n"; 1122 | 1123 | return handshakeRequest.join("\r\n"); 1124 | } 1125 | 1126 | /*! 1127 | Returns the current state of the socket 1128 | */ 1129 | QAbstractSocket::SocketState WebSocket::state() const 1130 | { 1131 | return m_socketState; 1132 | } 1133 | 1134 | /** 1135 | @brief Waits until the socket is connected, up to \a msecs milliseconds. If the connection has been established, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error. 1136 | The following example waits up to one second for a connection to be established: 1137 | 1138 | ~~~{.cpp} 1139 | socket->open("ws://localhost:1234", false); 1140 | if (socket->waitForConnected(1000)) 1141 | { 1142 | qDebug("Connected!"); 1143 | } 1144 | ~~~ 1145 | 1146 | If \a msecs is -1, this function will not time out. 1147 | @note This function may wait slightly longer than msecs, depending on the time it takes to complete the host lookup. 1148 | @note Multiple calls to this functions do not accumulate the time. If the function times out, the connecting process will be aborted. 1149 | 1150 | \param msecs The number of milliseconds to wait before a time out occurs; when -1, this function will block until the socket is connected. 1151 | 1152 | \sa connected(), open(), state() 1153 | */ 1154 | bool WebSocket::waitForConnected(int msecs) 1155 | { 1156 | bool retVal = false; 1157 | if (m_pSocket) 1158 | { 1159 | retVal = m_pSocket->waitForConnected(msecs); 1160 | } 1161 | return retVal; 1162 | } 1163 | 1164 | /*! 1165 | Waits \a msecs for the socket to be disconnected. 1166 | If the socket was successfully disconnected within time, this method returns true. 1167 | Otherwise false is returned. 1168 | 1169 | \param msecs The number of milliseconds to wait before a time out occurs; when -1, this function will block until the socket is disconnected. 1170 | 1171 | \sa close(), state() 1172 | */ 1173 | bool WebSocket::waitForDisconnected(int msecs) 1174 | { 1175 | bool retVal = true; 1176 | if (m_pSocket) 1177 | { 1178 | retVal = m_pSocket->waitForDisconnected(msecs); 1179 | } 1180 | return retVal; 1181 | } 1182 | 1183 | /*! 1184 | \internal 1185 | Sets the internal socket state 1186 | */ 1187 | void WebSocket::setSocketState(QAbstractSocket::SocketState state) 1188 | { 1189 | if (m_socketState != state) 1190 | { 1191 | m_socketState = state; 1192 | Q_EMIT stateChanged(m_socketState); 1193 | } 1194 | } 1195 | 1196 | /*! 1197 | \internal 1198 | Sets the error string. 1199 | Only used internally. 1200 | */ 1201 | void WebSocket::setErrorString(QString errorString) 1202 | { 1203 | m_errorString = errorString; 1204 | } 1205 | 1206 | /*! 1207 | Returns the local address 1208 | */ 1209 | QHostAddress WebSocket::localAddress() const 1210 | { 1211 | QHostAddress address; 1212 | if (m_pSocket) 1213 | { 1214 | address = m_pSocket->localAddress(); 1215 | } 1216 | return address; 1217 | } 1218 | 1219 | /*! 1220 | Returns the local port 1221 | */ 1222 | quint16 WebSocket::localPort() const 1223 | { 1224 | quint16 port = 0; 1225 | if (m_pSocket) 1226 | { 1227 | port = m_pSocket->localPort(); 1228 | } 1229 | return port; 1230 | } 1231 | 1232 | /*! 1233 | Returns the peer address 1234 | */ 1235 | QHostAddress WebSocket::peerAddress() const 1236 | { 1237 | QHostAddress peer; 1238 | if (m_pSocket) 1239 | { 1240 | peer = m_pSocket->peerAddress(); 1241 | } 1242 | return peer; 1243 | } 1244 | 1245 | /*! 1246 | Returns the peerName 1247 | */ 1248 | QString WebSocket::peerName() const 1249 | { 1250 | QString name; 1251 | if (m_pSocket) 1252 | { 1253 | name = m_pSocket->peerName(); 1254 | } 1255 | return name; 1256 | } 1257 | 1258 | /*! 1259 | Returns the peerport 1260 | */ 1261 | quint16 WebSocket::peerPort() const 1262 | { 1263 | quint16 port = 0; 1264 | if (m_pSocket) 1265 | { 1266 | port = m_pSocket->peerPort(); 1267 | } 1268 | return port; 1269 | } 1270 | 1271 | /*! 1272 | * Returns the currently configured proxy 1273 | */ 1274 | QNetworkProxy WebSocket::proxy() const 1275 | { 1276 | QNetworkProxy proxy; 1277 | if (m_pSocket) 1278 | { 1279 | proxy = m_pSocket->proxy(); 1280 | } 1281 | return proxy; 1282 | } 1283 | 1284 | /*! 1285 | * Returns the size in bytes of the readbuffer that is used by the socket. 1286 | */ 1287 | qint64 WebSocket::readBufferSize() const 1288 | { 1289 | qint64 readBuffer = 0; 1290 | if (m_pSocket) 1291 | { 1292 | readBuffer = m_pSocket->readBufferSize(); 1293 | } 1294 | return readBuffer; 1295 | } 1296 | 1297 | /*! 1298 | Sets the proxy to \a networkProxy 1299 | */ 1300 | void WebSocket::setProxy(const QNetworkProxy &networkProxy) 1301 | { 1302 | if (m_pSocket) 1303 | { 1304 | m_pSocket->setProxy(networkProxy); 1305 | } 1306 | } 1307 | 1308 | /** 1309 | Sets the size of WebSocket's internal read buffer to be size bytes. 1310 | If the buffer size is limited to a certain size, WebSocket won't buffer more than this size of data. 1311 | Exceptionally, a buffer size of 0 means that the read buffer is unlimited and all incoming data is buffered. This is the default. 1312 | This option is useful if you only read the data at certain points in time (e.g., in a real-time streaming application) or if you want to protect your socket against receiving too much data, which may eventually cause your application to run out of memory. 1313 | \sa readBufferSize() and read(). 1314 | */ 1315 | void WebSocket::setReadBufferSize(qint64 size) 1316 | { 1317 | if (m_pSocket) 1318 | { 1319 | m_pSocket->setReadBufferSize(size); 1320 | } 1321 | } 1322 | 1323 | /*! 1324 | Sets the given \a option to the value described by \a value. 1325 | \sa socketOption(). 1326 | */ 1327 | void WebSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) 1328 | { 1329 | if (m_pSocket) 1330 | { 1331 | m_pSocket->setSocketOption(option, value); 1332 | } 1333 | } 1334 | 1335 | /*! 1336 | Returns the value of the option \a option. 1337 | \sa setSocketOption(). 1338 | */ 1339 | QVariant WebSocket::socketOption(QAbstractSocket::SocketOption option) 1340 | { 1341 | QVariant result; 1342 | if (m_pSocket) 1343 | { 1344 | result = m_pSocket->socketOption(option); 1345 | } 1346 | return result; 1347 | } 1348 | 1349 | /*! 1350 | Returns true if the WebSocket is valid. 1351 | */ 1352 | bool WebSocket::isValid() 1353 | { 1354 | bool valid = false; 1355 | if (m_pSocket) 1356 | { 1357 | valid = m_pSocket->isValid(); 1358 | } 1359 | return valid; 1360 | } 1361 | -------------------------------------------------------------------------------- /QtClient/source/websocket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file websocket.h 3 | * @brief Defines the WebSocket class. 4 | * 5 | * \note Currently, only V13 (RFC6455) is supported. 6 | * \note Both text and binary websockets are supported. 7 | * \note The secure version (wss) is currently not implemented. 8 | * @author Kurt Pattyn (pattyn.kurt@gmail.com) 9 | */ 10 | 11 | #ifndef WEBSOCKET_H 12 | #define WEBSOCKET_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include "websocketprotocol.h" 18 | #include "dataprocessor.h" 19 | #include 20 | #include 21 | 22 | class HandshakeRequest; 23 | class HandshakeResponse; 24 | class QTcpSocket; 25 | 26 | class WebSocket:public QObject 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | explicit WebSocket(QString origin = QString(), WebSocketProtocol::Version version = WebSocketProtocol::V_LATEST, QObject *parent = 0); 32 | virtual ~WebSocket(); 33 | 34 | void abort(); 35 | QAbstractSocket::SocketError error() const; 36 | QString errorString() const; 37 | bool flush(); 38 | bool isValid(); 39 | QHostAddress localAddress() const; 40 | quint16 localPort() const; 41 | QHostAddress peerAddress() const; 42 | QString peerName() const; 43 | quint16 peerPort() const; 44 | QNetworkProxy proxy() const; 45 | qint64 readBufferSize() const; 46 | void setProxy(const QNetworkProxy &networkProxy); 47 | void setReadBufferSize(qint64 size); 48 | void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value); 49 | QVariant socketOption(QAbstractSocket::SocketOption option); 50 | QAbstractSocket::SocketState state() const; 51 | 52 | bool waitForConnected(int msecs = 30000); 53 | bool waitForDisconnected(int msecs = 30000); 54 | 55 | WebSocketProtocol::Version getVersion(); 56 | QString getResourceName(); 57 | QUrl getRequestUrl(); 58 | QString getOrigin(); 59 | QString getProtocol(); 60 | QString getExtension(); 61 | 62 | qint64 send(const char *message); 63 | qint64 send(const QString &message); //send data as text 64 | qint64 send(const QByteArray &data); //send data as binary 65 | 66 | public Q_SLOTS: 67 | virtual void close(WebSocketProtocol::CloseCode closeCode = WebSocketProtocol::CC_NORMAL, QString reason = QString()); 68 | virtual void open(const QUrl &url, bool mask = true); 69 | void ping(); 70 | 71 | Q_SIGNALS: 72 | void aboutToClose(); 73 | void connected(); 74 | void disconnected(); 75 | void stateChanged(QAbstractSocket::SocketState state); 76 | void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *pAuthenticator); 77 | void readChannelFinished(); 78 | void textFrameReceived(QString frame, bool isLastFrame); 79 | void binaryFrameReceived(QByteArray frame, bool isLastFrame); 80 | void textMessageReceived(QString message); 81 | void binaryMessageReceived(QByteArray message); 82 | void error(QAbstractSocket::SocketError error); 83 | void pong(quint64 elapsedTime); 84 | 85 | private Q_SLOTS: 86 | void processData(); 87 | void processControlFrame(WebSocketProtocol::OpCode opCode, QByteArray frame); 88 | void processHandshake(QTcpSocket *pSocket); 89 | void processStateChanged(QAbstractSocket::SocketState socketState); 90 | 91 | private: 92 | Q_DISABLE_COPY(WebSocket) 93 | 94 | WebSocket(QTcpSocket *pTcpSocket, WebSocketProtocol::Version version, QObject *parent = 0); 95 | void setVersion(WebSocketProtocol::Version version); 96 | void setResourceName(QString resourceName); 97 | void setRequestUrl(QUrl requestUrl); 98 | void setOrigin(QString origin); 99 | void setProtocol(QString protocol); 100 | void setExtension(QString extension); 101 | void enableMasking(bool enable); 102 | void setSocketState(QAbstractSocket::SocketState state); 103 | void setErrorString(QString errorString); 104 | 105 | qint64 doWriteData(const QByteArray &data, bool isBinary); 106 | qint64 doWriteFrames(const QByteArray &data, bool isBinary); 107 | 108 | void makeConnections(const QTcpSocket *pTcpSocket); 109 | void releaseConnections(const QTcpSocket *pTcpSocket); 110 | 111 | QByteArray getFrameHeader(WebSocketProtocol::OpCode opCode, quint64 payloadLength, quint32 maskingKey, bool lastFrame) const; 112 | QString calculateAcceptKey(const QString &key) const; 113 | QString createHandShakeRequest(QString resourceName, 114 | QString host, 115 | QString origin, 116 | QString extensions, 117 | QString protocols, 118 | QByteArray key); 119 | 120 | quint32 generateMaskingKey() const; 121 | QByteArray generateKey() const; 122 | quint32 generateRandomNumber() const; 123 | qint64 writeFrames(const QList &frames); 124 | qint64 writeFrame(const QByteArray &frame); 125 | 126 | static WebSocket *upgradeFrom(QTcpSocket *tcpSocket, 127 | const HandshakeRequest &request, 128 | const HandshakeResponse &response, 129 | QObject *parent = 0); 130 | friend class WebSocketServer; 131 | 132 | QTcpSocket *m_pSocket; 133 | QString m_errorString; 134 | WebSocketProtocol::Version m_version; 135 | QUrl m_resource; 136 | QString m_resourceName; 137 | QUrl m_requestUrl; 138 | QString m_origin; 139 | QString m_protocol; 140 | QString m_extension; 141 | QAbstractSocket::SocketState m_socketState; 142 | 143 | QByteArray m_key; //identification key used in handshake requests 144 | 145 | bool m_mustMask; //a server must not mask the frames it sends 146 | 147 | bool m_isClosingHandshakeSent; 148 | bool m_isClosingHandshakeReceived; 149 | 150 | QTime m_pingTimer; 151 | 152 | DataProcessor m_dataProcessor; 153 | }; 154 | 155 | #endif // WEBSOCKET_H 156 | -------------------------------------------------------------------------------- /QtClient/source/websocket.pri: -------------------------------------------------------------------------------- 1 | QT *= network 2 | 3 | SOURCES += $$PWD/websocket.cpp \ 4 | $$PWD/websocketserver.cpp \ 5 | $$PWD/websocketprotocol.cpp \ 6 | $$PWD/handshakerequest.cpp \ 7 | $$PWD/handshakeresponse.cpp \ 8 | $$PWD/dataprocessor.cpp 9 | 10 | HEADERS += $$PWD/websocket.h \ 11 | $$PWD/websocketserver.h \ 12 | $$PWD/websocketprotocol.h \ 13 | $$PWD/handshakerequest.h \ 14 | $$PWD/handshakeresponse.h \ 15 | $$PWD/dataprocessor.h 16 | 17 | INCLUDEPATH += $$PWD 18 | DEPENDPATH += $$PWD 19 | -------------------------------------------------------------------------------- /QtClient/source/websocketprotocol.cpp: -------------------------------------------------------------------------------- 1 | #include "websocketprotocol.h" 2 | #include 3 | #include 4 | #include 5 | 6 | /*! 7 | \enum WebSocketProtocol::CloseCode 8 | 9 | The close codes supported by WebSockets V13 10 | \sa WebSocket::close() 11 | */ 12 | /*! 13 | \var WebSocketProtocol::CloseCode::CC_NORMAL 14 | Normal closure 15 | */ 16 | /*! 17 | \var WebSocketProtocol::CloseCode::CC_GOING_AWAY 18 | Going away 19 | */ 20 | /*! 21 | \var WebSocketProtocol::CloseCode::CC_PROTOCOL_ERROR 22 | Protocol error 23 | */ 24 | /*! 25 | \var WebSocketProtocol::CloseCode::CC_DATATYPE_NOT_SUPPORTED 26 | Unsupported data 27 | */ 28 | /*! 29 | \var WebSocketProtocol::CloseCode::CC_RESERVED_1004 30 | Reserved 31 | */ 32 | /*! 33 | \var WebSocketProtocol::CloseCode::CC_MISSING_STATUS_CODE 34 | No status received 35 | */ 36 | /*! 37 | \var WebSocketProtocol::CloseCode::CC_ABNORMAL_DISCONNECTION 38 | Abnormal closure 39 | */ 40 | /*! 41 | \var WebSocketProtocol::CloseCode::CC_WRONG_DATATYPE 42 | Invalid frame payload data 43 | */ 44 | /*! 45 | \var WebSocketProtocol::CloseCode::CC_POLICY_VIOLATED 46 | Policy violation 47 | */ 48 | /*! 49 | \var WebSocketProtocol::CloseCode::CC_TOO_MUCH_DATA 50 | Message too big 51 | */ 52 | /*! 53 | \var WebSocketProtocol::CloseCode::CC_MISSING_EXTENSION 54 | Mandatory extension missing 55 | */ 56 | /*! 57 | \var WebSocketProtocol::CloseCode::CC_BAD_OPERATION 58 | Internal server error 59 | */ 60 | /*! 61 | \var WebSocketProtocol::CloseCode::CC_TLS_HANDSHAKE_FAILED 62 | TLS handshake failed 63 | */ 64 | /*! 65 | \enum WebSocketProtocol::Version 66 | 67 | \brief The different defined versions of the Websocket protocol. 68 | 69 | For an overview of the differences between the different protocols, see 70 | 71 | */ 72 | /*! 73 | \var WebSocketProtocol::Version::V_Unknow 74 | */ 75 | /*! 76 | \var WebSocketProtocol::Version::V_0 77 | hixie76: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 & hybi-00: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00. 78 | Works with key1, key2 and a key in the payload.\n 79 | Attribute: Sec-WebSocket-Draft value 0. 80 | */ 81 | /*! 82 | \var WebSocketProtocol::Version::V_4 83 | hybi-04: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-04.txt. 84 | Changed handshake: key1, key2, key3 ==> Sec-WebSocket-Key, Sec-WebSocket-Nonce, Sec-WebSocket-Accept\n 85 | Sec-WebSocket-Draft renamed to Sec-WebSocket-Version\n 86 | Sec-WebSocket-Version = 4 87 | */ 88 | /*! 89 | \var WebSocketProtocol::Version::V_5 90 | hybi-05: http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-05.txt. 91 | Sec-WebSocket-Version = 5\n 92 | Removed Sec-WebSocket-Nonce\n 93 | Added Sec-WebSocket-Accept\n 94 | */ 95 | /*! 96 | \var WebSocketProtocol::Version::V_6 97 | Sec-WebSocket-Version = 6. 98 | */ 99 | /*! 100 | \var WebSocketProtocol::Version::V_7 101 | hybi-07: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07. 102 | Sec-WebSocket-Version = 7 103 | */ 104 | /*! 105 | \var WebSocketProtocol::Version::V_8 106 | hybi-8, hybi-9, hybi-10, hybi-11 and hybi-12. 107 | Status codes 1005 and 1006 are added and all codes are now unsigned\n 108 | Internal error results in 1006 109 | */ 110 | /*! 111 | \var WebSocketProtocol::Version::V_13 112 | hybi-13, hybi14, hybi-15, hybi-16, hybi-17 and RFC 6455. 113 | Sec-WebSocket-Version = 13\n 114 | Status code 1004 is now reserved\n 115 | Added 1008, 1009 and 1010\n 116 | Must support TLS\n 117 | Clarify multiple version support 118 | */ 119 | /*! 120 | \var WebSocketProtocol::Version::V_LATEST 121 | Refers to the latest know version to QWebSockets. 122 | */ 123 | 124 | /*! 125 | \fn WebSocketProtocol::isOpCodeReserved(OpCode code) 126 | Checks if \a code is a valid OpCode 127 | \internal 128 | */ 129 | 130 | /*! 131 | \fn WebSocketProtocol::isCloseCodeValid(int closeCode) 132 | Checks if \a closeCode is a valid web socket close code 133 | \internal 134 | */ 135 | 136 | /*! 137 | \fn WebSocketProtocol::getCurrentVersion() 138 | Returns the latest version that WebSocket is supporting 139 | \internal 140 | */ 141 | 142 | /** 143 | * @brief Contains constants related to the WebSocket standard. 144 | */ 145 | namespace WebSocketProtocol 146 | { 147 | /*! 148 | Parses the \a versionString and converts it to a Version value 149 | \internal 150 | */ 151 | Version versionFromString(const QString &versionString) 152 | { 153 | bool ok = false; 154 | Version version = V_Unknow; 155 | int ver = versionString.toInt(&ok); 156 | QSet supportedVersions; 157 | supportedVersions << V_0 << V_4 << V_5 << V_6 << V_7 << V_8 << V_13; 158 | if (ok) 159 | { 160 | if (supportedVersions.contains(static_cast(ver))) 161 | { 162 | version = static_cast(ver); 163 | } 164 | } 165 | return version; 166 | } 167 | 168 | /*! 169 | Mask the \a payload with the given \a maskingKey and stores the result back in \a payload. 170 | \internal 171 | */ 172 | void mask(QByteArray *payload, quint32 maskingKey) 173 | { 174 | quint32 *payloadData = reinterpret_cast(payload->data()); 175 | quint32 numIterations = static_cast(payload->size()) / sizeof(quint32); 176 | quint32 remainder = static_cast(payload->size()) % sizeof(quint32); 177 | quint32 i; 178 | for (i = 0; i < numIterations; ++i) 179 | { 180 | *(payloadData + i) ^= maskingKey; 181 | } 182 | if (remainder) 183 | { 184 | const quint32 offset = i * static_cast(sizeof(quint32)); 185 | char *payloadBytes = payload->data(); 186 | uchar *mask = reinterpret_cast(&maskingKey); 187 | for (quint32 i = 0; i < remainder; ++i) 188 | { 189 | *(payloadBytes + offset + i) ^= static_cast(mask[(i + offset) % 4]); 190 | } 191 | } 192 | } 193 | 194 | /*! 195 | Masks the \a payload of length \a size with the given \a maskingKey and stores the result back in \a payload. 196 | \internal 197 | */ 198 | void mask(char *payload, quint64 size, quint32 maskingKey) 199 | { 200 | quint32 *payloadData = reinterpret_cast(payload); 201 | quint32 numIterations = static_cast(size / sizeof(quint32)); 202 | quint32 remainder = size % sizeof(quint32); 203 | quint32 i; 204 | for (i = 0; i < numIterations; ++i) 205 | { 206 | *(payloadData + i) ^= maskingKey; 207 | } 208 | if (remainder) 209 | { 210 | const quint32 offset = i * static_cast(sizeof(quint32)); 211 | uchar *mask = reinterpret_cast(&maskingKey); 212 | for (quint32 i = 0; i < remainder; ++i) 213 | { 214 | *(payload + offset + i) ^= static_cast(mask[(i + offset) % 4]); 215 | } 216 | } 217 | } 218 | } //end namespace WebSocketProtocol 219 | -------------------------------------------------------------------------------- /QtClient/source/websocketprotocol.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file websocketprotocol.h 3 | * @brief Contains constants related to the WebSocket standard. 4 | * @author Kurt Pattyn (pattyn.kurt@gmail.com) 5 | */ 6 | #ifndef WEBSOCKETPROTOCOL_H 7 | #define WEBSOCKETPROTOCOL_H 8 | 9 | class QString; 10 | class QByteArray; 11 | #include 12 | 13 | namespace WebSocketProtocol 14 | { 15 | enum Version 16 | { 17 | V_Unknow = -1, 18 | V_0 = 0, 19 | //hybi-01, hybi-02 and hybi-03 not supported 20 | V_4 = 4, 21 | V_5 = 5, 22 | V_6 = 6, 23 | V_7 = 7, 24 | V_8 = 8, 25 | V_13 = 13, 26 | V_LATEST = V_13 27 | }; 28 | 29 | Version versionFromString(const QString &versionString); 30 | 31 | enum CloseCode 32 | { 33 | CC_NORMAL = 1000, 34 | CC_GOING_AWAY = 1001, 35 | CC_PROTOCOL_ERROR = 1002, 36 | CC_DATATYPE_NOT_SUPPORTED = 1003, 37 | CC_RESERVED_1004 = 1004, 38 | CC_MISSING_STATUS_CODE = 1005, 39 | CC_ABNORMAL_DISCONNECTION = 1006, 40 | CC_WRONG_DATATYPE = 1007, 41 | CC_POLICY_VIOLATED = 1008, 42 | CC_TOO_MUCH_DATA = 1009, 43 | CC_MISSING_EXTENSION = 1010, 44 | CC_BAD_OPERATION = 1011, 45 | CC_TLS_HANDSHAKE_FAILED = 1015 46 | }; 47 | 48 | enum OpCode 49 | { 50 | OC_CONTINUE = 0x0, 51 | OC_TEXT = 0x1, 52 | OC_BINARY = 0x2, 53 | OC_RESERVED_3 = 0x3, 54 | OC_RESERVED_4 = 0x4, 55 | OC_RESERVED_5 = 0x5, 56 | OC_RESERVED_6 = 0x6, 57 | OC_RESERVED_7 = 0x7, 58 | OC_CLOSE = 0x8, 59 | OC_PING = 0x9, 60 | OC_PONG = 0xA, 61 | OC_RESERVED_B = 0xB, 62 | OC_RESERVED_V = 0xC, 63 | OC_RESERVED_D = 0xD, 64 | OC_RESERVED_E = 0xE, 65 | OC_RESERVED_F = 0xF 66 | }; 67 | 68 | 69 | inline bool isOpCodeReserved(OpCode code) 70 | { 71 | return ((code > OC_BINARY) && (code < OC_CLOSE)) || (code > OC_PONG); 72 | } 73 | inline bool isCloseCodeValid(int closeCode) 74 | { 75 | return (closeCode > 999) && (closeCode < 5000) && 76 | (closeCode != CC_RESERVED_1004) && //see RFC6455 7.4.1 77 | (closeCode != CC_MISSING_STATUS_CODE) && 78 | (closeCode != CC_ABNORMAL_DISCONNECTION) && 79 | ((closeCode >= 3000) || (closeCode < 1012)); 80 | } 81 | 82 | void mask(QByteArray *payload, quint32 maskingKey); 83 | void mask(char *payload, quint64 size, quint32 maskingKey); 84 | 85 | inline Version getCurrentVersion() { return V_LATEST; } 86 | 87 | } //end namespace WebSocketProtocol 88 | 89 | #endif // WEBSOCKETPROTOCOL_H 90 | -------------------------------------------------------------------------------- /QtClient/source/websocketserver.cpp: -------------------------------------------------------------------------------- 1 | #include "websocketserver.h" 2 | #include 3 | #include 4 | #include 5 | #include "websocketprotocol.h" 6 | #include "handshakerequest.h" 7 | #include "handshakeresponse.h" 8 | #include "websocket.h" 9 | 10 | /*! 11 | \class WebSocketServer 12 | 13 | The WebSocketServer class provides a websocket-based server. 14 | It is modeled after QTcpServer, and behaves the same. So, if you know how to use QTcpServer, you know how to use WebSocketServer.\n 15 | This class makes it possible to accept incoming websocket connections.\n 16 | You can specify the port or have WebSocketServer pick one automatically.\n 17 | You can listen on a specific address or on all the machine's addresses.\n 18 | Call listen() to have the server listen for incoming connections.\n 19 | 20 | The newConnection() signal is then emitted each time a client connects to the server.\n 21 | Call nextPendingConnection() to accept the pending connection as a connected WebSocket. 22 | The function returns a pointer to a WebSocket in QAbstractSocket::ConnectedState that you can use for communicating with the client.\n 23 | If an error occurs, serverError() returns the type of error, and errorString() can be called to get a human readable description of what happened.\n 24 | When listening for connections, the address and port on which the server is listening are available as serverAddress() and serverPort().\n 25 | Calling close() makes WebSocketServer stop listening for incoming connections.\n 26 | Although WebSocketServer is mostly designed for use with an event loop, it's possible to use it without one. In that case, you must use waitForNewConnection(), which blocks until either a connection is available or a timeout expires. 27 | 28 | \ref echoserver 29 | 30 | \author Kurt Pattyn (pattyn.kurt@gmail.com) 31 | 32 | \sa WebSocket 33 | */ 34 | 35 | /*! 36 | \page echoserver WebSocket server example 37 | \brief A sample websocket server echoing back messages sent to it. 38 | 39 | \section Description 40 | The echoserver example implements a web socket server that echoes back everything that is sent to it. 41 | \section Code 42 | We start by creating a WebSocketServer (`new WebSocketServer()`). After the creation, we listen on all local network interfaces (`QHostAddress::Any`) on the specified \a port. 43 | \snippet echoserver.cpp constructor 44 | If listening is successful, we connect the `newConnection()` signal to the slot `onNewConnection()`. 45 | The `newConnection()` signal will be thrown whenever a new web socket client is connected to our server. 46 | 47 | \snippet echoserver.cpp onNewConnection 48 | When a new connection is received, the client WebSocket is retrieved (`nextPendingConnection()`), and the signals we are interested in 49 | are connected to our slots (`textMessageReceived()`, `binaryMessageReceived()` and `disconnected()`). 50 | The client socket is remembered in a list, in case we would like to use it later (in this example, nothing is done with it). 51 | 52 | \snippet echoserver.cpp processMessage 53 | Whenever `processMessage()` is triggered, we retrieve the sender, and if valid, send back the original message (`send()`). 54 | The same is done with binary messages. 55 | \snippet echoserver.cpp processBinaryMessage 56 | The only difference is that the message now is a QByteArray instead of a QString. 57 | 58 | \snippet echoserver.cpp socketDisconnected 59 | Whenever a socket is disconnected, we remove it from the clients list and delete the socket. 60 | Note: it is best to use `deleteLater()` to delete the socket. 61 | */ 62 | 63 | /*! 64 | \fn void WebSocketServer::newConnection() 65 | This signal is emitted every time a new connection is available. 66 | 67 | \sa hasPendingConnections() and nextPendingConnection(). 68 | */ 69 | 70 | /*! 71 | Constructs a new WebSocketServer. 72 | 73 | \a parent is passed to the QObject constructor. 74 | */ 75 | WebSocketServer::WebSocketServer(QObject *parent) : 76 | QObject(parent), 77 | m_pTcpServer(0), 78 | m_pendingConnections() 79 | { 80 | m_pTcpServer = new QTcpServer(this); 81 | connect(m_pTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); 82 | } 83 | 84 | /*! 85 | Destroys the WebSocketServer object. If the server is listening for connections, the socket is automatically closed. 86 | Any client WebSockets that are still connected are closed and deleted. 87 | 88 | \sa close() 89 | */ 90 | WebSocketServer::~WebSocketServer() 91 | { 92 | while (!m_pendingConnections.isEmpty()) 93 | { 94 | WebSocket *pWebSocket = m_pendingConnections.dequeue(); 95 | pWebSocket->close(WebSocketProtocol::CC_GOING_AWAY, "Server closed."); 96 | pWebSocket->deleteLater(); 97 | } 98 | m_pTcpServer->deleteLater(); 99 | } 100 | 101 | /*! 102 | Closes the server. The server will no longer listen for incoming connections. 103 | */ 104 | void WebSocketServer::close() 105 | { 106 | m_pTcpServer->close(); 107 | } 108 | 109 | /*! 110 | Returns a human readable description of the last error that occurred. 111 | 112 | \sa serverError(). 113 | */ 114 | QString WebSocketServer::errorString() const 115 | { 116 | return m_pTcpServer->errorString(); 117 | } 118 | 119 | /*! 120 | Returns true if the server has pending connections; otherwise returns false. 121 | 122 | \sa nextPendingConnection() and setMaxPendingConnections(). 123 | */ 124 | bool WebSocketServer::hasPendingConnections() const 125 | { 126 | return !m_pendingConnections.isEmpty(); 127 | } 128 | 129 | /*! 130 | Returns true if the server is currently listening for incoming connections; otherwise returns false. 131 | 132 | \sa listen(). 133 | */ 134 | bool WebSocketServer::isListening() const 135 | { 136 | return m_pTcpServer->isListening(); 137 | } 138 | 139 | /*! 140 | Tells the server to listen for incoming connections on address \a address and port \a port. 141 | If \a port is 0, a port is chosen automatically.\n 142 | If \a address is QHostAddress::Any, the server will listen on all network interfaces. 143 | 144 | Returns true on success; otherwise returns false. 145 | 146 | \sa isListening(). 147 | */ 148 | bool WebSocketServer::listen(const QHostAddress &address, quint16 port) 149 | { 150 | return m_pTcpServer->listen(address, port); 151 | } 152 | 153 | /*! 154 | Returns the maximum number of pending accepted connections. The default is 30. 155 | 156 | \sa setMaxPendingConnections() and hasPendingConnections(). 157 | */ 158 | int WebSocketServer::maxPendingConnections() const 159 | { 160 | return m_pTcpServer->maxPendingConnections(); 161 | } 162 | 163 | /*! 164 | This function is called to add the socket to the list of pending incoming websocket connections. 165 | 166 | \sa nextPendingConnection() and hasPendingConnections() 167 | */ 168 | void WebSocketServer::addPendingConnection(WebSocket *pWebSocket) 169 | { 170 | if (m_pendingConnections.size() < maxPendingConnections()) 171 | { 172 | m_pendingConnections.enqueue(pWebSocket); 173 | } 174 | } 175 | 176 | /*! 177 | Returns the next pending connection as a connected WebSocket object. 178 | The socket is created as a child of the server, which means that it is automatically deleted when the WebSocketServer object is destroyed. It is still a good idea to delete the object explicitly when you are done with it, to avoid wasting memory. 179 | 0 is returned if this function is called when there are no pending connections. 180 | 181 | Note: The returned WebSocket object cannot be used from another thread.. 182 | 183 | \sa hasPendingConnections(). 184 | */ 185 | WebSocket *WebSocketServer::nextPendingConnection() 186 | { 187 | WebSocket *pWebSocket = 0; 188 | if (!m_pendingConnections.isEmpty()) 189 | { 190 | pWebSocket = m_pendingConnections.dequeue(); 191 | } 192 | return pWebSocket; 193 | } 194 | 195 | /*! 196 | Returns the network proxy for this socket. By default QNetworkProxy::DefaultProxy is used. 197 | 198 | \sa setProxy(). 199 | */ 200 | QNetworkProxy WebSocketServer::proxy() const 201 | { 202 | return m_pTcpServer->proxy(); 203 | } 204 | 205 | /*! 206 | Returns the server's address if the server is listening for connections; otherwise returns QHostAddress::Null. 207 | 208 | \sa serverPort() and listen(). 209 | */ 210 | QHostAddress WebSocketServer::serverAddress() const 211 | { 212 | return m_pTcpServer->serverAddress(); 213 | } 214 | 215 | /*! 216 | Returns an error code for the last error that occurred. 217 | \sa errorString(). 218 | */ 219 | QAbstractSocket::SocketError WebSocketServer::serverError() const 220 | { 221 | return m_pTcpServer->serverError(); 222 | } 223 | 224 | /*! 225 | Returns the server's port if the server is listening for connections; otherwise returns 0. 226 | \sa serverAddress() and listen(). 227 | */ 228 | quint16 WebSocketServer::serverPort() const 229 | { 230 | return m_pTcpServer->serverPort(); 231 | } 232 | 233 | /*! 234 | Sets the maximum number of pending accepted connections to \a numConnections. 235 | WebSocketServer will accept no more than \a numConnections incoming connections before nextPendingConnection() is called.\n 236 | By default, the limit is 30 pending connections. 237 | 238 | Clients may still able to connect after the server has reached its maximum number of pending connections (i.e., WebSocket can still emit the connected() signal). WebSocketServer will stop accepting the new connections, but the operating system may still keep them in queue. 239 | \sa maxPendingConnections() and hasPendingConnections(). 240 | */ 241 | void WebSocketServer::setMaxPendingConnections(int numConnections) 242 | { 243 | m_pTcpServer->setMaxPendingConnections(numConnections); 244 | } 245 | 246 | /*! 247 | \brief Sets the explicit network proxy for this socket to \a networkProxy. 248 | 249 | To disable the use of a proxy for this socket, use the QNetworkProxy::NoProxy proxy type: 250 | 251 | \code 252 | server->setProxy(QNetworkProxy::NoProxy); 253 | \endcode 254 | 255 | \sa proxy(). 256 | */ 257 | void WebSocketServer::setProxy(const QNetworkProxy &networkProxy) 258 | { 259 | m_pTcpServer->setProxy(networkProxy); 260 | } 261 | 262 | /*! 263 | Sets the socket descriptor this server should use when listening for incoming connections to \a socketDescriptor. 264 | 265 | Returns true if the socket is set successfully; otherwise returns false.\n 266 | The socket is assumed to be in listening state. 267 | 268 | \sa socketDescriptor() and isListening(). 269 | */ 270 | bool WebSocketServer::setSocketDescriptor(int socketDescriptor) 271 | { 272 | return m_pTcpServer->setSocketDescriptor(socketDescriptor); 273 | } 274 | 275 | /*! 276 | Returns the native socket descriptor the server uses to listen for incoming instructions, or -1 if the server is not listening. 277 | If the server is using QNetworkProxy, the returned descriptor may not be usable with native socket functions. 278 | 279 | \sa setSocketDescriptor() and isListening(). 280 | */ 281 | int WebSocketServer::socketDescriptor() const 282 | { 283 | return m_pTcpServer->socketDescriptor(); 284 | } 285 | 286 | /*! 287 | Waits for at most \a msec milliseconds or until an incoming connection is available. 288 | Returns true if a connection is available; otherwise returns false. 289 | If the operation timed out and \a timedOut is not 0, \a *timedOut will be set to true. 290 | 291 | \note This is a blocking function call. 292 | \note Its use is disadvised in a single-threaded GUI application, since the whole application will stop responding until the function returns. waitForNewConnection() is mostly useful when there is no event loop available. 293 | \note The non-blocking alternative is to connect to the newConnection() signal. 294 | 295 | If \a msec is -1, this function will not time out. 296 | 297 | \sa hasPendingConnections() and nextPendingConnection(). 298 | */ 299 | bool WebSocketServer::waitForNewConnection(int msec, bool *timedOut) 300 | { 301 | return m_pTcpServer->waitForNewConnection(msec, timedOut); 302 | } 303 | 304 | /*! 305 | Returns a list of websocket versions that this server is supporting. 306 | */ 307 | QList WebSocketServer::getSupportedVersions() const 308 | { 309 | QList supportedVersions; 310 | supportedVersions << WebSocketProtocol::getCurrentVersion(); //we only support V13 311 | return supportedVersions; 312 | } 313 | 314 | /*! 315 | Returns a list of websocket subprotocols that this server supports. 316 | */ 317 | QList WebSocketServer::getSupportedProtocols() const 318 | { 319 | QList supportedProtocols; 320 | return supportedProtocols; //no protocols are currently supported 321 | } 322 | 323 | /*! 324 | Returns a list of websocket extensions that this server supports. 325 | */ 326 | QList WebSocketServer::getSupportedExtensions() const 327 | { 328 | QList supportedExtensions; 329 | return supportedExtensions; //no extensions are currently supported 330 | } 331 | 332 | void WebSocketServer::onNewConnection() 333 | { 334 | QTcpSocket *pTcpSocket = m_pTcpServer->nextPendingConnection(); 335 | connect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); 336 | } 337 | 338 | void WebSocketServer::onCloseConnection() 339 | { 340 | QTcpSocket *pTcpSocket = qobject_cast(sender()); 341 | if (pTcpSocket != 0) 342 | { 343 | pTcpSocket->close(); 344 | } 345 | } 346 | 347 | void WebSocketServer::handshakeReceived() 348 | { 349 | QTcpSocket *pTcpSocket = qobject_cast(sender()); 350 | if (pTcpSocket != 0) 351 | { 352 | bool success = false; 353 | bool isSecure = false; 354 | HandshakeRequest request(pTcpSocket->peerPort(), isSecure); 355 | QTextStream textStream(pTcpSocket); 356 | textStream >> request; 357 | 358 | HandshakeResponse response(request, 359 | getSupportedVersions(), 360 | getSupportedProtocols(), 361 | getSupportedExtensions()); 362 | disconnect(pTcpSocket, SIGNAL(readyRead()), this, SLOT(handshakeReceived())); 363 | 364 | if (response.isValid()) 365 | { 366 | QTextStream httpStream(pTcpSocket); 367 | httpStream << response; 368 | httpStream.flush(); 369 | 370 | if (response.canUpgrade()) 371 | { 372 | WebSocket *pWebSocket = WebSocket::upgradeFrom(pTcpSocket, request, response); 373 | if (pWebSocket) 374 | { 375 | pWebSocket->setParent(this); 376 | addPendingConnection(pWebSocket); 377 | Q_EMIT newConnection(); 378 | success = true; 379 | } 380 | else 381 | { 382 | qDebug() << "WebSocketServer::dataReceived: Upgrading to WebSocket failed."; 383 | } 384 | } 385 | else 386 | { 387 | qDebug() << "WebSocketServer::dataReceived: Cannot upgrade to websocket."; 388 | } 389 | } 390 | else 391 | { 392 | qDebug() << "WebSocketServer::dataReceived: Invalid response. This should not happen!!!"; 393 | } 394 | if (!success) 395 | { 396 | qDebug() << "WebSocketServer::dataReceived: Closing socket because of invalid or unsupported request"; 397 | pTcpSocket->close(); 398 | } 399 | } 400 | else 401 | { 402 | qDebug() << "WebSocketServerImp::dataReceived: Sender socket is NULL. This should not happen!!!"; 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /QtClient/source/websocketserver.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file websocketserver.h 3 | * @author Kurt Pattyn (pattyn.kurt@gmail.com) 4 | * @brief Defines the WebSocketServer class. 5 | */ 6 | 7 | #ifndef WEBSOCKETSERVER_H 8 | #define WEBSOCKETSERVER_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "websocket.h" 15 | 16 | class QTcpServer; 17 | 18 | class WebSocketServerImp; 19 | 20 | class WebSocketServer : public QObject 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | explicit WebSocketServer(QObject *parent = 0); 26 | virtual ~WebSocketServer(); 27 | 28 | void close(); 29 | QString errorString() const; 30 | bool hasPendingConnections() const; 31 | bool isListening() const; 32 | bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); 33 | int maxPendingConnections() const; 34 | virtual WebSocket *nextPendingConnection(); 35 | QNetworkProxy proxy() const; 36 | QHostAddress serverAddress() const; 37 | QAbstractSocket::SocketError serverError() const; 38 | quint16 serverPort() const; 39 | void setMaxPendingConnections(int numConnections); 40 | void setProxy(const QNetworkProxy &networkProxy); 41 | bool setSocketDescriptor(int socketDescriptor); 42 | int socketDescriptor() const; 43 | bool waitForNewConnection(int msec = 0, bool *timedOut = 0); 44 | 45 | QList getSupportedVersions() const; 46 | QList getSupportedProtocols() const; 47 | QList getSupportedExtensions() const; 48 | 49 | Q_SIGNALS: 50 | void newConnection(); 51 | 52 | private Q_SLOTS: 53 | void onNewConnection(); 54 | void onCloseConnection(); 55 | void handshakeReceived(); 56 | 57 | private: 58 | Q_DISABLE_COPY(WebSocketServer) 59 | 60 | QTcpServer *m_pTcpServer; 61 | QQueue m_pendingConnections; 62 | 63 | void addPendingConnection(WebSocket *pWebSocket); 64 | }; 65 | 66 | #endif // WEBSOCKETSERVER_H 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QtWebsocket 2 | 3 | ### 服务端请安装php,打上swoole扩展使用 4 | --------------------------------------------------------------------------------