在Web应用开发时使用WebSocket也会面对一些特别的挑战,WebSocket的Session与HTTP的Session之间并无任何关联,虽然也可将其用作类似的目标。在Session中可以附加某些通用的数据,因此所有的消息处理过程都可以依赖于Session中所维护的某些状态和数据。WebSocket的Session也可以根据空闲(不活跃)时间间隔的配置产生超时情况,正如HTTP Session一样。不过有些系统会自动地持续发送Ping这一控制消息,以防止出现超时。JSR 356建议将HTTP Session与WebSocket Session的超时进行同步。一旦HTTP Session超时,在其范围内所创建的所有WebSocket连接也都必须关闭。但有些Web应用的设计不会产生任何HTTP Session,而有些应用的Session超时不依赖于HTTP Session,而是由JavaScript所管理的,因此这种机制并不能够进行可靠的推广。
另一种需要注意的要点在于,某些浏览器会维护一个连接池,以重用连接的方式访问相同的网站,因此这种流程可以被串行化。而如果浏览器为 WebSocket连接也创建一个连接池,那么它会受到严重的制约。因为如果没有某种机制保持WebSocket连接的关闭,这个连接就永远处于活跃状态,而其它任何创建新连接的尝试都会产生死锁。因此,最佳实践的推荐做法是只使用一个WebSocket连接。
浏览器无法对通过WebSocket进行传递的数据进行缓存,因此通过WebSocket传递可以在浏览器中缓存的资源
(例如图片、CSS等)并非一种有效的途径。
在网络上对于RESTful与WebSocket之间的讨论从未停歇2。不过,这些比较中的大部分都不是在一个层面上的比较,好比关公战秦琼。REST是指表述性状态转换,多数情况下它需要依赖底层的HTTP协议实现,也就是说REST是一个基于请求 - 响应的协议。REST这种风格没有经过标准化,因此任何一种通过HTTP进行通信的方式在某些范围内都可以称为REST。REST通常会将新增、读取、更新和删除操作(CRUD)与对应的HTTP方法PUT、GET、DELETE之间建立映射关系。而WebSocket所处理的是消息,因此对于单一的 RPC来说不存在一个确定的范围。REST的通信数据格式通常仅限JSON格式以及请求参数,而一个WebSocket消息体可以表现为任何类型,包括纯粹的二进制数据3。
当然,WebSocket也能够用于与REST相似的目的,但在大多数情况下,这种做法有些刻意为之了。正如上文所述,在使用WebSocket过程中需要应用一些不同的设计原则。下表描述了这两者之间的主要区别4。
WebSocket
REST
已实现标准化
得到广泛支持
异步消息传递
(同步)请求/响应
基于帧
基于HTTP方法(get,put,delete,post)
子协议
可发现的操作
二进制与文本
目前大都为 JSON 数据
并行双向更新
目前大都为 CRUD 操作(Create、Retrieve、Update 和 Delete)
文件上传的示例以下示例展现了如何通过使用WebSocket将一个文件上传至服务器,首先最好定义一个服务端的终结点。
@ServerEndpoint("/upload/{file}") public class UploadServer {
其中要定义两个消息处理函数,一个用于接收上传文件的二进制数据,而另一个则用于命令接口。由于在WebSocket中允许分离文本与二进制消息,因此在定义两个处理函数时无需进行额外的操作。用于接收命令的OnMessage处理函数定义如下:
@OnMessage public void processCmd(CMD cmd, Session ses) {
CMD类的定义如下
static class CMD { public int cmd; public String data; }
为了将文本消息转换为CMD对象,需要指定一个解码器,其定义如下:
CmdDecoder implements Decoder.Text<CMD> {
将文本信息编码为JSON格式并不是一种强制性的要求,只是在这个示例中需要用到JSON。大文件的上传是分多个块进行的,以减少内存的开销。在浏览器中无法利用WebSocket的部分帧,因此需要用到完整的帧来模拟块传送。由于浏览器以异步的方式发送所有的消息,因此无法得知服务端是否已经接收到了一个完整的文件。命令接口的作用是完成以下工作:
同样的CMD对象可以进行重用,以满足各种需求。传入的命令是按照以下方式进行处理的:
@OnMessage public void processCmd(CMD cmd, Session ses) { switch (cmd.cmd) { case 1: // start fileName = cmd.data; break; case 2: // finish close(ses); cmd.cmd = 3; ses.getAsyncRemote().sendObject(cmd); break; } }
这种实现方式假设浏览器端会将所有发送消息的活动进行串行化,即所有消息的到达顺序与发送顺序是一致的。但是如果某个客户端使用了某些并行方式进行发送,那么就需要一种更为复杂的实现方式,让每个所发送的消息都带有一个ID。另一种方案是为每个收到的文件块都发送一次确认消息,只是这样一来WebSocket的优势也就丧失殆尽了。由于CMD对象的目标是将消息发送至客户端,因此必须提供一个编码器:
CmdEncoder implements Encoder.Text<CMD> {
在ServerEndpoint的注解中必须指定解码器与编码器信息,如下所示:
@ServerEndpoint(value = "/upload/{file}", decoders = UploadServer.CmdDecoder.class, encoders=UploadServer.CmdEncoder.class) public class UploadServer {
二进制消息的处理函数定义如下: