用jetty搭建websocket服务并与ie78兼容的方法

openkk 12年前

用jetty搭建websocket服务并与ie78兼容的方法

jetty8中已经自带有websocket功能,所以我们可以很方便搭建一个自己的websocket服务。

源程序:http://sdrv.ms/N5BuKw

启动类:org.noahx.websocket.WebSocketServer

访问地址:http://127.0.0.1:8085/test.html

1、外部依赖包如下(maven)

<?xml version="1.0" encoding="UTF-8"?>  <project xmlns="http://maven.apache.org/POM/4.0.0"           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">      <modelVersion>4.0.0</modelVersion>        <groupId>org.noahx</groupId>      <artifactId>ws-test</artifactId>      <version>1.0</version>        <properties>          <jetty.version>8.1.5.v20120716</jetty.version>          <slf4j.version>1.6.1</slf4j.version>      </properties>        <dependencies>            <dependency>              <groupId>org.slf4j</groupId>              <artifactId>slf4j-log4j12</artifactId>              <version>${slf4j.version}</version>          </dependency>            <dependency>              <groupId>org.eclipse.jetty</groupId>              <artifactId>jetty-server</artifactId>              <version>${jetty.version}</version>          </dependency>            <dependency>              <groupId>org.eclipse.jetty</groupId>              <artifactId>jetty-websocket</artifactId>              <version>${jetty.version}</version>          </dependency>        </dependencies>    </project>

2、搭建的websocket server,写一个服务器启动类

 WebSocketServer

package org.noahx.websocket;    import org.eclipse.jetty.server.Server;  import org.eclipse.jetty.server.handler.ResourceHandler;  import org.eclipse.jetty.util.resource.FileResource;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;    import java.io.IOException;  import java.net.URISyntaxException;  import java.net.URL;    /**   * Created with IntelliJ IDEA.   * User: noah   * Date: 8/8/12   * Time: 5:10 PM   * To change this template use File | Settings | File Templates.   */  public class WebSocketServer {        private Logger logger = LoggerFactory.getLogger(this.getClass());        private Server server;        private FlashPolicyServer fpServer;        private int port;        public static void main(String[] args) {            WebSocketServer server = new WebSocketServer(8085);          server.start();        }        public WebSocketServer(int port) {          this.port=port;      }        public void start(){            fpServer=new FlashPolicyServer(10843);          fpServer.start();          server = new Server(port);            MyWebSocketHandler myWebSocketHandler = new MyWebSocketHandler();            URL url=this.getClass().getClassLoader() .getResource("org/noahx/websocket/http");            ResourceHandler resourceHandler=new ResourceHandler();          try {              resourceHandler.setBaseResource(new FileResource(url));          } catch (IOException e) {              e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.          } catch (URISyntaxException e) {              e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.          }            myWebSocketHandler.setHandler(resourceHandler);            server.setHandler(myWebSocketHandler);            try {              server.start();          server.join();          } catch (Exception e) {              logger.error(e.getMessage(),e);          }      }  }

websocket服务端口我设置的为8085。通过向jetty server中加入WebSocketHandler就可以提供websocket服务了。由于WebSocketHandler是HandlerWrapper的子类,所以这个handler中还可以再加入一个ResourceHandler。这样就可以在一个端口上同时提供websocket与http服务。ResourceHandler指向了类路径org/noahx/websocket/http下,这个包下的所有资源都将可以发布为web资源给http请求。

3、开发MyWebSocketHandler与MyWebSocket

MyWebSocketHandler与MyWebSocket

package org.noahx.websocket;    import org.eclipse.jetty.websocket.WebSocket;  import org.eclipse.jetty.websocket.WebSocketHandler;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;    import javax.servlet.http.HttpServletRequest;  import java.io.IOException;  import java.util.Timer;    /**   * Created with IntelliJ IDEA.   * User: noah   * Date: 8/8/12   * Time: 5:16 PM   * To change this template use File | Settings | File Templates.   */  public class MyWebSocketHandler extends WebSocketHandler {        private Logger logger = LoggerFactory.getLogger(this.getClass());        @Override      public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {            if (logger.isDebugEnabled()) {              logger.debug("url=" + request.getRequestURL() + ",protocol=" + protocol);          }            return new MyWebSocket();      }        public class MyWebSocket implements WebSocket.OnTextMessage {            private Logger logger = LoggerFactory.getLogger(this.getClass());            private Connection connection;            private Timer timer = new Timer();              @Override          public void onMessage(String data) {              if (logger.isDebugEnabled()) {                  logger.debug("onMessage");              }          }            @Override          public void onOpen(Connection connection) {              if (logger.isDebugEnabled()) {                  logger.debug("onOpen");              }              this.connection = connection;                timer.schedule(new MemTask(this), 0, 500);          }            @Override          public void onClose(int closeCode, String message) {              if (logger.isDebugEnabled()) {                  logger.debug("onClose");              }              timer.cancel();          }            public void send(String msg) {              try {                  if (logger.isDebugEnabled()) {                      logger.debug("send:" + msg);                  }                  connection.sendMessage(msg);              } catch (IOException e) {                  logger.error(e.getMessage(), e);                  timer.cancel();              }          }        }  }

