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) {
    	//用户信息列表
    
    };
    

      

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

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

    天行健君子以自强不息。
  • 相关阅读:
    2019-2020-2 网络对抗技术 20175232 司浩楠 Exp2 后门原理与实践
    2019-2020-2 网络对抗技术 20175232 司浩楠 Exp1 PC平台逆向破解
    2019-2020-1 20175232 20175233 《信息安全系统设计基础》实验五 通讯协议设计
    Linux中Qt的安装
    面向对象编程与面向过程编程的区别与联系
    Web服务器端程序的实现
    Web服务器文件传输程序客户端程序实现
    Web服务器实现文件传输程序设计
    屏蔽信号的多路选择I/O
    浅谈HTTP请求与响应
  • 原文地址:https://www.cnblogs.com/mrye/p/2499294.html
Copyright © 2011-2022 走看看