websocket是H5里新增的一种技术,主要用于web客户端与服务端之间建立双工通信通道,进行实时消息传输。在websocket之前,web客户端与服务器之间传递消息都是基于HTTP协议,总所周知,HTTP是一种无状态的协议,web客户端与服务端传递消息必须由客户端主动request,然后服务端返回response结果,服务端无法主动推送消息给客户端。为此,出现了轮询(polling)和Comet技术,这里不做详细介绍,有兴趣可以自己研究一下。轮询(polling)和Comet的本质还是基于HTTP的请求,虽在性能上有不少提升,但在并发量环境下,对服务器要求较高。HTML5推出的WebSocket,才真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。好了,言归正传,结合实例讲一下websocket如何使用。
1、websocket原理
webscoket也是一种基于TCP的协议,与HTTP类似。websocket实现双工通信分为两步:
(1)握手:客户端与服务器进行握手,客户端通过http协议向服务器发出握手请求,服务器响应客户端的握手请求。
客户端到服务端:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Upgrade: WebSocket
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5
Origin: http://example.com
[8-byte security key]
服务端到客户端:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]
客户端通过Upgrade命令请求服务器,把协议升级为websocket协议,并传递一个秘钥Sec-WebSocket-Key1给服务端,服务端响应请求。
(2)通信:握手完成后,客户端与服务端之间的双工通道建立成功,双方可以进行实时通信。
相比传统基于HTTP协议的通信,websocket只在发送握手请求的时候进行了一次HTTP请求,资源占用大大减少。
2、websocket支持
(1)前端websocket支持:在H5中嵌入websocket对象,实现了对websocket支持
websocket js定义:
[Constructor(in DOMString url, in optional 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 readyState;
readonly attribute unsigned long bufferedAmount;
//networking
attribute Function onopen;
attribute Function onmessage;
attribute Function onclose;
attribute Function onerror;
boolean send(in DOMString data);
void close();
};
WebSocket implements EventTarget;
介绍一下对象中几个关键的参数和事件:
URL:客户端连接的服务端地址;
onopen:客户端与服务端通过websocket连接成功时回调;
onmessage:客户端收到服务端消息时回调;
onclose:客户端与服务端之间的websocket通道断开时回调;
onerror:websocket连接出错时回调;
send:websocket对象通过此方法向服务器发消息;
close:websocket对象通过此方法主动关闭连接。
(2)服务端websocket支持:根据websocket版本不同,服务端对websocket支持分为两种:
a 在JavaEE7以前的版本,服务端对websocket的支持都是采用服务器中集成的相关类,
比如tomcat在7.0.27版本开始对websocket支持(websocket1.0),7.0.47版本对websocket1.1支持。使用tomcat提供websocket服务端支持,需引入catalina.jar和tomcat-coyote.jar。注意:这两个jar包千万不要放在WEB-INF/lib目录下,会和tomcat中的jar包发生冲突;
b 从JavaEE7版本开始,Java中集成了websocket,提供了对websocket支持,此时的websocket
即是websocket1.1,和tomcat7.0.47版本中的websocket一致。
3、websocket通信demo
(1)web客户端:
function connect() {
var target = 'ws://' + window.location.host
+ "/websocketTest/HelloWebSocketServlet";
if ('WebSocket' in window) {
ws = new WebSocket(target);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(target);
} else {
alert('WebSocket is not supported by this browser.');
return;
}
ws.onopen = function (event) {
$("#outputPanel").text("WebSocket has opened, Waiting message.......");
};
ws.onmessage = function (event) {
$("#outputPanel").text(event.data);
};
ws.error = function (event){
$("#outputPanel").text("WebSocket error:" + event);
}
ws.onclose = function () {
$("#outputPanel").text("WebSocket has closed");
};
}
function sendMessage(message){
ws.send(message);
}
function disconnect() {
if (ws != null) {
ws.close();
ws = null;
}
}
客户端js代码中new一个websocket对象的时候,向服务端发起连接请求,注意URL前缀的协议是ws或wss(加密),而不是http。无论是websocket1.0还是websocket1.1版本,前端的实现都是一样的!
(2)服务端:服务端主要实现对两个类继承,WebSocketServlet(httpServlet的子类)和StreamInbound。前者监听客户端的websocket连接请求,并执行createWebSocketInbound()方法返回一个StreamInbound对象;
后者与客户端进行通信,一般都是采用其子类MessageInbound;
监听websocket连接请求:
package com.websocket.servlet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
public class HelloWebSocketServlet extends WebSocketServlet {
private static final long serialVersionUID = 1L;
private final AtomicInteger connectionIds = new AtomicInteger(0);
@Override
protected StreamInbound createWebSocketInbound(String arg0,
HttpServletRequest request) {
return new HelloMessageInbound(connectionIds.getAndIncrement(), request
.getSession().getId());
}
}
与客户端进行通信:
package com.websocket.servlet;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.sql.Time;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;
public class HelloMessageInbound extends MessageInbound {
private String WS_NAME;
private final String FORMAT = "%s : %s";
private final String PREFIX = "ws_";
private String sessionId = "";
private String filename = "";
public HelloMessageInbound(int id, String _sessionId) {
this.WS_NAME = PREFIX + id;
this.sessionId = _sessionId;
}
@Override
protected void onClose(int status) {
System.out.println(String.format(FORMAT, WS_NAME, "closing ......"));
super.onClose(status);
}
@Override
protected void onOpen(WsOutbound outbound) {
super.onOpen(outbound);
try {
send(this.sessionId + "连接成功!");
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
private void send(String message) throws IOException {
message = String.format(FORMAT, WS_NAME, message);
System.out.println(message);
getWsOutbound().writeTextMessage(CharBuffer.wrap(message));
}
@Override
protected void onBinaryMessage(ByteBuffer arg0) throws IOException {
// TODO Auto-generated method stub
System.out.println("this is BinaryMessage:"+arg0.toString());
}
@Override
protected void onPong(ByteBuffer payload) {
// TODO Auto-generated method stub
super.onPong(payload);
System.out.println("this is Pong:"+payload.toString());
}
@Override
protected void onTextMessage(CharBuffer arg0) throws IOException {
// TODO Auto-generated method stub
filename = arg0.toString();
System.out.println("hello");
System.out.println("this is TextMessage:"+arg0.toString());
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// // TODO Auto-generated method stub
// Integer cacheTime = 1000;
// Timer timer = new Timer();
// timer.schedule(new TimerTask() {
//
// @Override
// public void run() {
// // TODO Auto-generated method stub
// Date date = new Date();
// SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss");
// try {
// send(sd.format(date));
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
// }, 5000, cacheTime);
// }
// }).start();
}
}
其中,onOpen()在websocket连接成功时执行,onClose()在websocket连接断开时执行,onTextMessage()用于接收客户端发送的文本消息,onBinaryMessage()用于接收客户端发送的二进制消息,websocket1.0版本
向客户端发送消息getWsOutbound().writeTextMessage(CharBuffer.wrap(message));,websocket1.1向客户端发送消息,从session中获取session.getAsyncRemote().sendText(arg0);
好了,这就是我对websocket的研究结果,比较浅显,有需要的还可以查看Java Websocket API,里面很详细描述了各个类的继承关系和方法的使用。