doWebSocketConnect中实现doWebSocketConnect方法,返回我们需要的WebSocket对象。
doWebSocketConnect方法中可以取到请求的url,所以可以当做分发器,通过url的不同分发到不同WebSocket。

如:ws://127.0.0.1:8085/ws1与ws://127.0.0.1:8085/ws2

MyWebSocket所实现的内容是以每500毫秒的速度,向浏览器发送0-100的随机数(注意这里是由服务器主动推送)。

MemTask

package org.noahx.websocket;    import java.util.Random;  import java.util.TimerTask;    /**   * Created with IntelliJ IDEA.   * User: noah   * Date: 8/8/12   * Time: 5:31 PM   * To change this template use File | Settings | File Templates.   */  public class MemTask extends TimerTask {        private MyWebSocketHandler.MyWebSocket myWebSocket;        public MemTask(MyWebSocketHandler.MyWebSocket myWebSocket) {          this.myWebSocket = myWebSocket;      }        @Override      public void run() {          myWebSocket.send("" +new Random().nextInt(100));        }    }

取随机数的任务类

4、开发前台javascript客户机(test.html) 

使用到了以下内容:

web-socket-js,https://github.com/gimite/web-socket-js/
就是这个解决了不支持html5的websocket的浏览器也可以调用websocket的问题。web-socket-js会自动判断是不是支持html5的websocket,如果支持没有什么区别。如果发现不支持将通过Flash自动调用websocket来做socket的中转。

highcharts,http://www.highcharts.com/
这个是通过纯javascript(jquery)来绘制图表的js图表框架。来配合websocket,做到实时的动态图表。

test.html

