websocket应用

<!doctype html>websocket应用.md

html { }
.CodeMirror { height: auto }
.CodeMirror-scroll { }
.CodeMirror-lines { padding: 4px 0px }
.CodeMirror pre { }
.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler { background-color: white }
.CodeMirror-gutters { border-right: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247); white-space: nowrap }
.CodeMirror-linenumbers { }
.CodeMirror-linenumber { padding: 0px 3px 0px 5px; text-align: right; color: rgb(153, 153, 153) }
.CodeMirror div.CodeMirror-cursor { border-left: 1px solid black; z-index: 3 }
.CodeMirror div.CodeMirror-secondarycursor { border-left: 1px solid silver }
.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { width: auto; border: 0px; background: rgb(119, 238, 119); z-index: 1 }
.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite { }
.cm-tab { display: inline-block }
.cm-s-typora-default .cm-header,.cm-s-typora-default .cm-property { color: rgb(217, 79, 138) }
.cm-s-typora-default pre.cm-header1:not(.cm-atom) :not(.cm-overlay) { font-size: 2rem; line-height: 2rem }
.cm-s-typora-default pre.cm-header2:not(.cm-atom) :not(.cm-overlay) { font-size: 1.4rem; line-height: 1.4rem }
.cm-s-typora-default .cm-atom,.cm-s-typora-default .cm-number { color: rgb(149, 132, 134) }
.cm-s-typora-default .cm-table-row,.cm-s-typora-default .cm-block-start { font-family: monospace }
.cm-s-typora-default .cm-comment,.cm-s-typora-default .cm-code { color: rgb(74, 90, 159); font-family: monospace }
.cm-s-typora-default .cm-tag { color: rgb(169, 68, 66) }
.cm-s-typora-default .cm-string { color: rgb(126, 134, 169) }
.cm-s-typora-default .cm-link { color: rgb(196, 122, 15); text-decoration: underline }
.cm-s-typora-default .cm-variable-2,.cm-s-typora-default .cm-variable-1 { color: inherit }
.cm-s-typora-default .cm-overlay { font-family: monospace }
.CodeMirror.cm-s-typora-default div.CodeMirror-cursor { border-left: 3px solid rgb(228, 98, 154) }
.cm-s-typora-default .CodeMirror-activeline-background { left: -60px; right: -30px; background: rgba(204, 204, 204, 0.2) }
.cm-s-typora-default .CodeMirror-gutters { border-right: none; background-color: inherit }
.cm-s-typora-default .cm-trailing-space-new-line::after,.cm-startspace::after,.cm-starttab .cm-tab::after { content: "?"; position: absolute; left: 0px; opacity: 0; font-family: LetterGothicStd, monospace }
.os-windows .cm-startspace::after,.os-windows .cm-starttab .cm-tab::after { left: -0.1em }
.cm-starttab .cm-tab::after { content: " " }
.cm-startspace,.cm-tab,.cm-starttab,.cm-trailing-space-a,.cm-trailing-space-b,.cm-trailing-space-new-line { font-family: monospace; position: relative }
.cm-s-typora-default .cm-trailing-space-new-line::after { content: "↓"; opacity: 0.3 }
.cm-s-inner .cm-keyword { color: rgb(119, 0, 136) }
.cm-s-inner .cm-atom,.cm-s-inner.cm-atom { color: rgb(34, 17, 153) }
.cm-s-inner .cm-number { color: rgb(17, 102, 68) }
.cm-s-inner .cm-def { color: rgb(0, 0, 255) }
.cm-s-inner .cm-variable { color: black }
.cm-s-inner .cm-variable-2 { color: rgb(0, 85, 170) }
.cm-s-inner .cm-variable-3 { color: rgb(0, 136, 85) }
.cm-s-inner .cm-property { color: black }
.cm-s-inner .cm-operator { color: rgb(152, 26, 26) }
.cm-s-inner .cm-comment,.cm-s-inner.cm-comment { color: rgb(170, 85, 0) }
.cm-s-inner .cm-string { color: rgb(170, 17, 17) }
.cm-s-inner .cm-string-2 { color: rgb(255, 85, 0) }
.cm-s-inner .cm-meta { color: rgb(85, 85, 85) }
.cm-s-inner .cm-qualifier { color: rgb(85, 85, 85) }
.cm-s-inner .cm-builtin { color: rgb(51, 0, 170) }
.cm-s-inner .cm-bracket { color: rgb(153, 153, 119) }
.cm-s-inner .cm-tag { color: rgb(17, 119, 0) }
.cm-s-inner .cm-attribute { color: rgb(0, 0, 204) }
.cm-s-inner .cm-header,.cm-s-inner.cm-header { color: blue }
.cm-s-inner .cm-quote,.cm-s-inner.cm-quote { color: rgb(0, 153, 0) }
.cm-s-inner .cm-hr,.cm-s-inner.cm-hr { color: rgb(153, 153, 153) }
.cm-s-inner .cm-link,.cm-s-inner.cm-link { color: rgb(0, 0, 204) }
.cm-negative { color: rgb(221, 68, 68) }
.cm-positive { color: rgb(34, 153, 34) }
.cm-header,.cm-strong { font-weight: bold }
.cm-del { text-decoration: line-through }
.cm-em { font-style: italic }
.cm-link { text-decoration: underline }
.cm-error { color: rgb(255, 0, 0) }
.cm-invalidchar { color: rgb(255, 0, 0) }
.cm-constant { color: rgb(38, 139, 210) }
.cm-defined { color: rgb(181, 137, 0) }
div.CodeMirror span.CodeMirror-matchingbracket { color: rgb(0, 255, 0) }
div.CodeMirror span.CodeMirror-nonmatchingbracket { color: rgb(255, 34, 34) }
.cm-s-inner .CodeMirror-activeline-background { background: inherit }
.CodeMirror { position: relative; overflow: hidden }
.CodeMirror-scroll { margin-bottom: -30px; margin-right: -30px; padding-bottom: 30px; padding-right: 30px; height: 100%; outline: none; position: relative }
.CodeMirror-sizer { position: relative }
.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler { position: absolute; z-index: 6; display: none }
.CodeMirror-vscrollbar { right: 0px; top: 0px }
.CodeMirror-hscrollbar { bottom: 0px; left: 0px }
.CodeMirror-scrollbar-filler { right: 0px; bottom: 0px }
.CodeMirror-gutter-filler { left: 0px; bottom: 0px }
.CodeMirror-gutters { position: absolute; left: 0px; top: 0px; padding-bottom: 30px; z-index: 3 }
.CodeMirror-gutter { white-space: normal; height: 100%; padding-bottom: 30px; margin-bottom: -32px; display: inline-block }
.CodeMirror-gutter-wrapper { position: absolute; z-index: 4; background: none !important; border: none !important }
.CodeMirror-gutter-background { position: absolute; top: 0px; bottom: 0px; z-index: 4 }
.CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4 }
.CodeMirror-lines { cursor: text }
.CodeMirror pre { border-width: 0px; background: transparent; font-family: inherit; font-size: inherit; margin: 0px; white-space: pre; color: inherit; z-index: 2; position: relative; overflow: visible }
.CodeMirror-wrap pre { white-space: pre-wrap }
.CodeMirror-code pre { border-right: 30px solid transparent; width: fit-content }
.CodeMirror-wrap .CodeMirror-code pre { border-right: none; width: auto }
.CodeMirror-linebackground { position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 0 }
.CodeMirror-linewidget { position: relative; z-index: 2; overflow: auto }
.CodeMirror-widget { }
.CodeMirror-wrap .CodeMirror-scroll { }
.CodeMirror-measure { position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden }
.CodeMirror-measure pre { position: static }
.CodeMirror div.CodeMirror-cursor { position: absolute; visibility: hidden; border-right: none; width: 0px }
.CodeMirror div.CodeMirror-cursor { visibility: hidden }
.CodeMirror-focused div.CodeMirror-cursor { visibility: inherit }
.CodeMirror-selected { background: rgb(217, 217, 217) }
.CodeMirror-focused .CodeMirror-selected { background: rgb(215, 212, 240) }
.cm-searching { background: rgba(255, 255, 0, 0.4) }
.CodeMirror span { }
.CodeMirror-lint-markers { width: 16px }
.CodeMirror-lint-tooltip { background-color: infobackground; border: 1px solid black; color: infotext; font-family: monospace; overflow: hidden; padding: 2px 5px; position: fixed; white-space: pre-wrap; z-index: 10000; max-width: 600px; opacity: 0; font-size: 0.8em }
.CodeMirror-lint-mark-error,.CodeMirror-lint-mark-warning { background-position: left bottom; background-repeat: repeat-x }
.CodeMirror-lint-mark-error { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") }
.CodeMirror-lint-marker-error,.CodeMirror-lint-marker-warning { background-position: center center; background-repeat: no-repeat; cursor: pointer; display: inline-block; height: 16px; width: 16px; vertical-align: middle; position: relative }
.CodeMirror-lint-message-error,.CodeMirror-lint-message-warning { padding-left: 18px; background-position: left top; background-repeat: no-repeat }
.CodeMirror-lint-marker-error,.CodeMirror-lint-message-error { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=") }
.CodeMirror-lint-marker-warning,.CodeMirror-lint-message-warning { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=") }
.CodeMirror-lint-marker-multiple { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); background-repeat: no-repeat; background-position: right bottom; width: 100%; height: 100% }
:root { }
html { font-size: 14px; background-color: var(--bg-color); color: var(--text-color); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif }
body { margin: 0px; padding: 0px; height: auto; bottom: 0px; top: 0px; left: 0px; right: 0px; font-size: 1rem; line-height: 1.42857; background: inherit }
a:active,a:hover { outline: 0px }
.in-text-selection,::selection { background: rgb(181, 214, 252) }
#write { margin: 0px auto; height: auto; width: inherit; position: relative; padding-bottom: 70px; white-space: pre-wrap }
.for-image #write { padding-left: 8px; padding-right: 8px }
body.typora-export { padding-left: 30px; padding-right: 30px }
.typora-export #write { margin: 0px auto }
#write>p:first-child,#write>ul:first-child,#write>ol:first-child,#write>pre:first-child,#write>blockquote:first-child,#write>div:first-child,#write>table:first-child { margin-top: 30px }
#write li>table:first-child { margin-top: -20px }
img { max-width: 100%; vertical-align: middle }
input,button,select,textarea { color: inherit; font-style: inherit; font-variant: inherit; font-weight: inherit; font-size: inherit; line-height: inherit; font-family: inherit }
input[type="checkbox"],input[type="radio"] { line-height: normal; padding: 0px }
::before,::after,* { }
#write p,#write h1,#write h2,#write h3,#write h4,#write h5,#write h6,#write div,#write pre { width: inherit }
#write p,#write h1,#write h2,#write h3,#write h4,#write h5,#write h6 { position: relative }
h1 { font-size: 2rem }
h2 { font-size: 1.8rem }
h3 { font-size: 1.6rem }
h4 { font-size: 1.4rem }
h5 { font-size: 1.2rem }
h6 { font-size: 1rem }
p { }
.typora-export p { white-space: normal }
.mathjax-block { margin-top: 0px; margin-bottom: 0px }
.hidden { display: none }
.md-blockmeta { color: rgb(204, 204, 204); font-weight: bold; font-style: italic }
a { cursor: pointer }
sup.md-footnote { padding: 2px 4px; background-color: rgba(238, 238, 238, 0.7); color: rgb(85, 85, 85) }
#write input[type="checkbox"] { cursor: pointer; width: inherit; height: inherit; margin: 4px 0px 0px }
tr { }
thead { display: table-header-group }
table { border-collapse: collapse; border-spacing: 0px; width: 100%; overflow: auto; text-align: left }
table.md-table td { min-width: 80px }
.CodeMirror-gutters { border-right: 0px; background-color: inherit }
.CodeMirror { text-align: left }
.CodeMirror-placeholder { opacity: 0.3 }
.CodeMirror pre { padding: 0px 4px }
.CodeMirror-lines { padding: 0px }
div.hr:focus { cursor: none }
pre { white-space: pre-wrap }
.CodeMirror-gutters { margin-right: 4px }
.md-fences { font-size: 0.9rem; display: block; text-align: left; overflow: visible; white-space: pre; background: var(--code-block-bg-color); position: relative !important }
.md-diagram-panel { width: 100%; margin-top: 10px; text-align: center; padding-top: 0px; padding-bottom: 8px }
.md-fences .CodeMirror.CodeMirror-wrap { top: -1.6em; margin-bottom: -1.6em }
.md-fences.mock-cm { white-space: pre-wrap }
.show-fences-line-number .md-fences { padding-left: 0px }
.show-fences-line-number .md-fences.mock-cm { padding-left: 40px }
.footnotes { opacity: 0.8; font-size: 0.9rem; padding-top: 1em; padding-bottom: 1em }
.footnotes+.footnotes { margin-top: -1em }
.md-reset { margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: top; background: transparent; text-decoration: none; float: none; position: static; width: auto; height: auto; white-space: nowrap; cursor: inherit; line-height: normal; font-weight: normal; text-align: left; direction: ltr }
li div { padding-top: 0px }
blockquote { margin: 1rem 0px }
li p,li .mathjax-block { margin: 0.5rem 0px }
li { margin: 0px; position: relative }
blockquote>:last-child { margin-bottom: 0px }
blockquote>:first-child { margin-top: 0px }
.footnotes-area { color: rgb(136, 136, 136); margin-top: 0.714rem; padding-bottom: 0.143rem }
.footnote-line { margin-top: 0.714em; font-size: 0.7em }
a img,img a { cursor: pointer }
pre.md-meta-block { font-size: 0.8rem; min-height: 2.86rem; white-space: pre-wrap; background: rgb(204, 204, 204); display: block }
p .md-image:only-child { display: inline-block; width: 100%; text-align: center }
#write .MathJax_Display { margin: 0.8em 0px 0px }
.mathjax-block { white-space: pre; overflow: hidden; width: 100% }
p+.mathjax-block { margin-top: -1.143rem }
.mathjax-block:not(:empty)::after { display: none }
[contenteditable="true"]:active,[contenteditable="true"]:focus { outline: none }
.task-list { list-style-type: none }
.task-list-item { position: relative; padding-left: 1em }
.task-list-item input { position: absolute; top: 0px; left: 0px }
.math { font-size: 1rem }
.md-toc { min-height: 3.58rem; position: relative; font-size: 0.9rem }
.md-toc-content { position: relative; margin-left: 0px }
.md-toc::after,.md-toc-content::after { display: none }
.md-toc-item { display: block; color: rgb(65, 131, 196) }
.md-toc-item a { text-decoration: none }
.md-toc-inner:hover { }
.md-toc-inner { display: inline-block; cursor: pointer }
.md-toc-h1 .md-toc-inner { margin-left: 0px; font-weight: bold }
.md-toc-h2 .md-toc-inner { margin-left: 2em }
.md-toc-h3 .md-toc-inner { margin-left: 4em }
.md-toc-h4 .md-toc-inner { margin-left: 6em }
.md-toc-h5 .md-toc-inner { margin-left: 8em }
.md-toc-h6 .md-toc-inner { margin-left: 10em }
a.md-toc-inner { font-size: inherit; font-style: inherit; font-weight: inherit; line-height: inherit }
.footnote-line a:not(.reversefootnote) { color: inherit }
.md-attr { display: none }
.md-fn-count::after { content: "." }
.md-tag { opacity: 0.5 }
.md-comment { color: rgb(162, 127, 3); opacity: 0.8; font-family: monospace }
code { text-align: left }
h1 .md-tag,h2 .md-tag,h3 .md-tag,h4 .md-tag,h5 .md-tag,h6 .md-tag { font-weight: initial; opacity: 0.35 }
a.md-print-anchor { border-width: initial !important; border-style: none !important; border-color: initial !important; display: inline-block !important; position: absolute !important; width: 1px !important; right: 0px !important; outline: none !important; background: transparent !important; text-decoration: initial !important }
.md-inline-math .MathJax_SVG .noError { display: none !important }
.mathjax-block .MathJax_SVG_Display { text-align: center; margin: 1em 0em; position: relative; text-indent: 0px; max-width: none; max-height: none; min-height: 0px; min-width: 100%; width: auto; display: block !important }
.MathJax_SVG_Display,.md-inline-math .MathJax_SVG_Display { width: auto; margin: inherit; display: inline-block !important }
.MathJax_SVG .MJX-monospace { font-family: monospace }
.MathJax_SVG .MJX-sans-serif { font-family: sans-serif }
.MathJax_SVG { display: inline; font-style: normal; font-weight: normal; line-height: normal; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px }
.MathJax_SVG * { }
.md-diagram-panel>svg { max-width: 100% }
[lang="flow"] svg,[lang="mermaid"] svg { max-width: 100% }
:root { }
html { font-size: 16px }
body { font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; color: rgb(51, 51, 51); line-height: 1.6 }
#write { max-width: 860px; margin: 0px auto; padding: 20px 30px 100px }
#write>ul:first-child,#write>ol:first-child { margin-top: 30px }
body>:first-child { margin-top: 0px !important }
body>:last-child { margin-bottom: 0px !important }
a { color: rgb(65, 131, 196) }
h1,h2,h3,h4,h5,h6 { position: relative; margin-top: 1rem; margin-bottom: 1rem; font-weight: bold; line-height: 1.4; cursor: text }
h1:hover a.anchor,h2:hover a.anchor,h3:hover a.anchor,h4:hover a.anchor,h5:hover a.anchor,h6:hover a.anchor { text-decoration: none }
h1 tt,h1 code { font-size: inherit }
h2 tt,h2 code { font-size: inherit }
h3 tt,h3 code { font-size: inherit }
h4 tt,h4 code { font-size: inherit }
h5 tt,h5 code { font-size: inherit }
h6 tt,h6 code { font-size: inherit }
h1 { padding-bottom: 0.3em; font-size: 2.25em; line-height: 1.2; border-bottom: 1px solid rgb(238, 238, 238) }
h2 { padding-bottom: 0.3em; font-size: 1.75em; line-height: 1.225; border-bottom: 1px solid rgb(238, 238, 238) }
h3 { font-size: 1.5em; line-height: 1.43 }
h4 { font-size: 1.25em }
h5 { font-size: 1em }
h6 { font-size: 1em; color: rgb(119, 119, 119) }
p,blockquote,ul,ol,dl,table { margin: 0.8em 0px }
li>ol,li>ul { margin: 0px }
hr { height: 4px; padding: 0px; margin: 16px 0px; background-color: rgb(231, 231, 231); border-width: 0px 0px 1px; border-style: none none solid; border-top-color: initial; border-right-color: initial; border-left-color: initial; overflow: hidden; border-bottom-color: rgb(221, 221, 221) }
body>h2:first-child { margin-top: 0px; padding-top: 0px }
body>h1:first-child { margin-top: 0px; padding-top: 0px }
body>h1:first-child+h2 { margin-top: 0px; padding-top: 0px }
body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child { margin-top: 0px; padding-top: 0px }
a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6 { margin-top: 0px; padding-top: 0px }
h1 p,h2 p,h3 p,h4 p,h5 p,h6 p { margin-top: 0px }
li p.first { display: inline-block }
ul,ol { padding-left: 30px }
ul:first-child,ol:first-child { margin-top: 0px }
ul:last-child,ol:last-child { margin-bottom: 0px }
blockquote { border-left: 4px solid rgb(221, 221, 221); padding: 0px 15px; color: rgb(119, 119, 119) }
blockquote blockquote { padding-right: 0px }
table { padding: 0px }
table tr { border-top: 1px solid rgb(204, 204, 204); margin: 0px; padding: 0px }
table tr:nth-child(2n) { background-color: rgb(248, 248, 248) }
table tr th { font-weight: bold; border: 1px solid rgb(204, 204, 204); text-align: left; margin: 0px; padding: 6px 13px }
table tr td { border: 1px solid rgb(204, 204, 204); text-align: left; margin: 0px; padding: 6px 13px }
table tr th:first-child,table tr td:first-child { margin-top: 0px }
table tr th:last-child,table tr td:last-child { margin-bottom: 0px }
.CodeMirror-gutters { border-right: 1px solid rgb(221, 221, 221) }
.md-fences,code,tt { border: 1px solid rgb(221, 221, 221); background-color: rgb(248, 248, 248); font-family: Consolas, "Liberation Mono", Courier, monospace; padding: 2px 4px 0px; font-size: 0.9em }
.md-fences { margin-bottom: 15px; margin-top: 15px; padding: 8px 1em 6px }
.task-list { padding-left: 0px }
.task-list-item { padding-left: 32px }
.task-list-item input { top: 3px; left: 8px }
.md-fences { background-color: rgb(248, 248, 248) }
#write pre.md-meta-block { padding: 1rem; font-size: 85%; line-height: 1.45; background-color: rgb(247, 247, 247); border: 0px; color: rgb(119, 119, 119); margin-top: 0px !important }
.mathjax-block>.code-tooltip { bottom: 0.375rem }
#write>h3.md-focus::before { left: -1.5625rem; top: 0.375rem }
#write>h4.md-focus::before { left: -1.5625rem; top: 0.285714rem }
#write>h5.md-focus::before { left: -1.5625rem; top: 0.285714rem }
#write>h6.md-focus::before { left: -1.5625rem; top: 0.285714rem }
.md-image>.md-meta { border: 1px solid rgb(221, 221, 221); font-family: Consolas, "Liberation Mono", Courier, monospace; padding: 2px 4px 0px; font-size: 0.9em; color: inherit }
.md-tag { color: inherit }
.md-toc { margin-top: 20px; padding-bottom: 20px }
.sidebar-tabs { border-bottom: none }
#typora-quick-open { border: 1px solid rgb(221, 221, 221); background-color: rgb(248, 248, 248) }
#typora-quick-open-item { background-color: rgb(250, 250, 250); border-color: rgb(254, 254, 254) rgb(229, 229, 229) rgb(229, 229, 229) rgb(238, 238, 238); border-style: solid; border-width: 1px }
#md-notification::before { top: 10px }
.on-focus-mode blockquote { border-left-color: rgba(85, 85, 85, 0.12) }
header,.context-menu,.megamenu-content,footer { font-family: "Segoe UI", Arial, sans-serif }
.file-node-content:hover .file-node-icon,.file-node-content:hover .file-node-open-state { visibility: visible }
.mac-seamless-mode #typora-sidebar { background-color: var(--side-bar-bg-color) }
.md-lang { color: rgb(180, 101, 77) }

websocket应用

手动实现的websocket

你所见过的websocket

你一定见过在网站中,有一个游客聊天的聊天框,比如人人影视。这个聊天框是如何实现即时通讯的呢,就是用到了websocket

你可以打开浏览器的network,会看到有个ws://xxxxx,这就代表了是websocket做的

那么什么是websocket?

websocket就是一套协议。

看名字,虽然有个websocket,但他和http协议一样,也要走socket。

不同的是:http是短连接,处理完一个请求就断开;

websocket是连上就不断开,一直不断开,属于双工通道,服务端可以主动给客户端推送消息,客户端也可以主动给服务端推送消息

当某一个客户端发送一条消息,服务端接收以后,再推送给所有的客户端,所以才会呈现出所有人都在即时通讯的效果

服务端当然就是我们写的程序了,那客户端是浏览器,所以还需要浏览器支持才行。不要以为浏览器是都支持的,如果所有人都用chrome,前端开发工程师估计就没什么工作了。还有,如果所有的浏览器都支持,腾讯的webQQ,web微信,也不会使用长轮询来做这个事了。

来看一下具体的代码实现

x
import socket
import base64
import hashlib
?
?
def get_headers(data):
    """
    将请求头格式化成字典
    :param data:
    :return:
    """
    header_dict = {}
    data = str(data, encoding=‘utf-8‘)
?
    for i in data.split(‘\r\n‘):
        print(i)
    header, body = data.split(‘\r\n\r\n‘, 1)
    header_list = header.split(‘\r\n‘)
    for i in range(0, len(header_list)):
        if i == 0:
            if len(header_list[i].split(‘ ‘)) == 3:
                header_dict[‘method‘], header_dict[‘url‘], header_dict[‘protocol‘] = header_list[i].split(‘ ‘)
        else:
            k, v = header_list[i].split(‘:‘, 1)
            header_dict[k] = v.strip()
    return header_dict
?
?
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((‘127.0.0.1‘, 8002))
sock.listen(5)
?
conn, address = sock.accept()
data = conn.recv(1024)
?
headers = get_headers(data)  # 提取请求头信息
?
?
# 对请求头中的sec-websocket-key进行加密
response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
               "Upgrade:websocket\r\n" \
               "Connection: Upgrade\r\n" \
               "Sec-WebSocket-Accept: %s\r\n" \
               "WebSocket-Location: ws://%s%s\r\n\r\n"
?
magic_string = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11‘       #固定的,魔法字符串就是这个字符串
value = headers[‘Sec-WebSocket-Key‘] + magic_string
ac = base64.b64encode(hashlib.sha1(value.encode(‘utf-8‘)).digest()) #把返回消息加密
?
?
response_str = response_tpl % (ac.decode(‘utf-8‘), headers[‘Host‘], headers[‘url‘])
# 响应【握手】信息
conn.send(bytes(response_str, encoding=‘utf-8‘))
?
info = conn.recv(8096)
?
?
#下面是对浏览器发来的消息解密的过程
payload_len = info[1] & 127
if payload_len == 126:
    extend_payload_len = info[2:4]
    mask = info[4:8]
    decoded = info[8:] # 数据
elif payload_len == 127:
    extend_payload_len = info[2:10]
    mask = info[10:14]
    decoded = info[14:]
else:
    extend_payload_len = None
    mask = info[2:6]
    decoded = info[6:]
?
bytes_list = bytearray()
for i in range(len(decoded)):       #上面解密的最终结果,就是拿到这个decode,就是浏览器发来的真实的数据(加密的)
    chunk = decoded[i] ^ mask[i % 4]    #按位异或
    bytes_list.append(chunk)
?
?
body = str(bytes_list, encoding=‘utf-8‘)
print(body)

客户端向服务端发送的请求里,有Sec-WebSocket-Key这样一个key,服务端回消息的时候,就要拿到这个key,加密后再发给浏览器,浏览器会判断自己加密后的值,与浏览器处理的是否一致,一致才能连接。加密的方式,用到一个magic_string,其实就是一段固定的字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11,加密后打包发给浏览器,浏览器验证通过后就可以通讯了,再来看看客户端:

客户端就直接用浏览器运行这个html文件就行

xxxxxxxxxx
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="dist/css/bootstrap.css">
</head>
<body>
?
    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
    </div>
    <div id="content"></div>
?
?
?
    <script type="text/javascript">
          var socket = new WebSocket("ws://127.0.0.1:8002");
            socket.onopen = function () {
                /* 与服务器端连接成功后,自动执行 */
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = "【连接成功】";
                document.getElementById(‘content‘).appendChild(newTag);
            };
            socket.onmessage = function (event) {
                /* 服务器端向客户端发送数据时,自动执行 */
                var response = event.data;
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = response;
                document.getElementById(‘content‘).appendChild(newTag);
            };
            socket.onclose = function (event) {
                /* 服务器端主动断开连接时,自动执行 */
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = "【关闭连接】";
                document.getElementById(‘content‘).appendChild(newTag);
            };
            function sendMsg() {
                var txt = document.getElementById(‘txt‘);
                socket.send(txt.value);
                txt.value = "";
            }
            function closeConn() {
                socket.close();
                var newTag = document.createElement(‘div‘);
                newTag.innerHTML = "【关闭连接】";
                document.getElementById(‘content‘).appendChild(newTag);
            }
    </script>
?
<script></script>
</body>
</html>

这里面有三个方法:

  1. 连接上后,onopen会自动执行
  2. 发消息时,onmessage自动执行
  3. 断开连接,onclose自动执行

客户端发送给服务端的数据,还有一层加密,必须通过解密才能拿到正确的消息

xxxxxxxxxx
payload_len = info[1] & 127
if payload_len == 126:
    extend_payload_len = info[2:4]
    mask = info[4:8]
    decoded = info[8:] # 数据
elif payload_len == 127:
    extend_payload_len = info[2:10]
    mask = info[10:14]
    decoded = info[14:]
else:
    extend_payload_len = None
    mask = info[2:6]
    decoded = info[6:]
?
bytes_list = bytearray()
for i in range(len(decoded)):       #上面解密的最终结果,就是拿到这个decode,就是浏览器发来的真实的数据(加密的)
    chunk = decoded[i] ^ mask[i % 4]    #按位异或
    bytes_list.append(chunk)
?
?
body = str(bytes_list, encoding=‘utf-8‘)

这段就是解密的过程,用到位运算

Django默认是不支持websocket的,虽然有个第三方的channels插件

但是tornado默认就支持

tornado实现websocket

如果用tornado,客户端不能直接用浏览器运行了,而应该是运行tornado的一个模板文件

服务端代码:

xxxxxxxxxx
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import uuid
import json
import tornado.ioloop
import tornado.web
import tornado.websocket
?
?
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(‘index.html‘)
?
?
class ChatHandler(tornado.websocket.WebSocketHandler):
    # 用户存储当前聊天室用户
    waiters = set()
    # 用于存储历时消息
    messages = []
?
    def open(self):
        """
        客户端连接成功时,自动执行
        :return:
        """
        ChatHandler.waiters.add(self)
        uid = str(uuid.uuid4())
        self.write_message(uid)
?
        # 下面这段代码是给新加入的用户,显示历史信息的
        for msg in ChatHandler.messages:
            # {‘uid‘:‘xxx‘,‘message‘:asdfasd}
            content = self.render_string(‘message.html‘, **msg)
            self.write_message(content)
?
    def on_message(self, message):
        """
        客户端连发送消息时,自动执行
        :param message:
        :return:
        """
        msg = json.loads(message)
        ChatHandler.messages.append(msg)
?
        for client in ChatHandler.waiters:
            content = client.render_string(‘message.html‘, **msg)
            client.write_message(content)
?
    def on_close(self):
        """
        客户端关闭连接时,,自动执行
        :return:
        """
        ChatHandler.waiters.remove(self)
?
?
def run():
    settings = {
        ‘template_path‘: ‘templates‘,       # 配置模板文件
        ‘static_path‘: ‘static‘,            # 配置静态文件路径
    }
    application = tornado.web.Application([         # 配置路由
        (r"/", IndexHandler),
        (r"/chat", ChatHandler),
    ], **settings)
    application.listen(8009)
    tornado.ioloop.IOLoop.instance().start()
?
?
if __name__ == "__main__":
    run()

模板文件(客户端代码):

xxxxxxxxxx
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Python聊天室</title>
</head>
<body>
    <div>
        <input type="text" id="txt"/>
        <input type="button" id="btn" value="提交" onclick="sendMsg();"/>
        <input type="button" id="close" value="关闭连接" onclick="closeConn();"/>
    </div>
    <div id="container" style="border: 1px solid #dddddd;margin: 20px;min-height: 500px;">
?
    </div>
?
    <script src="/static/jquery-3.2.1.js"></script>
    <script type="text/javascript">
        $(function () {
            wsUpdater.start();
        });
        var wsUpdater = {
            socket: null,
            uid: null,
            start: function() {
                var url = "ws://192.168.16.200:8009/chat";
                wsUpdater.socket = new WebSocket(url);
                wsUpdater.socket.onmessage = function(event) {
                    if(wsUpdater.uid){
                        wsUpdater.showMessage(event.data);
                    }else{
                        wsUpdater.uid = event.data;
                    }
                }
            },
            showMessage: function(content) {
                $(‘#container‘).append(content);
            }
        };
        function sendMsg() {
            var msg = {
                uid: wsUpdater.uid,
                message: $("#txt").val()
            };
            wsUpdater.socket.send(JSON.stringify(msg));
        }
</script>
?
</body>
</html>

原理都一样,但是用tornado实现起来,就清爽多了。

ps:再说一下腾讯的长轮询,如果你登录webQQ,或者web微信,你可以在network里面找到 pending的字样,这就是表示是使用的长轮询。

长轮询与轮询的区别就是:

轮询是过来以后看到没消息就立马去走了,但是长轮询不会立马走,而是在这等30秒(约定的时间)之后,如果一直没有消息,才返回,下一次来在等30秒,直到有消息了,这样有个缺点就是,拿到的消息并不是即时的。那腾讯这么大的公司,为什么不用性能更好的websocket呢?原因就是他是个大公司,必须要考虑兼容性,必须要保证所有的浏览器都能使用才行。

你可以从这里拿到完整 的示例代码

https://github.com/zEllis/websocket_demo

时间: 2024-10-25 20:48:56

websocket应用的相关文章

web新特性 之 WebSocket

详情参见:你真的了解WebSocket吗?     WebSocket系列教程   HTML5新特性之WebSocket WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符.它实现了浏览器与服务器全双工(full-duplex)通信.其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信. 服务端与客户端的连接不断开,实现全双工的操作.及服务端或是客户端都会给对方发送消息. WebSocke

websocket之拨云见雾

websocket是基于http相应的特性弥补其不足(就是个socket,不再是一次请求一次相应) 但缺点就是只有在版本较高的浏览器才支持websocket. 浏览器: <script type="text/javascript"> var socket = new WebSocket("ws://127.0.0.1:8002/xxoo"); ... </script> 服务端: import socket sock = socket.soc

websocket

什么是websocket? 不错的介绍:https://www.zhihu.com/question/20215561 websocket是一种持久化的协议,实现了浏览器和服务端的全双工通信: 握手阶段与HTTP协议相同,返回状态码101(switching protocols): 握手阶段完成之后,按照websocket协议进行通信 应用场景: 浏览器与服务端需要实时通信的场景,以前的解决方式:ajax轮询.HTTP长连接.Flash等. 实现: 浏览器端 const ws = new Web

websocket链接数限制

无法解决 当我需要每跳转新打开一个页面链接一次websocket的时候,第六个开始loading,关掉前面的,会加载出来,查了查.... 由于websocket属于属于持久链接 不同的浏览器针对同一服务器的持久链接的数量限制如下: chrome  6 firefox  6 Safari 5 IE11以下 不支持 这种H5的新新技术不得不说有各种安全和兼容性问题,慎重选用

java SSM框架 多数据源 代码生成器 websocket即时通讯 shiro redis 后台框架源码

获取[下载地址]   QQ: 313596790官网 http://www.fhadmin.org/A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块C 集成阿里巴巴数据库连接池druid  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都

异步通信----WebSocket

什么是WebSocket? WebSocket API是下一代客户端-服务器的异步通信方法.该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序.WebSocket目前由W3C进行标准化.WebSocket已经受到Firefox 4.Chrome 4.Opera 10.70以及Safari 5等浏览器的支持. WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息.WebSocket并不限于以Ajax(或XHR)方式通

QDjango,tufao,C++ websocket client/server

QDjango, a Qt-based C++ web frameworkhttps://github.com/jlaine/qdjango/ An asynchronous web framework for C++ built on top of Qt http://vinipsmaker.github.io/tufao/https://github.com/vinipsmaker/tufao C++ websocket client/server library http://www.za

PHP基于websocket实时通信的实现—GoEasy

PHP websocket实时消息推送 实现步骤如下: 1. 获取 GoEasy appkey. 在 goeasy 官网上注册一个账号,并新建一个 app. APP 创建好后系统会为该 app 自动生成两个 key, 一个既可以用来接收又可以用来推送 (supper key),另一个只可以用来接收(subscriber key). 2. 客户端订阅一个 channel. a. 在客户端引入 goeasy.js , js 地址: http://cdn.goeasy.io/goeasy.js 需要注

websocket实战(1) 入门

1.WebSocket介绍 1.1 概念 WebSocket是HTML5中一系列新的API,或者说新规范,新技术.支持页面上使用Web Socket协议与远程主机进行全双工的通信.它引入了WebSocket接口并且定义了一个全双工的通信通道,通过一个单一的套接字在Web上进行操作. 1.2 websocket vs HTTP 首先,web技术发展经历了以下阶段. 静态页面(html) 动态页面(cgi,j2ee,php...) Ajax技术 comet技术(轮询) 1.2.1 实现方案对比 举个

认识HTML5的WebSocket

认识HTML5的WebSocket 在HTML5规范中,我最喜欢的Web技术就是正迅速变得流行的WebSocket API.WebSocket提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术.这个新的API提供了一个方法,从客户端使用简单的语法有效地推动消息到服务器.让我们看一看HTML5的WebSocket API:它可用于客户端.服务器端.而且有一个优秀的第三方API,名为Socket.IO. 一.什么是WebSocket API? WebSocket API是下一代客户端-