3.x版本中如何通过WebSocket连接查看服务器版本

Access denied | www.ruanyifeng.com used Cloudflare to restrict access
Please enable cookies.
What happened?
The owner of this website (www.ruanyifeng.com) has banned your access based on your browser's signature (4cd9625-ua98).使用四种框架分别实现百万websocket常连接的服务器 - cn三少&script&&/script& - 博客园
随笔 - 402, 文章 - 1, 评论 - 18, 引用 - 0
著名的&&问题提出的时候, 正是 2001 年。这篇文章可以说是高性能服务器开发的一个标志性文档,它讨论的就是单机为1万个连接提供服务这个问题,当时因为硬件和软件的**,单机1万还是一个非常值得挑战的目标。但是时光荏苒,随着硬件和软件的飞速发展,单机1万的目标已经变成了最简单不过的事情。现在用任何一种主流语言都能提供单机1万的并发处理的能力。所以现在目标早已提高了100倍,变成C1000k,也就是一台服务器为100万连接提供服务。在2010年,2011年已经看到一些实现C1000K的文章了,所以在2015年,实现C1000K应该不是一件困难的事情。
本文是我在实践过程中的记录,我的目标是使用spran-websocket,netty, undertow和node.js四种框架分别实现C1000K的服务器,看看这几个框架实现的难以程度,性能如何。开发语言为Scala和Javascript。
当然,谈起性能,我们还必须谈到每秒每个连接有多少个请求,也就是RPS数,还要考虑每条消息的大小。一般来说,我们会选取一个百分比,比如每秒20%的连接会收发消息。我的需求是服务器只是push,客户端不会主动发送消息。 一般每一分钟会为这一百万群发一条消息。所以实现的测试工具每个client建立60000个websocket连接,一共二十个client。实际不可能使用20台机器,我使用了两台AWS C3.2xlarge(8核16G)服务器作为客户端机。每台机器10个客户端。服务器每1分钟群发一条消息。消息内容很简单,只是服务器的当天时间。
最近看到360用Go实现的消息推送系统,下面是他们的数据:
目前360消息推送系统服务于50+内部产品,万款开发平台App,实时长连接数亿量级,日独数十亿量级,1分钟内可以实现亿量级广播,日下发峰值百亿量级,400台物理机,3000多个实例分布在9个独立集群中,每个集群跨国内外近10个IDC。
四个服务器的代码和Client测试工具代码可以在上下载。&(其实不止四种框架了,现在包括Netty, Undertow, Jetty, Spray-websocket, Vert.x, Grizzly 和 Node.js 七种框架的实现)
测试下来可以看到每种服务器都能轻松达到同时120万的websocket活动连接,只是资源占用和事务处理时间有差别。120万只是保守数据,在这么多连接情况下服务器依然很轻松,下一步我会进行C2000K的测试。
在测试之前我们需要对服务器/客户机的一些参数进行调优。
服务器的参数调优
一般会修改两个文件,/etc/sysctl.conf和/etc/security/limits.conf, 用来配置TCP/IP参数和最大文件描述符。
TCP/IP参数配置
修改文件/etc/sysctl.conf,配置网络参数。
net.ipv4.tcp_wmem = 4096 87380 4161536
net.ipv4.tcp_rmem = 4096 87380 4161536
net.ipv4.tcp_mem = 786432 2097152 3145728
数值根据需求进行调整。更多的参数可以看以前整理的一篇文章:&。执行/sbin/sysctl -p即时生效。
最大文件描述符
Linux内核本身有文件描述符最大值的**,你可以根据需要更改:
系统最大打开文件描述符数:/proc/sys/fs/file-max
临时性设置:echo 1000000 & /proc/sys/fs/file-max
永久设置:修改/etc/sysctl.conf文件,增加fs.file-max = 1000000
进程最大打开文件描述符数使用ulimit -n查看当前设置。使用ulimit -n 1000000进行临时性设置。要想永久生效,你可以修改/etc/security/limits.conf文件,增加下面的行:
还有一点要注意的就是hard limit不能大于/proc/sys/fs/nr_open,因此有时你也需要修改nr_open的值。执行echo 2000000 & /proc/sys/fs/nr_open
查看当前系统使用的打开文件描述符数,可以使用下面的命令:
[root@localhost ~]# cat /proc/sys/fs/file-nr
其中第一个数表示当前系统已分配使用的打开文件描述符数,第二个数为分配后已释放的(目前已不再使用),第三个数等于file-max。
总结一下:
所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max
单个进程打开的文件描述符数不能超过user limit中nofile的soft limit
nofile的soft limit不能超过其hard limit
nofile的hard limit不能超过/proc/sys/fs/nr_open
应用运行时调优
Java 应用内存调优服务器使用12G内存,吞吐率优先的垃圾回收器:
JAVA_OPTS="-Xms12G -Xmx12G -Xss1M -XX:+UseParallelGC"
node --nouse-idle-notification --expose-gc --max-new-space-size=1024 --max-new-space-size=2048 --max-old-space-size=8192 ./webserver.js
OutOfMemory Killer
如果服务器本身内存不大,比如8G,在不到100万连接的情况下,你的服务器进程有可能出现"Killed"的问题。 运行dmesg可以看到
Out of memory: Kill process 10375 (java) score 59 or sacrifice child
这是Linux的OOM Killer主动杀死的。 开启oom-killer的话,在/proc/pid下对每个进程都会多出3个与oom打分调节相关的文件。临时对某个进程可以忽略oom-killer可以使用下面的方式:echo -17 & /proc/$(pidof java)/oom_adj解决办法有多种,可以参看文章最后的参考文章,最好是换一个内存更大的机器。
客户端的参数调优
在一台系统上,连接到一个远程服务时的本地端口是有限的。根据TCP/IP协议,由于端口是16位整数,也就只能是0到 65535,而0到1023是预留端口,所以能分配的端口只是,也就是64511个。也就是说,一台机器一个IP只能创建六万多个长 连接。要想达到更多的客户端连接,可以用更多的机器或者网卡,也可以使用虚拟IP来实现,比如下面的命令增加了19个IP地址,其中一个给服务器用,其它18个给client,这样可以产生18 * 60000 = 1080000个连接。
ifconfig eth0:0 192.168.77.10 netmask 255.255.255.0 up
ifconfig eth0:1 192.168.77.11 netmask 255.255.255.0 up
ifconfig eth0:2 192.168.77.12 netmask 255.255.255.0 up
ifconfig eth0:3 192.168.77.13 netmask 255.255.255.0 up
ifconfig eth0:4 192.168.77.14 netmask 255.255.255.0 up
ifconfig eth0:5 192.168.77.15 netmask 255.255.255.0 up
ifconfig eth0:6 192.168.77.16 netmask 255.255.255.0 up
ifconfig eth0:7 192.168.77.17 netmask 255.255.255.0 up
ifconfig eth0:8 192.168.77.18 netmask 255.255.255.0 up
ifconfig eth0:9 192.168.77.19 netmask 255.255.255.0 up
ifconfig eth0:10 192.168.77.20 netmask 255.255.255.0 up
ifconfig eth0:11 192.168.77.21 netmask 255.255.255.0 up
ifconfig eth0:12 192.168.77.22 netmask 255.255.255.0 up
ifconfig eth0:13 192.168.77.23 netmask 255.255.255.0 up
ifconfig eth0:14 192.168.77.24 netmask 255.255.255.0 up
ifconfig eth0:15 192.168.77.25 netmask 255.255.255.0 up
ifconfig eth0:16 192.168.77.26 netmask 255.255.255.0 up
ifconfig eth0:17 192.168.77.27 netmask 255.255.255.0 up
ifconfig eth0:18 192.168.77.28 netmask 255.255.255.0 up
修改/etc/sysctl.conf文件:
net.ipv4.ip_local_port_range =
执行/sbin/sysctl -p即时生效。
服务器测试
实际测试中我使用一台AWS C3.4xlarge (16 cores, 32G memory)作为应用服务器,两台AWS C3.2xlarge (8 cores, 16G memory)服务器作为客户端。这两台机器作为测试客户端绰绰有余,每台客户端机器创建了十个内网虚拟IP, 每个IP创建60000个websocket连接。
客户端配置如下:/etc/sysctl.conf配置
fs.file-max = 2000000
fs.nr_open = 2000000
net.ipv4.ip_local_port_range = 1024 65535
/etc/security/limits.conf配置
* soft nproc 2000000
* hard nproc 2000000
服务端配置如下:/etc/sysctl.conf配置
fs.file-max = 2000000
fs.nr_open = 2000000
net.ipv4.ip_local_port_range = 1024 65535
/etc/security/limits.conf配置
* soft nproc 2000000
* hard nproc 2000000
Netty服务器
建立120万个连接,不发送消息,轻轻松松达到。内存还剩14G未用。
[roocolobu ~]# ss -s; free -m
Total: 1200231 (kernel 1200245)
1200006 (estab 1200002, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 4
Transport Total
-/+ buffers/cache:
每分钟给所有的120万个websocket发送一条消息,消息内容为当前的服务器的时间。这里发送显示是单线程发送,服务器发送完120万个总用时15秒左右。
02:15:43.307 [pool-1-thread-1]
02:15:57.190 [pool-1-thread-1]
发送时CPU使用率并不高,网络带宽占用基本在10M左右。
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read
writ| recv
客户端(一共20个,这里选取其中一个查看它的指标)。每个客户端保持6万个连接。每个消息从服务器发送到客户端接收到总用时平均633毫秒,而且标准差很小,每个连接用时差不多。
Active WebSockets for eb810c24-8565-43ea-bc27-9a0b2c910ca4
count = 60000
WebSocket Errors for eb810c24-8565-43ea-bc27-9a0b2c910ca4
-- Histograms ------------------------------------------------------------------
Message latency for eb810c24-8565-43ea-bc27-9a0b2c910ca4
count = 693831
mean = 633.06
stddev = 9.61
median = 631.00
75% &= 633.00
95% &= 640.00
98% &= 651.00
99% &= 670.00
99.9% &= 735.00
-- Meters ----------------------------------------------------------------------
Message Rate for eb810c24-8565-43ea-bc27-9a0b2c910ca4
count = 693832
mean rate = 32991.37 events/minute
1-minute rate = 60309.26 events/minute
5-minute rate = 53523.45 events/minute
15-minute rate = 31926.26 events/minute
平均每个client的RPS = 1000, 总的RPS大约为 20000 requests /seconds.latency平均值为633 ms,最长735 ms,最短627ms。
Spray服务器
建立120万个连接,不发送消息,轻轻松松达到。它的内存相对较高,内存还剩7G。
[root@colobu ~]# ss -s; free -m
Total: 1200234 (kernel 1200251)
1200006 (estab 1200002, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 4
Transport Total
-/+ buffers/cache:
每分钟给所有的120万个websocket发送一条消息,消息内容为当前的服务器的时间。CPU使用较高,发送很快,带宽可以达到46M。群发完一次大约需要8秒左右。
05/22 04:42:57.569 INFO [ool-2-worker-15] c.c.w.s.WebServer - send msg to workers 。for -b8ca-cdf3e6787bf
05/22 04:43:05.279 INFO [ool-2-worker-15] c.c.w.s.WebServer - sent msg to workers for -b8ca-cdf3e6787bf. current workers: 1200000
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read
writ| recv
客户端(一共20个,这里选取其中一个查看它的指标)。每个客户端保持6万个连接。每个消息从服务器发送到客户端接收到总用时平均1412毫秒,而且标准差较大,每个连接用时差别较大。
Active WebSockets for -24c6-4e77-9fc0-58afabe7436f
count = 60000
WebSocket Errors for -24c6-4e77-9fc0-58afabe7436f
-- Histograms ------------------------------------------------------------------
Message latency for -24c6-4e77-9fc0-58afabe7436f
count = 454157
max = 9297
mean = 1412.77
stddev = 1102.64
median = 991.00
75% &= 1449.00
95% &= 4136.00
98% &= 4951.00
99% &= 5308.00
99.9% &= 8854.00
-- Meters ----------------------------------------------------------------------
Message Rate for -24c6-4e77-9fc0-58afabe7436f
count = 454244
mean rate = 18821.51 events/minute
1-minute rate = 67705.18 events/minute
5-minute rate = 49917.79 events/minute
15-minute rate = 24355.57 events/minute
建立120万个连接,不发送消息,轻轻松松达到。内存占用较少,还剩余11G内存。
[root@colobu ~]# ss -s; free -m
Total: 1200234 (kernel 1200240)
1200006 (estab 1200002, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 4
Transport Total
-/+ buffers/cache:
每分钟给所有的120万个websocket发送一条消息,消息内容为当前的服务器的时间。群发玩一次大约需要15秒。
03:19:31.154 [pool-1-thread-1]
03:19:46.755 [pool-1-thread-1]
客户端(一共20个,这里选取其中一个查看它的指标)。每个客户端保持6万个连接。每个消息从服务器发送到客户端接收到总用时平均672毫秒,而且标准差较小,每个连接用时差别不大。
Active WebSockets for b2e95e8d-b17a-4cfa-94d5-ed
count = 60000
WebSocket Errors for b2e95e8d-b17a-4cfa-94d5-ed
-- Histograms ------------------------------------------------------------------
Message latency for b2e95e8d-b17a-4cfa-94d5-ed
count = 460800
mean = 672.12
stddev = 5.90
median = 671.00
-- Meters ----------------------------------------------------------------------
Message Rate for b2e95e8d-b17a-4cfa-94d5-ed
count = 460813
mean rate = 27065.85 events/minute
1-minute rate = 69271.67 events/minute
5-minute rate = 48641.78 events/minute
15-minute rate = 24128.67 events/minute
Setup Rate for b2e95e8d-b17a-4cfa-94d5-ed
node.js不是我要考虑的框架,列在这里只是作为参考。性能也不错。
Active WebSockets for 537c7f0d-e58b-4996-b29e-098fe2682dcf
count = 60000
WebSocket Errors for 537c7f0d-e58b-4996-b29e-098fe2682dcf
-- Histograms ------------------------------------------------------------------
Message latency for 537c7f0d-e58b-4996-b29e-098fe2682dcf
count = 180000
mean = 812.10
stddev = 1.95
median = 812.00
75% &= 812.00
95% &= 813.00
98% &= 814.00
99% &= 815.00
99.9% &= 847.00
-- Meters ----------------------------------------------------------------------
Message Rate for 537c7f0d-e58b-4996-b29e-098fe2682dcf
count = 180000
mean rate = 7191.98 events/minute
1-minute rate = 10372.33 events/minute
5-minute rate = 16425.78 events/minute
15-minute rate = 9080.53 events/minute> 博客详情
摘要: WebSocket使用教程 - 带完整实例
什么是WebSocket?看过html5的同学都知道,WebSocket protocol 是HTML5一种新的协议。它是实现了浏览器与服务器全双工通信(full-duplex)。HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。现在我们来探讨一下html5的WebSocket
HTML5作为下一代WEB标准,拥有许多引人注目的新特性,如Canvas、本地存储、多媒体编程接口、WebSocket 等等。今天我们就来看看具有“Web TCP”之称的WebSocket.
WebSocket的出现是基于Web应用的实时性需要而产生的。这种实时的Web应用大家应该不陌生,在生活中都应该用到过,比如新浪微博的评论、私信的通知,腾讯的WebQQ等。让我们来回顾下实时 Web 应用的窘境吧。
在WebSocket出现之前,一般通过两种方式来实现Web实时用:轮询机制和流技术;其中轮询有不同的轮询,还有一种叫Comet的长轮询。
轮询:这是最早的一种实现实时 Web 应用的方案。客户端以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。这种同步方案的缺点是,当客户端以固定频率向服务 器发起请求的时候,服务器端的数据可能并没有更新,这样会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。
长轮询:是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者 时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提 高。
流:常就是在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务 器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。这种机制在用户体验上有一点问题,需要针对不同的设计不同的方案来改进 用户体验,同时这种机制在并发比较大的情况下,对服务器端的资源是一个极大的考验。
上述方式其实并不是真正的实时技术,只是使用了一种技巧来实现的模拟实时。在每次客户端和服务器端交互的时候都是一次 HTTP 的请求和应答的过程,而每一次的 HTTP 请求和应答都带有完整的 HTTP 头信息,这就增加了每次传输的数据量。但这些方式最痛苦的是开发人员,因为不论客户端还是服务器端的实现都很复杂,为了模拟比较真实的实时效果,开发人员 往往需要构造两个HTTP连接来模拟客户端和服务器之间的双向通讯,一个连接用来处理客户端到服务器端的数据传输,一个连接用来处理服务器端到客户端的数 据传输,这不可避免地增加了编程实现的复杂度,也增加了服务器端的负载,制约了应用系统的扩展性。
基于上述弊端,实现Web实时应用的技术出现了,WebSocket通过浏览器提供的API真正实现了具备像C/S架构下的桌面系统的实时通讯能 力。其原理是使用JavaScript调用浏览器的API发出一个WebSocket请求至服务器,经过一次握手,和服务器建立了TCP通讯,因为它本质 上是一个TCP连接,所以数据传输的稳定性强和数据传输量比较小。
WebSocket 协议
WebSocket 协议本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息”Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
下面我们来详细介绍一下 WebSocket 协议,由于这个协议目前还是处于草案阶段,版本的变化比较快,我们选择目前最新的 draft-ietf-hybi-thewebsocketprotocol-17 版本来描述 WebSocket 协议。因为这个版本目前在一些主流的浏览器上比如 Chrome,、FireFox、Opera 上都得到比较好的支持。通过描述可以看到握手协议
客户端发到服务器的内容:
&&& GET /chat HTTP/1.1 &&& Host: server.example.com &&& Upgrade: websocket &&& Connection: Upgrade &&& Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== &&& Origin: http://example.com &&& Sec-WebSocket-Protocol: chat, superchat &&& Sec-WebSocket-Version: 13
从服务器到客户端的内容:
&&& HTTP/1.1 101 Switching Protocols &&& Upgrade: websocket &&& Connection: Upgrade &&& Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= &&& Sec-WebSocket-Protocol: chat
这些请求和通常的 HTTP 请求很相似,但是其中有些内容是和 WebSocket 协议密切相关的。我们需要简单介绍一下这些请求和应答信息,”Upgrade:WebSocket”表示这是一个特殊的 HTTP 请求,请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。其中客户端的Sec-WebSocket-Key和服务器端的Sec-WebSocket-Accept就是重要的握手认证信息了,这些内容将在服 务器端实现的博文中讲解。
相信通过上文的讲解你应该对WebSocket有了个初步认识了,如果有任何疑问欢迎交流。&
如概念篇中介绍的握手协议,客户端是由浏览器提供了API,所以只要使用JavaScript来简单调用即可,而服务器端是要自己实现的,服务器端将在下个博文来讲。
WebSocket JavaScript 接口定义:
[Constructor(in DOMString url, optional in DOMString protocol)] interface WebSocket { readonly attribute DOMString URL; // ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSED = 2; readonly attribute unsigned short readyS readonly attribute unsigned long bufferedA & // networking attribute F attribute F attribute F boolean send(in DOMString data); void close(); }; WebSocket implements EventT
简单了解下接口方法和属性:
readyState表示连接有四种状态: CONNECTING (0):表示还没建立连接; OPEN (1): 已经建立连接,可以进行通讯; CLOSING (2):通过关闭握手,正在关闭连接; CLOSED (3):连接已经关闭或无法打开; url是代表 WebSocket 服务器的网络地址,协议通常是”ws”或“wss(加密通信)”,send 方法就是发送数据到服务器端; close 方法就是关闭连接; onopen连接建立,即握手成功触发的事件; onmessage收到服务器消息时触发的事件; onerror异常触发的事件; onclose关闭连接触发的事件;
JavaScript调用浏览器接口实例如下:
var wsServer = 'ws://localhost:8888/Demo'; //服务器地址 var websocket = new WebSocket(wsServer); //创建WebSocket对象 websocket.send("hello");//向服务器发送消息 alert(websocket.readyState);//查看websocket当前状态 websocket.onopen = function (evt) { //已经建立连接 }; websocket.onclose = function (evt) { //已经关闭连接 }; websocket.onmessage = function (evt) { //收到服务器消息,使用evt.data提取 }; websocket.onerror = function (evt) { //产生异常 };&
握手协议的客户端数据已经由浏览器代劳了,服务器端需要我们自己来实现,目前市场上开源的实现也比较多如:
&&& Kaazing WebSocket Gateway(一个 Java 实现的 WebSocket Server); &&& mod_pywebsocket(一个 Python 实现的 WebSocket Server); &&& Netty(一个 Java 实现的网络框架其中包括了对 WebSocket 的支持); &&& node.js(一个 Server 端的 JavaScript 框架提供了对 WebSocket 的支持); &&& WebSocket4Net(一个.net的服务器端实现);
其实在目前的.net4.5框架中已经实现了WebSocket,不用官方实现,我们自己来写个简单的。服务器端需要根据协议来握手、接收和发送。
首先我们再来回顾下握手协议:
客户端发到服务器的内容:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
& 从服务器到客户端的内容:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
关键是服务器端Sec-WebSocket-Accept,它是根据Sec-WebSocket-Key计算出来的:
取出Sec-WebSocket-Key,与一个magic string “258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 连接成一个新的key串; 将新的key串SHA1编码,生成一个由多组两位16进制数构成的加密串; 把加密串进行base64编码生成最终的key,这个key就是Sec-WebSocket-Key;
实例代码如下:
/// &summary& /// 生成Sec-WebSocket-Accept /// &/summary& /// &param name="handShakeText"&客户端握手信息&/param& /// &returns&Sec-WebSocket-Accept&/returns& private static string GetSecKeyAccetp(byte[] handShakeBytes,int bytesLength) { string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength); string key = string.E Regex r = new Regex(@"Sec-WebSocket-Key:(.*?)rn"); Match m = r.Match(handShakeText); if (m.Grou.Count != 0) { key = Regex.Replace(m.Value, @"Sec-WebSocket-Key:(.*?)rn", "$1").Trim(); } byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); return Convert.ToBase64String(encryptionString); }
如果握手成功,将会触发客户端的onopen事件。
解析接收的客户端信息
接收到客户端数据解析规则如下:
1byte bit: frame-fin,x0表示该message后续还有frame;x1表示是message的最后一个frame 3bit: 分别是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x0 4bit: frame-opcode,x0表示是延续frame;x1表示文本frame;x2表示二进制frame;x3-7保留给非控制frame;x8表示关 闭连接;x9表示ping;xA表示pong;xB-F保留给控制frame 2byte 1bit: Mask,1表示该frame包含掩码;0,表示无掩码 7bit、7bit+2byte、7bit+8byte: 7bit取整数值,若在0-125之间,则是负载数据长度;若是126表示,后两个byte取无符号16位整数值,是负载长度;127表示后8个 byte,取64位无符号整数值,是负载长度 3-6byte: 这里假定负载长度在0-125之间,并且Mask为1,则这4个byte是掩码 7-end byte: 长度是上面取出的负载长度,包括扩展数据和应用数据两部分,通常没有扩展数据;若Mask为1,则此数据需要解码,解码规则为1-4byte掩码循环和数据byte做异或操作。
解析代码如下,但没有处理多帧和不包含掩码的包:
/// &summary& /// 解析客户端数据包 /// &/summary& /// &param name="recBytes"&服务器接收的数据包&/param& /// &param name="recByteLength"&有效数据长度&/param& /// &returns&&/returns& private static string AnalyticData(byte[] recBytes, int recByteLength) { if (recByteLength & 2) { return string.E } & bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧 if (!fin){ return string.E// 超过一帧暂不处理 } & bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码 if (!mask_flag){ return string.E// 不包含掩码的暂不处理 } & int payload_len = recBytes[1] & 0x7F; // 数据长度 & byte[] masks = new byte[4]; byte[] payload_ & if (payload_len == 126){ Array.Copy(recBytes, 4, masks, 0, 4); payload_len = (UInt16)(recBytes[2] && 8 | recBytes[3]); payload_data = new byte[payload_len]; Array.Copy(recBytes, 8, payload_data, 0, payload_len); & }else if (payload_len == 127){ Array.Copy(recBytes, 10, masks, 0, 4); byte[] uInt64Bytes = new byte[8]; for (int i = 0; i & 8; i++){ uInt64Bytes[i] = recBytes[9 - i]; } UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0); & payload_data = new byte[len]; for (UInt64 i = 0; i & i++){ payload_data[i] = recBytes[i + 14]; } }else{ Array.Copy(recBytes, 2, masks, 0, 4); payload_data = new byte[payload_len]; Array.Copy(recBytes, 6, payload_data, 0, payload_len); & } & for (var i = 0; i & payload_ i++){ payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]); } return Encoding.UTF8.GetString(payload_data); }
发送数据至客户端
服务器发送的数据以0x81开头,紧接发送内容的长度(若长度在0-125,则1个byte表示长度;若长度不超过0xFFFF,则后2个byte 作为无符号16位整数表示长度;若超过0xFFFF,则后8个byte作为无符号64位整数表示长度),最后是内容的byte数组。
代码如下:
/// &summary& /// 打包服务器数据 /// &/summary& /// &param name="message"&数据&/param& /// &returns&数据包&/returns& private static byte[] PackData(string message) { byte[] contentBytes = byte[] temp = Encoding.UTF8.GetBytes(message); & if (temp.Length & 126){ contentBytes = new byte[temp.Length + 2]; contentBytes[0] = 0x81; contentBytes[1] = (byte)temp.L Array.Copy(temp, 0, contentBytes, 2, temp.Length); }else if (temp.Length & 0xFFFF){ contentBytes = new byte[temp.Length + 4]; contentBytes[0] = 0x81; contentBytes[1] = 126; contentBytes[2] = (byte)(temp.Length & 0xFF); contentBytes[3] = (byte)(temp.Length && 8 & 0xFF); Array.Copy(temp, 0, contentBytes, 4, temp.Length); }else{ // 暂不处理超长内容 } & return contentB }
这里只是简单介绍,下节来做个完整的实例。&
说是完整的实例,其实并不完整,这里需要说明,这个实例并没有实现并发,也没考虑到算法和资源管理,所谓的完整是有客户端和服务器端,并且能跑起来演示。直接上菜,关于理论请看前面三篇博文,TCP请另看相关知识。
客户端代码:
&html& &head& &meta charset="UTF-8"& &title&Web sockets test&/title& &script src="-min.js" type="text/"&&/script& &script type="text/javascript"& function ToggleConnectionClicked() { try { ws = new WebSocket("ws://10.9.146.31:1818/chat");//连接服务器 ws.onopen = function(event){alert("已经与服务器建立了连接rn当前连接状态:"+this.readyState);}; ws.onmessage = function(event){alert("接收到服务器发送的数据:rn"+event.data);}; ws.onclose = function(event){alert("已经与服务器断开连接rn当前连接状态:"+this.readyState);}; ws.onerror = function(event){alert("WebSocket异常!");}; }&&(ex) { alert(ex.message); } }; & function SendData() { try{ ws.send("beston"); }catch(ex){ alert(ex.message); } }; & function seestate(){ alert(ws.readyState); } &/script& &/head& &body& &button id='ToggleConnection' type="button" onclick='ToggleConnectionClicked();'&连接服务器&/button&&br /&&br /& &button id='ToggleConnection' type="button" onclick='SendData();'&发送我的名字:beston&/button&&br /&&br /& &button id='ToggleConnection' type="button" onclick='seestate();'&查看状态&/button&&br /&&br /& &/body& &/html&
服务器端代码:
using S using System.N using System.Net.S using System.Security.C using System.T using System.Text.RegularE & namespace WebSocket { class Program { static void Main(string[] args) { int port = 1818; byte[] buffer = new byte[1024]; & IPEndPoint localEP = new IPEndPoint(IPAddress.Any, port); Socket listener = new Socket(localEP.Address.AddressFamily,SocketType.Stream, ProtocolType.Tcp); try{ listener.Bind(localEP); listener.Listen(10); & Console.WriteLine("等待客户端连接...."); Socket sc = listener.Accept();//接受一个连接 Console.WriteLine("接受到了客户端:"+sc.RemoteEndPoint.ToString()+"连接...."); //握手 int length = sc.Receive(buffer);//接受客户端握手信息 sc.Send(PackHandShakeData(GetSecKeyAccetp(buffer,length))); Console.WriteLine("已经发送握手协议了...."); //接受客户端数据 Console.WriteLine("等待客户端数据...."); length = sc.Receive(buffer);//接受客户端信息 string clientMsg=AnalyticData(buffer, length); Console.WriteLine("接受到客户端数据:" + clientMsg); & //发送数据 string sendMsg = "您好," + clientM Console.WriteLine("发送数据:“"+sendMsg+"” 至客户端...."); sc.Send(PackData(sendMsg)); Console.WriteLine("演示Over!"); & } catch (Exception e) { Console.WriteLine(e.ToString()); } } & /// &summary& /// 打包握手信息 /// &/summary& /// &param name="secKeyAccept"&Sec-WebSocket-Accept&/param& /// &returns&数据包&/returns& private static byte[] PackHandShakeData(string secKeyAccept) { var responseBuilder = new StringBuilder(); responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine); responseBuilder.Append("Upgrade: websocket" + Environment.NewLine); responseBuilder.Append("Connection: Upgrade" + Environment.NewLine); responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine); //如果把上一行换成下面两行,才是thewebsocketprotocol-17协议,但居然握手不成功,目前仍没弄明白! //responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine); //responseBuilder.Append("Sec-WebSocket-Protocol: chat" + Environment.NewLine); & return Encoding.UTF8.GetBytes(responseBuilder.ToString()); } & /// &summary& /// 生成Sec-WebSocket-Accept /// &/summary& /// &param name="handShakeText"&客户端握手信息&/param& /// &returns&Sec-WebSocket-Accept&/returns& private static string GetSecKeyAccetp(byte[] handShakeBytes,int bytesLength) { string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength); string key = string.E Regex r = new Regex(@"Sec-WebSocket-Key:(.*?)rn"); Match m = r.Match(handShakeText); if (m.Groups.Count != 0) { key = Regex.Replace(m.Value, @"Sec-WebSocket-Key:(.*?)rn", "$1").Trim(); } byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); return Convert.ToBase64String(encryptionString); } & /// &summary& /// 解析客户端数据包 /// &/summary& /// &param name="recBytes"&服务器接收的数据包&/param& /// &param name="recByteLength"&有效数据长度&/param& /// &returns&&/returns& private static string AnalyticData(byte[] recBytes, int recByteLength) { if (recByteLength & 2) { return string.E } & bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧 if (!fin){ return string.E// 超过一帧暂不处理 } & bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码 if (!mask_flag){ return string.E// 不包含掩码的暂不处理 } & int payload_len = recBytes[1] & 0x7F; // 数据长度 & byte[] masks = new byte[4]; byte[] payload_ & if (payload_len == 126){ Array.Copy(recBytes, 4, masks, 0, 4); payload_len = (UInt16)(recBytes[2] && 8 | recBytes[3]); payload_data = new byte[payload_len]; Array.Copy(recBytes, 8, payload_data, 0, payload_len); & }else if (payload_len == 127){ Array.Copy(recBytes, 10, masks, 0, 4); byte[] uInt64Bytes = new byte[8]; for (int i = 0; i & 8; i++){ uInt64Bytes[i] = recBytes[9 - i]; } UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0); & payload_data = new byte[len]; for (UInt64 i = 0; i & i++){ payload_data[i] = recBytes[i + 14]; } }else{ Array.Copy(recBytes, 2, masks, 0, 4); payload_data = new byte[payload_len]; Array.Copy(recBytes, 6, payload_data, 0, payload_len); & } & for (var i = 0; i & payload_ i++){ payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]); } return Encoding.UTF8.GetString(payload_data); } & & /// &summary& /// 打包服务器数据 /// &/summary& /// &param name="message"&数据&/param& /// &returns&数据包&/returns& private static byte[] PackData(string message) { byte[] contentBytes = byte[] temp = Encoding.UTF8.GetBytes(message); & if (temp.Length & 126){ contentBytes = new byte[temp.Length + 2]; contentBytes[0] = 0x81; contentBytes[1] = (byte)temp.L Array.Copy(temp, 0, contentBytes, 2, temp.Length); }else if (temp.Length & 0xFFFF){ contentBytes = new byte[temp.Length + 4]; contentBytes[0] = 0x81; contentBytes[1] = 126; contentBytes[2] = (byte)(temp.Length & 0xFF); contentBytes[3] = (byte)(temp.Length && 8 & 0xFF); Array.Copy(temp, 0, contentBytes, 4, temp.Length); }else{ // 暂不处理超长内容 } & return contentB } } }
运行效果:
使用的浏览器:
疑问:如实例中
responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine); //如果把上一行换成下面两行,才是thewebsocketprotocol-17协议,但居然握手不成功,目前仍没弄明白! //responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine); //responseBuilder.Append("Sec-WebSocket-Protocol: chat" + Environment.NewLine);
这是为什么呢?看到这篇博文的兄弟希望能够给我解惑!
连接键盘 功能
什么是”连接键盘“功能
”连接键盘“功能其实就是开通了网页版的,利用键盘可以快速录入文字聊天。
连接方法很简单,用手机打开微信点击右上角魔术棒,会弹出三个选项,只用选择连接键盘就可以了,这时用浏览器打开wx.qq.com然后用手机扫描网页中的二维码即可打开网页版微信,而这时也就可以直接利用电脑键盘实现快速聊天了。
WebSocket-Server里项目含义如下:
Mobile:手机模拟器,与手机通讯服务器进行UDP通讯,负责提示打开的页面地址,并输入GUID(相当于二维码)与页面进行绑定; MobileServer:手机通讯服务器,负责接收手机信息(比如微信的账户信息以及二维码信息),此处接收GUID。并转发至WebSocket通讯服务器; WebSocket:WebSocket通讯服务器,与手机通讯服务器和页面的WebSocket进行通讯;
WebSocket-Client里项目含义如下:
test.html:测试的web页面(类似微信的wx.qq.com); jquery-1.8.0.min.js:jquery框架;
页面test.html生成GUID并存储在WebSocket,手机模拟器输入GUID并传至WebSocket服务器,在WebSocket服务器检索页面Socket信息并通讯。
如果自己测试请根据上述步骤先启动手机通讯服务器和WebSocket通讯服务器; 把所有是“10.9.146.31”的字符串更换为自己的IP;
支付宝支付
微信扫码支付
打赏金额: ¥
已支付成功
打赏金额: ¥}

我要回帖

更多关于 版本服务器连接超时 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信