<html>  <head>      <title></title>        <script type="text/javascript" src="swfobject.js"></script>      <script type="text/javascript" src="web_socket.js"></script>        <script type="text/javascript" src="jquery-1.7.2.min.js"></script>      <script src="hc/highcharts.js"></script>      <script src="hc/modules/exporting.js"></script>      </head>  <body>    <script type="text/javascript">        var host = window.location.host.split(":")[0];          WEB_SOCKET_SWF_LOCATION = "WebSocketMain.swf";      WEB_SOCKET_DEBUG = false;      try {          WebSocket.loadFlashPolicyFile("xmlsocket://" + host + ":10843");      } catch (e) {      }        var ws;        function init(series) {            ws = new WebSocket("ws://" + host + ":8085/");            ws.onopen = function () {              output("onOpen");          };          ws.onmessage = function (e) {              var dStr = e.data;              outputmem(dStr);                var x = (new Date()).getTime(), // current time                      y = parseInt(dStr);              series.addPoint([x, y], true, true);          };          ws.onclose = function () {              output("onClose");          };          ws.onerror = function () {              output("onError");          };        }        function outputmem(str) {          var mem = document.getElementById("mem");          mem.innerHTML = str;      }        function output(str) {            var log = document.getElementById("log");            var escaped = str.replace(/&/, "&amp;").replace(/</, "&lt;").                  replace(/>/, "&gt;").replace(/"/, "&quot;"); // "          log.innerHTML = escaped + "<br>" + log.innerHTML;      }        $(function () {          $(document).ready(function () {              Highcharts.setOptions({                  global:{                      useUTC:false                  }              });                var chart;              chart = new Highcharts.Chart({                  chart:{                      renderTo:'container',                      type:'spline',                      marginRight:10,                      events:{                          load:function () {                                // set up the updating of the chart each second                              var series = this.series[0];                              init(series);                          }                      }                  },                  title:{                      text:'WebSocket random data'                  },                  xAxis:{                      type:'datetime',                      tickPixelInterval:150                  },                  yAxis:{                      title:{                          text:'Value'                      },                      plotLines:[                          {                              value:0,                              width:1,                              color:'#808080'                          }                      ]                  },                  tooltip:{                      formatter:function () {                          return '<b>' + this.series.name + '</b><br/>' +                                  Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +                                  Highcharts.numberFormat(this.y, 2);                      }                  },                  legend:{                      enabled:false                  },                  exporting:{                      enabled:false                  },                  series:[                      {                          name:'Zero data',                          data:(function () {                              var data = [],                                      time = (new Date()).getTime(),                                      i;                                for (i = -19; i <= 0; i++) {                                  data.push({                                      x:time + i * 1000,                                      y:0                                  });                              }                              return data;                          })()                      }                  ]              });          });        });    </script>    <div id="mem"></div>    <div id="log"></div>      <div id="container" style="min-width: 400px; height: 400px; margin: 0 auto"></div>    </body>  </html>

原理是通过web-socket-js建立与服务器的websocket连接,服务器发现连接后会主动推送数据给浏览器。

收到服务器推送过来的数据会触发onmessage,这时在onmessage中对图表进行绘制。从而达到效果。

*5、Flash Policy Server的配置

ie78可以使用websocket的关键。web-socket-js会调用flash请求websocket。

flash请求websocket也有一个前提。flash会检查安全策略文件(xml)是否允许它这样做。默认flash会请求相同host的843端口,发送policy-file-request请求,这时返回正常的安全策略时,websocket才可以正常使用。当然我们也可以通过WebSocket.loadFlashPolicyFile(方法重新指定策略文件url。样例是指向了10843端口,因为如果监听<1000的端口需要root权限,所以内嵌Java版的Flash Policy Server端口为10843。

但还是建议不要手动指定WebSocket.loadFlashPolicyFile,而是用843。因为flash总是先会从843找,如果找不到再从websocket端口找(8085)。所以如果设置其它url了会影响加载速度。

Flash Policy的资料可以参考:http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
里面提供了一个flashpolicyd_v0.6程序,可以做Flash Policy Server,但不是java实现的。

我也写了一个Flash Policy Server,java版的

FlashPolicyServer

package org.noahx.websocket;    import org.slf4j.Logger;  import org.slf4j.LoggerFactory;    import java.io.IOException;  import java.io.InputStream;  import java.io.OutputStream;  import java.net.ServerSocket;  import java.net.Socket;  import java.net.SocketException;    /**   * Created with IntelliJ IDEA.   * User: noah   * Date: 8/8/12   * Time: 10:05 PM   * To change this template use File | Settings | File Templates.   */  public class FlashPolicyServer {        private ServerSocket serverSocket;        private static Thread serverThread;        private int port;        private static boolean listening = true;        private Logger logger = LoggerFactory.getLogger(this.getClass());        public FlashPolicyServer() {          this(843);      }        public FlashPolicyServer(int port) {          this.port = port;      }        public void start() {            try {              serverThread = new Thread(new Runnable() {                  public void run() {                      try {                            logger.info("FlashPolicyServer: Starting...");                          serverSocket = new ServerSocket(port);                            while (listening) {                              final Socket socket = serverSocket.accept();                                Thread t = new Thread(new Runnable() {                                  public void run() {                                      try {                                            if (logger.isDebugEnabled()) {                                              logger.debug("FlashPolicyServer: Handling Request...");                                          }                                            socket.setSoTimeout(10000);                                            InputStream in = socket.getInputStream();                                            byte[] buffer = new byte[23];                                            if (in.read(buffer) != -1 && (new String(buffer, "ISO-8859-1")).startsWith("<policy-file-request/>")) {                                                if (logger.isDebugEnabled()) {                                                  logger.debug("PolicyServerServlet: Serving Policy File...");                                              }                                                OutputStream out = socket.getOutputStream();                                                  byte[] bytes = ("<?xml version=\"1.0\"?>\n" +                                                      "<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\n" +                                                      "<cross-domain-policy> \n" +                                                      "   <site-control permitted-cross-domain-policies=\"master-only\"/>\n" +                                                      "   <allow-access-from domain=\"*\" to-ports=\"*\" />\n" +                                                      "</cross-domain-policy>").getBytes("ISO-8859-1");                                                out.write(bytes);                                                out.write(0x00);                                                out.flush();                                              out.close();                                          } else {                                              logger.warn("FlashPolicyServer: Ignoring Invalid Request");                                              logger.warn("  " + (new String(buffer)));                                          }                                        } catch (SocketException e) {                                          logger.error(e.getMessage(), e);                                      } catch (IOException e) {                                          logger.error(e.getMessage(), e);                                      } finally {                                          try {                                              socket.close();                                          } catch (Exception ex2) {                                          }                                      }                                  }                              });                              t.start();                          }                      } catch (IOException ex) {                          logger.error("PolicyServerServlet Error---");                          logger.error(ex.getMessage(), ex);                      }                  }                });              serverThread.start();          } catch (Exception ex) {              logger.error("PolicyServerServlet Error---");              logger.error(ex.getMessage(), ex);          }        }        public void stop() {          logger.info("FlashPolicyServer: Shutting Down...");            if (listening) {              listening = false;          }            if (!serverSocket.isClosed()) {              try {                  serverSocket.close();              } catch (Exception ex) {              }          }      }  }

这样就可以和websocket server整合在一起。

安全策略文件的样例格式:

<?xml version="1.0"?>  <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">  <cross-domain-policy>      <site-control permitted-cross-domain-policies="master-only"/>     <allow-access-from domain="*" to-ports="*" />  </cross-domain-policy>

6、websocket展望

继ajax后websocket会给界面带来更快更好的交互效果与体验,伴随移动市场的壮大websocket也将有可能成为标准的api协议。

框架介绍:

AS3WebSocket,https://github.com/Worlize/AS3WebSocket
提供给ActiveScript3使用的websocket框架

jWebSocket,http://jwebsocket.org/
有服务器与客户端,高一级别websocket框架,提供了对基础websocket的扩展,如jsonSocket,xmlSocket,cvsSocket

kaazing(商业),http://kaazing.com/
商业级整套html5与websocket解决方案,效果最好,可惜是商业的