├── 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 |
--------------------------------------------------------------------------------