`
lchshu001
  • 浏览: 24574 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

webSocket 服务器端的简单实现

阅读更多
WEBSOCKET 已经 定稿
http://tools.ietf.org/html/rfc6455
如:TOMCAT RESIN 已经有相应的实现.
所以时间已经不能使用.
--  2013.3.16

上周研究了一下HTML5.
发现很多令人激动的功能。
路漫漫其修远兮,吾将上下而求索!

1. 内置数据库
2. 支持WebSocket
3. 支持多线程
4. 支持本地存储

但是,仍然处于草案中的 WebSocket 竟然找不到合适的服务器,刚好工作比较闲,用来三天时间自己写了一个。
功能有点简单!设计上也有很大缺陷。只能简单的发送信息,和推送信息。
而且现在的协议还不成熟,不久就有一个版本出现!昨天看到才是V16,今天出V17了。

简单介绍一下 WebSocket 它是实现了浏览器与服务器的全双工信息传输。Websocket协议基于Http 的 Upgrade 头和101的响应进行协议切换。经过简单的握手协议,建立一个长连接,按照协议的规则进行数据的传输。具体介绍可以参考google.

1.握手协议
版本0--3中:
握手通过请求头Sec-WebSocket-Key1 和 Sec-WebSocket-Key2 的值和 8 字节的请求实体,进行MD5加密,将加密结果,构造出一个16字节作为请求实体的内容返回。如下实例:
------------------请求--------------------------------------------

 GET /demo HTTP/1.1
 Host: example.com
 Connection: Upgrade
 Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
 Sec-WebSocket-Protocol: sample
 Upgrade: WebSocket
 Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
 Origin: http://example.com
 (\r\n)
 ^n:ds[4U


------------------响应--------------------------------------------
 HTTP/1.1 101 WebSocket Protocol Handshake
 Upgrade: WebSocket
 Connection: Upgrade
 Sec-WebSocket-Origin: http://example.com
 Sec-WebSocket-Location: ws://example.com/demo
 Sec-WebSocket-Protocol: sample
 (\r\n)
 8jKS'y:G*Co,Wxa-

------------------------------------------------------------------

把第一个Key中的数字除以第一个Key的空白字符的数量,而第二个Key也是如此,这样得到两个整数,把每个整数写的四个字节里去,串为8个字节,然后和请求实体里面的8个字节串为16字节,将这16个字节进行MD5加密(如实例中的结果:8jKS'y:G*Co,Wxa-),得到一个16字节的数据作为响应实体的内容,返回给客户端,这样握手成功。


代码实现:

int len = 8; // in.available();
byte[] key3 = new byte[len];
if (in.read(key3) != len)
	throw new RuntimeException();
log.debug(HelpUtil.formatBytes(key3));
String key1 = requestHeaders.get("Sec-WebSocket-Key1");
String key2 = requestHeaders.get("Sec-WebSocket-Key2");
int k1 = HelpUtil.parseWebsokcetKey(key1);
int k2 = HelpUtil.parseWebsokcetKey(key2);

byte[] sixteenByte = new byte[16];
System.arraycopy(HelpUtil.intTo4Byte(k1), 0, sixteenByte, 0, 4);
System.arraycopy(HelpUtil.intTo4Byte(k2), 0, sixteenByte, 4, 4);
System.arraycopy(key3, 0, sixteenByte, 8, 8);
byte[] md5 = MessageDigest.getInstance("MD5").digest(sixteenByte);




在版本4之后,握手协议修改了:
------------------请求--------------------------------------------
 GET /chat HTTP/1.1
 Host: server.example.com
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
 Sec-WebSocket-Origin: http://example.com
 Sec-WebSocket-Protocol: chat, superchat
 (\r\n)


------------------响应--------------------------------------------
 HTTP/1.1 101 Switching Protocols
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
 Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
 Sec-WebSocket-Protocol: chat


使用请求头的值 Sec-WebSocket-Key,该值是BASE-64编码(base64-encoded)的,我们不需要转码,加上一个魔幻字符串: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",(结果:[dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11])使用 SHA-1 加密,之后进行 BASE-64编码,将结果做为 Sec-WebSocket-Accept 头的值,返回给客户端。
如果服务器端有 Sec-WebSocket-Nonce 头,表示要在Sec-WebSocket-Key 的值,和魔幻字符串之间加入该 Sec-WebSocket-Nonce 头的值,即“dGhlIHNhbXBsZSBub25jZQ==AQIDBAUGBwgJCgsMDQ4PEC==258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,进行 SHA-1 加密,之后和前面的相同。完成握手协议。

public static final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
public static final String HEADER_CODE = "iso-8859-1";

String code = requestHeaders.get("Sec-WebSocket-Key") + GUID;
byte[] bts = MessageDigest.getInstance("SHA1").digest(code.getBytes(HEADER_CODE));
code = HelpUtil.getBASE64(bts);
resMap.put("Sec-WebSocket-Accept", code);



握手完成就是数据帧的传输了。

在版本 0 中, 数据帧比较的简单。数据帧以 0x00 开头,以0xFF结尾,中间的数据以utf-8编码的字符就可以了。当然这个简单的格式只能用来传输字符串。无法传输字节流。所以 版本 1 就做了修改了,后面的版本绝大部分是兼容的。
后面的这个帧结构就有点复杂了,如下所示(一行是4个字节,32 bit):
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |M|R|R|R| opcode|R| Payload len |    Extended payload length    |
     |O|S|S|S|  (4)  |S|     (7)     |             (16/63)           |
     |R|V|V|V|       |V|             |   (if payload len==126/127)   |
     |E|1|2|3|       |4|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |         Extension data        |
     +-------------------------------+ - - - - - - - - - - - - - - - +
     :                                                               :
     +---------------------------------------------------------------+
     :                       Application data                        :
     +---------------------------------------------------------------+

(后续的版本略有修改)

获取数据长度

		int dataLen = bt & PAYLOADLEN;

		if (dataLen == HAS_EXTEND_DATA) {// read next 16 bit
			bt = in.read();
			b2 = in.read();
			fram.setDateLength(HelpUtil.toShort((byte) bt, (byte) b2));
		} else if (dataLen == HAS_EXTEND_DATA_CONTINUE) {// read next 32 bit
			byte[] bts = new byte[8];
			if (in.read(bts) != 8){
				//fram.setOpcode
				throw new RuntimeException(
						"reader Payload-Len-Extended-Continued data length < 64 bit");
			}
			fram.setDateLength(HelpUtil.toLong(bts));
		} else {
			fram.setDateLength(dataLen);
		}


[MORE] 表示一个数据通过多个帧进行传输, 如果是 0 表示后面还有数据帧,如果是 1 则表示是最后一个帧。
[RSV1][RSV2][RSV3][RSV4] 未做定义暂时全为零。
[opcode] 标识数据的格式,以及帧的控制,如:08标识数据内容是 文本,01标识:要求远端去关闭当前连接。
[Payload len] 如果小于126 表示后面的数据长度是 [Payload len] 的值。(最大125byte)
              等于 126 表示之后的16 bit位的数据值标识数据的长度。(最大65535byte)
              等于 127 表示之后的64 bit位的数据值标识数据的长度。(一个有符号长整型的最大值)
[Extension data]没有提及怎么使用。
[Application data] 为应用提供的数据。

版本7之后,添加了 MASK 的概念。相当于对数据加密。而且要求客户端必须是MASK的。
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/63)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

[opcode]  01标识数据内容是 文本,08标识 : 要求远端去关闭当前连接。
[MASK](即原先的RSV4)如果是 1 则数据是被 MASK 的。
[Masking-key] 如果MASK为 1 则有4字节的 Masking-key,用于与传输的数据 [Payload Data] 进行异或运算,4byte(32bit)进行一次运算,不足四位从前往后对应,如只有三位,则只与[Masking-key]的前三位进行运算。

解码 MASK 数据,使用了一个过滤流
@Override
public int read() throws IOException {
	if (readLength >= length)
		return -1;
	int b = 0;
	synchronized (lock) {
		if (readLength >= length)
			return -1;
		b = super.read();
		if (isMask) {
			b ^= maskKey[(int) (readLength % 4)];
		}
		readLength++;
	}
	return b;
}


关于流的关闭:一般情况我们可以直接 使用socket.close() 进行关闭,客户端JS状态会显示 webSocket.readyState 的值为 2 (正在关闭的状态)。需要我们通过握手去要求远端关闭流。
有三个版本:
在版本 0 时:传两个字节 (0xff,0x00);
在版本 1--6 时:传三个字节 (0x80,0x01,0x00);
在版本 7--以上 时:传两个字节 (0x88,0x00);

经测试 只有 在版本 7--以上 时:传两个字节 (0x88,0x00); 这时可以实现 webSocket.readyState 的值为 3。
估计是我的代码有问题。如有发现请告知,谢谢!

websocket 协议: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 (其他版本查看相关链接)
源码SVN地址:http://lineblog.googlecode.com/svn/trunk/ 下面的目录
             httpAnalysis/src/com/googlecode/lineblog/websocket/
SVN地址移到:https://lineblog.googlecode.com/svn/trunk/
             httpAnalysis/websokcet/com/googlecode/lineblog/websocket/
            
或者后面的地址下载源码

[完]

转载请保留原文地址: http://lchshu001.iteye.com/blog/1184428 , 谢谢
  • ua.zip (1.9 KB)
  • 描述: 修改了 一个BUG 直接覆盖吧
  • 下载次数: 122
1
12
分享到:
评论
5 楼 aa915609085 2012-08-10  
貌似不支持google chrome 怎么办
4 楼 stwei1983 2012-06-14  
好多谢,正在找这方面的资料,帮大忙了
3 楼 pandong8183 2012-05-15  
太有用了,万分感谢
2 楼 lchshu001 2011-10-02  
max_dong_max_1 写道
老兄,你这是HTML5的功能是吧!

是啊  html5 的新功能,欢迎交流。
1 楼 max_dong_max_1 2011-10-02  
老兄,你这是HTML5的功能是吧!

相关推荐

    C#_WinForm实现WebSocket及时通讯

    实现WebSocket通信的第一步是设置服务器端。在C#中,我们需要使用SuperWebSocket库创建一个WebSocket服务,监听特定的端口,并处理来自客户端的连接请求。服务端代码会包含设置监听端口、启动服务以及处理接收到的...

    dotnet-websocketsharp是WebSocket协议客户端和服务器的C实现

    对于服务器端,WebSocketSharp同样提供了易于使用的API,允许开发者创建WebSocket服务器来接受来自客户端的连接。服务器可以监听特定端口,当接收到新的连接请求时,可以注册事件处理器来处理这些连接。 ```csharp ...

    websocket服务器

    WebSocket服务器是一种实现双向通信的技术,它允许服务器与客户端进行实时数据交换,而无需为每个请求创建新的连接。在Web开发中,WebSocket协议是HTML5的一部分,解决了传统的HTTP长轮询和 comet 技术效率低下的...

    websocket-demo前后端交互的实例代码

    - **测试**:示例可能包含一个简单的HTML页面,用于展示如何与WebSocket服务器进行交互,可能还有配套的JavaScript代码来测试连接和消息传递。 学习和理解`websocket-demo`这个示例,有助于开发者掌握WebSocket的...

    简单实现了websocket功能:websocket客户端、winformsocket客户端

    在本项目中,"简单实现了websocket功能:websocket客户端、winformsocket客户端",意味着我们有两个关键部分:WebSocket服务器端和基于WinForm的客户端。 WebSocket客户端通常是一个应用程序,它通过WebSocket协议...

    推送h264流的简易websocket服务器

    标题中的“推送h264流的简易websocket服务器”是指使用C++编程语言实现的一个服务器程序,它的功能是通过WebSocket协议向客户端推送H264编码的视频流。H264是一种高效的视频编码标准,广泛应用于网络视频传输,因为...

    Qt实现WebSocket客户端和服务端通信

    在Qt中,我们可以使用`QWebSocket`类来实现WebSocket的客户端和服务器端通信。`QWebSocket`是Qt网络模块的一部分,它为WebSocket协议提供了一个方便的API。 首先,我们来看客户端的实现。在Qt中创建一个WebSocket...

    C# WebSocketServer服务器源代码.zip

    总之,“C# WebSocketServer服务器源代码.zip”提供了一个C#实现的WebSocket服务器示例,可以帮助开发者理解WebSocket通信的原理,以及在C#中如何构建和管理WebSocket服务器。通过学习和分析这些代码,可以提升你的...

    WebSocket 的简单实现的的聊天

    9. **后端实现**:后端通常使用Node.js(例如借助ws库)、Java(如Jetty或Tomcat WebSocket支持)或其他支持WebSocket的服务器端语言实现,负责处理WebSocket连接、解析消息并广播。 10. **扩展功能**:除了基本的...

    spring boot+websocket前后端简单demo

    在本文中,我们将深入探讨如何使用Spring Boot和WebSocket技术创建一个简单的前后端交互示例。Spring Boot是Java领域中广泛使用的微服务框架,它简化了Spring应用的初始设置和配置。而WebSocket则是一种在客户端和...

    JAVA_Websocket_Server:使用 Java 作为 Websocket 服务器端和 Java-WebSocket 库的示例(http

    在本文中,我们将深入探讨如何使用Java来搭建一个WebSocket服务器,并使用Java-WebSocket库作为实现的基础。WebSocket协议是一种在客户端和服务器之间建立长连接的协议,它为实时通信提供了低延迟、高效的双向通信...

    WebSocket的两种简单实现

    在实际应用中,我们经常结合服务器端框架来实现WebSocket。Spring Boot是一个流行的Java后端框架,它提供了对WebSocket的全面支持。Spring的WebSocket库包含了STOMP(Simple Text Oriented Messaging Protocol)的...

    webSocket实现Android客户端之间简单的通讯

    WebSocket是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据可以在任意方向上流动,而无需反复建立和关闭...此外,服务器端也需要支持WebSocket协议,以便接收和转发客户端之间的消息。

    c++ websocket服务器和html客户端

    首先,让我们了解C++ WebSocket服务器的实现。通常,C++服务器端开发会涉及到网络编程,如套接字(socket)编程,以及WebSocket协议的具体实现。WebSocket协议是基于HTTP握手协议的,因此服务器需要处理HTTP升级请求...

    WebSocket客户端与服务端

    总的来说,WebSocket是现代Web应用实现实时通信的重要技术,它简化了客户端与服务器端的交互,提升了用户体验。通过深入理解和实践WebSocket协议以及相关的客户端和服务器端编程,开发者可以构建出更高效、实时的Web...

    websocket简单实现

    创建WebSocket服务器端 首先,我们需要创建一个实现了`javax.websocket.OnOpen`, `javax.websocket.OnClose`, `javax.websocket.OnMessage`, 和 `javax.websocket.Session`接口的类。例如: ```java @...

    简单的WebSocket实现

    总的来说,"简单的WebSocket实现"教程旨在帮助开发者理解如何在Java环境下使用Eclipse和Tomcat搭建WebSocket服务器,以及如何编写客户端代码进行通信。这个过程中涉及的主要知识点包括WebSocket协议、JSR 356规范、...

    WebSocket的简单实现

    在压缩包中的"WebSocketSample-master"文件可能是包含了一个简单的WebSocket示例项目,你可以通过阅读代码来进一步了解WebSocket在C#中的具体实现。通常,这样的示例会包含服务器端和客户端的代码,以及如何处理数据...

Global site tag (gtag.js) - Google Analytics