Web 應用的信息交互過程一般是客戶端經過瀏覽器發出一個請求,服務器端接收和審覈完請求後進行處理並返回結果給客戶端,而後客戶端瀏覽器將信息呈現出來,這種機制對於信息變化不是特別頻繁的應用尚能相安無事,可是對於那些實時要求比較高的應用來講,好比說在線遊戲、在線證券、設備監控、新聞在線播報、RSS 訂閱推送等等,當客戶端瀏覽器準備呈現這些信息的時候,這些信息在服務器端可能已通過時了。因此保持客戶端和服務器端的信息同步是實時 Web 應用的關鍵要素,對 Web 開發人員來講也是一個難題。在 WebSocket 規範出來以前,開發人員想實現這些實時的 Web 應用,不得不採用一些折衷的方案,其中最經常使用的就是輪詢 (Polling) 和 Comet 技術(AJAX)。html
但AJAX有顯著缺點:java
一、瀏覽器須要不斷的向服務器發出請求,然而HTTP request 的header是很是長的,裏面包含的有用數據可能只是一個很小的值,這樣會佔用不少的帶寬。web
二、客戶端和服務器端的編程實現都比較複雜,在實際的應用中,爲了模擬比較真實的實時效果,開發人員每每須要構造兩個 HTTP 鏈接來模擬客戶端和服務器之間的雙向通信,一個鏈接用來處理客戶端到服務器端的數據傳輸,一個鏈接用來處理服務器端到客戶端的數據傳輸。編程
WebSocket API是下一代客戶端-服務器的異步通訊方法。該通訊取代了單個的TCP套接字,使用ws或wss(ssl加密)協議,可用於任意的客戶端和服務器程序。WebSocket目前由W3C進行標準化。WebSocket已經受到Firefox 四、Chrome 四、Opera 10.70、Edge以及Safari 5等瀏覽器的支持。api
WebSocket API最偉大之處在於服務器和客戶端能夠在給定的時間範圍內(IdleTime)的任意時刻,相互推送信息。WebSocket並不限於以Ajax(或XHR)方式通訊,由於Ajax技術須要客戶端發起請求,而WebSocket服務器和客戶端能夠彼此相互推送信息;XHR受到域的限制,而WebSocket容許跨域通訊。跨域
爲方便開發,用了Jetty服務器,在網上不少例子都是基於jetty7.0的。但jetty9.0之後WebSocket的基類代碼變化不少,因而本身動手開發,參考jetty官網:瀏覽器
http://www.eclipse.org/jetty/documentation/current/websocket-jetty.html#jetty-websocket-api服務器
首先在Eclipse新建一個Dynamic web projectwebsocket
請自行下載安裝jetty,另外須要在Eclipse安裝Jetty工具,便於調試。session
工程目錄結構:
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>debug</param-name> <param-value>false</param-value> </context-param> <session-config> <!-- 10 minutes --> <session-timeout>10</session-timeout> </session-config> <servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/wsexample</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
ant用慣了,懶得去查maven的dependencies了。
build.xml
jetty.lib.dir、deploy.path請自行修改
<?xml version="1.0" encoding="UTF-8"?> <project name="WebSocket" default="compile"> <property name="name" value="ws"/> <property environment="env"/> <property name="src.dir" value="src"/> <property name="web.dir" value="WebContent" /> <property name="build.dir" location="${web.dir}/WEB-INF/classes"/> <property name="jetty.lib.dir" location="D:/Software/jetty93/lib"/> <property name="dist.dir" location="dist"/> <property name="deploy.path" location="D:/Software/jetty93/webapps"/> <path id="compile.classpath"> <fileset dir="${jetty.lib.dir}"/> </path> <target name="init"> <mkdir dir="${build.dir}"/> <mkdir dir="${dist.dir}"/> </target> <target name="compile" depends="init"> <javac srcdir="${src.dir}" destdir="${build.dir}" includeantruntime="false"> <classpath refid="compile.classpath"/> </javac> <echo>Compilation completed</echo> </target> <target name="archive" depends="compile"> <war destfile="${dist.dir}/${name}.war" needxmlfile="false"> <fileset dir="${web.dir}"/> </war> <echo>Archive created</echo> </target> <target name="clean" depends="init"> <delete dir="${build.dir}"/> <delete dir="${dist.dir}"/> <echo>Cleaning completed</echo> </target> <target name="deploy" depends="archive"> <copy file="${dist.dir}/${name}.war" overwrite="true" todir="${deploy.path}"/> <echo>Archive deployed</echo> </target> </project>
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>你好!websocket</title> </head> <body> hello,WebSocket <script src="index.js"></script> </body> </html>
index.js
var ws = new WebSocket("ws://localhost:8080/ws/wsexample"); ws.onopen = function() { document.write("WebSocket opened <br>"); ws.send("Hello Server"); }; ws.onmessage = function(evt) { document.write("Message: " + evt.data); }; ws.onclose = function() { document.write("<br>WebSocket closed"); }; ws.onerror = function(err) { document.write("Error: " + err); };
其中 ws://localhost:8080/ws/wsexample 是WebSocket 服務器地址,是服務器端的Servelt,經過MyServlet.java實現
WebSocketServlet 類中 configure(WebSocketServletFactory factory) 用來註冊socket處理類和設置鏈接時長,在這個時長內,保持長鏈接。
官網的定義:
http://www.eclipse.org/jetty/documentation/current/jetty-websocket-server-api.html
MyServlet.java
暫不理會HTTP GET請求的處理.
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.annotation.WebServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @WebServlet(name = "MyServlet", urlPatterns = { "/wsexample" }) public class MyServlet extends WebSocketServlet { private static final long serialVersionUID = 1L; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().println("HTTP GET method not implemented."); } @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(10000); factory.register(MySocket.class); } }
MySocket.java 是socket具體處理類,寫法在jetty7和jetty9有很大的不一樣。
import java.io.IOException; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @WebSocket public class MySocket { Session WebSession; @OnWebSocketClose public void onClose(int statusCode, String reason) { System.out.println("Close: " + reason); } @OnWebSocketError public void onError(Throwable t) { System.out.println("Error: " + t.getMessage()); } //客戶端鏈接 @OnWebSocketConnect public void onConnect(Session session) { WebSession = session; System.out.println("Connect: " + session.getRemoteAddress().getAddress()); try { session.getRemote().sendString("你好!客戶端。我是服務端。<br>"); } catch (IOException e) { System.out.println("IO Exception"); } } //接收到客戶端的文本消息 @OnWebSocketMessage public void onMessage(String message) { System.out.println("客戶端消息: " + message); try { WebSession.getRemote().sendString("客戶端,我收到這條消息了:" + message); } catch (IOException e) { System.out.println("IO Exception"); } } }
測試一下吧
客戶端運行結果:
完整源代碼下載 : source code