基于embedded jetty server的websocket开发
websocket提供一种在浏览器和服务器之间的全双工通信(full-duplex),替代了传统的轮询(polling)做法。目前,在一些web-base的富文本编辑器中有广泛的应用,例如Etherpad,石墨文档,有道云笔记等。具体的关于websocket的定义请参见:
http://baike.baidu.com/view/3623887.htm 和
https://en.wikipedia.org/wiki/WebSocket。下文是一个基于embedded jetty server的websocket开发的实例,供大家参考。
基本架构:
浏览器(javasscript) --(websocket with ws/wss)-->
nginx --(websocket with ws/wss)-->
embedded Jetty Server
服务端开发:
关于Jetty Server的介绍,请参见
http://www.eclipse.org/jetty/。以下是一些实际的使用经验和技巧,其中包括:
建立服务器端链接侦听(包括ws和wss),
创建消息处理句柄(其中包括WebSocketServlet和WebSocketAdapter)
- 构建server端的websocket链接
本实例采用ServerConnector来建立server端链接侦听。代码示例如下:
HttpConfiguration http_config = new HttpConfiguration();
ServerConnector http = new ServerConnector(
websocketServer, new HttpConnectionFactory(http_config));
http.setHost(serverName);
http.setPort(serverPort);
websocketServer.setConnectors(new Connector[]{http});
以上代码基于ws(相当于http),如果需要建立wss链接(相当于https),需要设置KeyStore,代码示例如下:
HttpConfiguration https_config = new HttpConfiguration();
https_config.setSecureScheme("https");
https_config.setSecurePort(securityPort);
https_config.setSendServerVersion(true);
https_config.setSendDateHeader(false);
https_config.addCustomizer(new SecureRequestCustomizer());
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(".\\ssl\\keystore");
sslContextFactory.setTrustStorePath(".\\ssl\\keystore");
// key store password when generate the store
sslContextFactory.setKeyStorePassword("testPassword");
sslContextFactory.setExcludeCipherSuites(
"SSL_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
ServerConnector sslConnector = new ServerConnector(websocketServer,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(https_config));
sslConnector.setPort(securityPort);
websocketServer.setConnectors(new Connector[]{sslConnector});
- 建立客户端消息处理句柄
本实例采用ServletContextHandler来处理发自客户端(javascript)的消息。注:同时引入ContextHandlerCollection,添加一个healthStatus的句柄,来监控jettyServer的运行状态。示例代码如下:
// Add health contextHandler
ContextHandler healthHandler = createHealthStatusHandler();
// Add websocket contextHandler
ServletContextHandler websocketHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
websocketHandler.setContextPath("/");
ServletHolder wsHolder = new ServletHolder(
"collabWebsocket",
new TestCollabSocketServlet());
websocketHandler.addServlet(wsHolder, "/");
// merge two contextHandlers
ContextHandlerCollection handlers = new ContextHandlerCollection();
handlers.setHandlers(new Handler[]{websocketHandler, healthHandler});
websocketServer.setHandler(handlers);
- 实现WebSocket Serverlet和Adapter:
public class TestCollabSocketServlet extends WebSocketServlet {
public TestCollabSocketServlet() {
}
@Override
public void configure(WebSocketServletFactory factory) {
factory.register(TestCollabSocketAdapter.class);
}
}
public class TestCollabSocketAdapter extends WebSocketAdapter{
public void onWebSocketClose(int statusCode, String reason){
// 处理关闭事件
}
public void onWebSocketConnect(Session websocketSession) {
// 处理链接事件
}
public void onWebSocketText(String msgText) {
// 处理接收到的消息
}
}
客户端(javascript开发):
- 建立和server的链接:
// 这个URL类似 ws://richEditor.com/collab 或者wss://richEditor.com/collab
that._websocket = new WebSocket(websocketURL);
- 注册websocket事件,包括open,close,error和message
that._websocket.onopen = that.onOpen.bind(that);
that._websocket.onclose = that.onClose.bind(that);
that._websocket.onmessage = that.onMessage.bind(that);
that._websocket.onerror = that.onError.bind(that);
onOpen: function() {
// 处理链接打开事件
},
onClose: function() {
// 处理链接关闭事件
},
onMessage: function() {
// 处理接受的消息
},
onError: function() {
// 处理链接失败事件
},
总结:
整体来说,这套代码实现相对简单,对于百人级别的并发也可以从容处理。建议:
1, 对于wss的支持,只需要实现从客户端到nginx这一层即可,nginx和Jetty Sever基本同处于内网,可以使用ws连接。
2,可以对传送消息进行一定的压缩,来降低对于带宽的依赖
3,实现自定义的ping/pong消息,用于保证web socket长连接的持续有效(nginx默认会关闭1分钟内没有消息通信的链接)