tomcat7.027-webSocket应用程序构建01
前幾天研究了一下tomcat7.027的webSocket實現。簡單看了下官方源碼自己實踐了一下。
在這里簡單介紹一下tomcat的webSocketAPI使用。
在這里啰嗦幾句:【
很多朋友聽說webSocket不知道是什么。知道是什么不知道怎么用,知道怎么用不知道具體實現。其實我當初也是這樣。
實際上webSocket可以簡單的理解為用瀏覽器與服務器簡歷socket連接,但是用了一個特殊的協議,偶收協議,它與http協議發送的報頭不一樣。
websocket需要服務器和瀏覽器支持,瀏覽器不支持,也就無法使用這個技術。服務器可以自己實現協議連接,但是我們不準備自己實現(其實看需求,至少對我來說不需要),當然目前javaEE官方不支持這個實現,沒有規范(據說jsr356準備支持,期待來年【2013】javaEE7吧)
目前實現的java服務端第三方webSocketAPI不算少,比如jetty就是一種(多的我也舉例不了,我只知道,沒研究過有多少實現。)tomcat也自帶了實現API
webSocket想要手動實現比較麻煩,可以看下tomcat實現過程,大致都一樣。
總之一句話,webSocket是一種客戶端與服務端連接socket的技術,實現即時消息,取代comet但是并沒廣泛只用,因為大多需要瀏覽器的支持,相對comet有很多優點,此處不舉例說明。可以自己google一下。
】
?
tomcat7.027如何實現webSocket程序:
總的來說,實現webSocket的servlet要繼承WebSocketServlet這個類。這個類是tomcat自己包裝的servlet。
所有的入口都在protected StreamInbound createWebSocketInbound(String subProtocol) {}這個方法。?也就是說,我們實現這個方法,就可以實現握手協議了。
注意看這個方法。?要求返回StreamInbound類型。這個類型我們需要繼承自己實現。打開源碼觀看這個類
有如下方法
?
/*** Intended to be overridden by sub-classes that wish to be notified* when the outbound connection is established. The default implementation* is a NO-OP.** @param outbound The outbound WebSocket connection.*/protected void onOpen(WsOutbound outbound) {// NO-OP}/*** Intended to be overridden by sub-classes that wish to be notified* when the outbound connection is closed. The default implementation* is a NO-OP.** @param status The status code of the close reason.*/protected void onClose(int status) {// NO-OP}/*** This method is called when there is a binary WebSocket message available* to process. The message is presented via a stream and may be formed from* one or more frames. The number of frames used to transmit the message is* not made visible to the application.** @param is The WebSocket message** @throws IOException If a problem occurs processing the message. Any* exception will trigger the closing of the WebSocket* connection.*/protected abstract void onBinaryData(InputStream is) throws IOException;/*** This method is called when there is a textual WebSocket message available* to process. The message is presented via a reader and may be formed from* one or more frames. The number of frames used to transmit the message is* not made visible to the application.** @param r The WebSocket message** @throws IOException If a problem occurs processing the message. Any* exception will trigger the closing of the WebSocket* connection.*/protected abstract void onTextData(Reader r) throws IOException;
?
?上面的方法都是要我們自己實現的。tomcat沒有給我們實現。
仔細看都是onXxx格式,類似事件監聽。其實也差不多。只是tomcat在得到消息或者鏈接發生變化的時候會去調用這些方法,實現方法“自動”觸發。
仔細看源碼還有很多函數可以使用,這里不一一介紹。感興趣可以打開源碼看看。
其實仔細看官方的例子,chat那個例子也能得到這個結論(tomcat的webSocket例子需要tomcat7.027才帶有)
我們定義一個servlet
?
@WebServlet(urlPatterns = { "/chatWebSocket" }) public class ChatWebSocketServlet extends WebSocketServlet {private static final long serialVersionUID = 1L;OnLineUser theUser;@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {theUser = (OnLineUser) req.getSession().getAttribute("loginUser");super.doGet(req, resp);}@Overrideprotected StreamInbound createWebSocketInbound(String subProtocol) {return new ChatMessageInbound(theUser);}}
?
?doget不用說,是連接的開始,然后取出登錄的用戶,這個是為了管理連接使用的,你在看這個例子的時候不需要doget方法和theUser聲明,只要有createWebSocketInbound方法就行。上面說了。這個方法是webSocket的入口。其實也是WebSocketServlet這個類寫好的doget,我們看WebSocketServlet的doget是如何寫的
?
@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {// Information required to send the server handshake messageString key;String subProtocol = null;List<String> extensions = Collections.emptyList();if (!headerContainsToken(req, "upgrade", "websocket")) {resp.sendError(HttpServletResponse.SC_BAD_REQUEST);return;}if (!headerContainsToken(req, "connection", "upgrade")) {resp.sendError(HttpServletResponse.SC_BAD_REQUEST);return;}if (!headerContainsToken(req, "sec-websocket-version", "13")) {resp.setStatus(426);resp.setHeader("Sec-WebSocket-Version", "13");return;}key = req.getHeader("Sec-WebSocket-Key");if (key == null) {resp.sendError(HttpServletResponse.SC_BAD_REQUEST);return;}String origin = req.getHeader("Origin");if (!verifyOrigin(origin)) {resp.sendError(HttpServletResponse.SC_FORBIDDEN);return;}List<String> subProtocols = getTokensFromHeader(req,"Sec-WebSocket-Protocol-Client");if (!subProtocols.isEmpty()) {subProtocol = selectSubProtocol(subProtocols);}// TODO Read client handshake - Sec-WebSocket-Extensions// TODO Extensions require the ability to specify something (API TBD)// that can be passed to the Tomcat internals and process extension// data present when the frame is fragmented.// If we got this far, all is good. Accept the connection.resp.setHeader("upgrade", "websocket");resp.setHeader("connection", "upgrade");resp.setHeader("Sec-WebSocket-Accept", getWebSocketAccept(key));if (subProtocol != null) {resp.setHeader("Sec-WebSocket-Protocol", subProtocol);}if (!extensions.isEmpty()) {// TODO}// Small hack until the Servlet API provides a way to do this.StreamInbound inbound = createWebSocketInbound(subProtocol);((RequestFacade) req).doUpgrade(inbound);}
?
注意倒數第三行,調用了createWebSocketInbound方法,我們重寫這個方法。
?
@Overrideprotected StreamInbound createWebSocketInbound(String subProtocol) {return new ChatMessageInbound(theUser);}
上面的ChatMessageInbound是我自己定義的繼承類。
?
public final class ChatMessageInbound extends MessageInbound {public ChatMessageInbound(OnLineUser theUser) {this.theUser = theUser;}@Overrideprotected void onOpen(WsOutbound outbound) {// 添加鏈接到容器ChatMessageInbound theBound = this;ChatContainer.addInbound(theBound.theUser, theBound);// 向每個在線用戶發送消息ChatContainer.eachAllBound(new ContainerCallBack() {@Overridepublic void eachCallBack(ChatMessageInbound theBound, OnLineUser theUser) {ListUserMsg listUserMsg = new ListUserMsg(ChatContainer.getUserList());WriteTookit.writeToBound(theBound, listUserMsg.toMsg());}});}@Overrideprotected void onClose(int status) {ChatContainer.removeInbound(theUser);}@Overrideprotected void onBinaryMessage(ByteBuffer message) throws IOException {}@Overrideprotected void onTextMessage(CharBuffer message) throws IOException { // CHAT_MODEL.setMessage(message.toString()); // ChatContainer.eachAllBound(new ContainerCallBack() { // @Override // public void eachCallBack(ChatMessageInbound theBound, OnLineUser theUser) { // WriteTookit.writeToBound(theBound, CHAT_MODEL.getSayMsg()); // } // });}// 變量區域private OnLineUser theUser; }
?這里只是簡單實現了一下,注釋部分只是處理這個方法的部分,那里是一個容器,存檔所有在線用戶。并且提供遍歷插入以及刪除等方法,在自己實現的時候完全不需要這么寫。
下面是容器代碼
?
public final class ChatContainer {/*** 保存服務器連接的用戶的容器*/private static final Map<OnLineUser, ChatMessageInbound> CHAT_MAP = new HashMap<OnLineUser, ChatMessageInbound>();/*** 取出用戶的連接*/public static ChatMessageInbound getInbound(OnLineUser theUser) {return CHAT_MAP.get(theUser);}/*** 放入一個連接*/public static void addInbound(OnLineUser theUser,ChatMessageInbound outbound) {CHAT_MAP.put(theUser, outbound);System.out.println(CHAT_MAP.size());}/*** 移除一個連接* * @param theUser* @return*/public static ChatMessageInbound removeInbound(OnLineUser theUser) {return CHAT_MAP.remove(theUser);}/*** 遍歷所有連接*/public static void eachAllBound(ContainerCallBack callBackInter) {Iterator<OnLineUser> keyIter = CHAT_MAP.keySet().iterator();while (keyIter.hasNext()) {OnLineUser theUser = keyIter.next();callBackInter.eachCallBack(CHAT_MAP.get(theUser), theUser);}}/*** 回調函數的接口* * @author WangZhenChong*/public interface ContainerCallBack {void eachCallBack(ChatMessageInbound theBound, OnLineUser theUser);}}
?
?
我定義了一種數據交約定,使用json 字符串,MsgType表示消息類型,類似windows的消息機制
?
/*** 前臺和后臺交互的信息類型常量* * @author WangZhenChong* */ public final class MsgTypeConstants {public static short GET_USER_LIST = 1;// 在線所有用戶信息交互public static short SEND_ONE_TO_ONE = 2;// 對一個用戶發送消息public static short SEND_ONE_TO_ALL = 3;// 對所有用戶發送消息public static short SEND_SERVER_MSG = 4;// 發送系統消息 }
?余下的msgContent就是消息內容,比如列出現在用戶這個內容就是[...,...,...,...]發送消息就是消息模型的內容。
這樣解決單通道多操作的方法。
?
?
下面列出前臺js核心內容。
使用jquery
?
$(document).ready(function() {$("#connBtn").bind('click', function() {$.ajax({url : "/tomcatWebSocket/Login#?asdasdasd",type : "POST",processData : false,data : $.param({username : document.getElementById("usernameField").value}),success : function(msg, status) {initChat();initUserList();$("#sendBtn").removeAttr("disabled");$("#connBtn").attr("disabled", "disabled");$("#usernameField").attr("disabled", "disabled");},error : function(jqXHR, textStatus, errorThrown) {alert("服務器內部錯誤");}});});var Chat = {}; Chat.socket = null; function initChat() {var wsURL = 'ws://' + window.location.host+ '/tomcatWebSocket/chatWebSocket';if ('WebSocket' in window) {Chat.socket = new WebSocket(wsURL);} else if ('MozWebSocket' in window) {Chat.socket = new MozWebSocket(wsURL);} else {alert("瀏覽器不支持");return false;}Chat.socket.onopen = function() {};Chat.socket.onclose = function() {Chat.writeToConsole("斷開連接了 ");initChat();};Chat.socket.onmessage = function(message) {if (typeof message.data == "string") {// 如果發送的是字符串信息.var msgObj = eval("(" + message.data + ")");switch (msgObj.MsgType) {case MsgTypeConstants.GET_USER_LIST :// 所有用戶信息Chat.preUserList(msgObj.userList);break;case MsgTypeConstants.SEND_ONE_TO_ALL :Chat.writeToConsole(msgObj.msgContext);break;default :alert("未知錯誤,請刷新頁面");}}};Chat.sendMessage = function() {Chat.socket.send(ueditor.getContentTxt());}; }Chat.writeToConsole = function(message) { //往控制臺打印得到的聊天消息 };/*** 處理刷新用戶信息的方法。*/ Chat.preUserList = function(userList) {//用戶信息列表};
?這些代碼只是參考內容,實際上不可能拷貝下來直接運行,
如果有什么不理解的地方可以參看,有什么不對希望指出。有什么疑問希望提出。
?
轉載于:https://www.cnblogs.com/mrye/archive/2012/05/14/2499294.html
總結
以上是生活随笔為你收集整理的tomcat7.027-webSocket应用程序构建01的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一句话评论设计模式六大原则
- 下一篇: 簡單安裝軟件 GNU Linux