zoukankan      html  css  js  c++  java
  • tomcat7.027webSocket应用程序构建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;
    
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    			throws ServletException, IOException {
    		theUser = (OnLineUser) req.getSession().getAttribute("loginUser");
    		super.doGet(req, resp);
    	}
    
    	@Override
    	protected StreamInbound createWebSocketInbound(String subProtocol) {
    		return new ChatMessageInbound(theUser);
    	}
    
    }
    

      

     doget不用说,是连接的开始,然后取出登录的用户,这个是为了管理连接使用的,你在看这个例子的时候不需要doget方法和theUser声明,只要有createWebSocketInbound方法就行。上面说了。这个方法是webSocket的入口。其实也是WebSocketServlet这个类写好的doget,我们看WebSocketServlet的doget是如何写的

    @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
    
            // Information required to send the server handshake message
            String 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方法,我们重写这个方法。

    @Override
    	protected StreamInbound createWebSocketInbound(String subProtocol) {
    		return new ChatMessageInbound(theUser);
    	}
    

      

    上面的ChatMessageInbound是我自己定义的继承类。

    public final class ChatMessageInbound extends MessageInbound {
    
    	public ChatMessageInbound(OnLineUser theUser) {
    		this.theUser = theUser;
    	}
    
    	@Override
    	protected void onOpen(WsOutbound outbound) {
    		// 添加链接到容器
    		ChatMessageInbound theBound = this;
    		ChatContainer.addInbound(theBound.theUser, theBound);
    		// 向每个在线用户发送消息
    		ChatContainer.eachAllBound(new ContainerCallBack() {
    			@Override
    			public void eachCallBack(ChatMessageInbound theBound, OnLineUser theUser) {
    				ListUserMsg listUserMsg = new ListUserMsg(ChatContainer.getUserList());
    				WriteTookit.writeToBound(theBound, listUserMsg.toMsg());
    			}
    		});
    	}
    
    	@Override
    	protected void onClose(int status) {
    		ChatContainer.removeInbound(theUser);
    	}
    
    	@Override
    	protected void onBinaryMessage(ByteBuffer message) throws IOException {
    	}
    
    	@Override
    	protected 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) {
    	//用户信息列表
    
    };
    

      

     这些代码只是参考内容,实际上不可能拷贝下来直接运行,

    如果有什么不理解的地方可以参看,有什么不对希望指出。有什么疑问希望提出。

    天行健君子以自强不息。
  • 相关阅读:
    虚函数和纯虚函数
    MS CRM 2011中PartyList类型字段的实例化
    MS CRM 2011的自定义与开发(12)——表单脚本扩展开发(4)
    MS CRM 2011的自定义与开发(12)——表单脚本扩展开发(2)
    MS CRM 2011的自定义和开发(10)——CRM web服务介绍(第二部分)——IOrganizationService(二)
    MS CRM 2011 SDK 5.08已经发布
    MS CRM 2011 Q2的一些更新
    最近很忙
    Microsoft Dynamics CRM 2011最近的一些更新
    补一篇,Update Rollup 12 终于发布了
  • 原文地址:https://www.cnblogs.com/mrye/p/2499294.html
Copyright © 2011-2022 走看看