[置顶] WEBSOKET服务器搭建
简单介绍一下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.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 {
- ;// 在线所有用户信息交互
- ;// 对一个用户发送消息
- ;// 对所有用户发送消息
- ;// 发送系统消息
- }
余下的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) {
- <span style="white-space: pre;"> </span>//往控制台打印得到的聊天消息
- };
- /**
- * 处理刷新用户信息的方法。
- */
- Chat.preUserList = function(userList) {
- //用户信息列表
- };
这些代码只是参考内容,实际上不可能拷贝下来直接运行,
[置顶] WEBSOKET服务器搭建的更多相关文章
- [置顶] SVN服务器搭建和使用
Subversion是优秀的版本控制工具,其具体的的优点和详细介绍,这里就不再多说. 首先来下载和搭建SVN服务器. 现在Subversion已经迁移到apache网站上了,下载地址: http:// ...
- [置顶] 两主机搭建MySQL主从复制后,show slave status显示:Last_IO_Error: error connecting to master ……
两台主机A.B搭建mysql主从复制关系(A为master,B为slave)后,在slave上执行show slave status,结果中显示Last_IO_Error: error connect ...
- Windows下基于http的git服务器搭建-gitstack
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Windows下基于http的git服务器搭建-gitstack 本文地址:http: ...
- [置顶] IIS应用程序池多工作进程设置及Session共享
[置顶] IIS应用程序池多工作进程设置及Session共享 在调优iis的时候,朋友分享给我一个特别棒的设置方法步骤,感谢好朋友的分享. IIS应用程序池多工作进程设置及Session共享 1 ...
- Git本地服务器搭建及使用详解
Git本地服务器搭建及使用 Git是一款免费.开源的分布式版本控制系统.众所周知的Github便是基于Git的开源代码库以及版本控制系统,由于其远程托管服务仅对开源免费,所以搭建本地Git服务器也是个 ...
- Linux下dns服务器搭建
Linux下dns服务器搭建1-环境Red Hat Enterprise Linux Server release 6.7 (Santiago)2-配置本地yum源安装dns相关包yum -y ins ...
- [置顶]Win2012R2的一个Bug安装群集后可能引发的软件崩溃问题及相应补丁
[置顶]Win2012R2的一个Bug安装群集后可能引发的软件崩溃问题及相应补丁 如标题,笔者查阅资料发现微软声称安装故障转角色后就可能发生上述描述问题,但不止于SSMS崩溃.建议使用win2012R ...
- IIS6.0服务器搭建网站无法访问解决方法
IIS6.0服务器搭建网站无法访问解决方法 IIS6.0服务器搭建网站无法访问解决方法很多朋友在用IIS6架网站的时候遇到不少问题,而这些问题有些在过去的IIS5里面就遇到过,有些是新出来的, ...
- Linux下SVN服务器搭建配置
Linux下SVN服务器搭建配置 1.SVN服务安装 yum install subversion 2.创建SVN代码仓库 mkdir /data/svn svnadmin create /data/ ...
随机推荐
- 对Spring from中日期显示格式化问题
开始时间 结束时间 保存 取消 想在input中让日期格式显示为HH:ss 但是各种百度没有找到答案 最后Google之 http://stackoverflow.com/questions/1173 ...
- TOJ 1702.A Knight's Journey
2015-06-05 问题简述: 有一个 p*q 的棋盘,一个骑士(就是中国象棋里的马)想要走完所有的格子,棋盘横向是 A...Z(其中A开始 p 个),纵向是 1...q. 原题链接:http:// ...
- IO库 8.1
题目:编写函数,接受一个istream&参数,返回值类型也是istream&.此函数须从给定流中读取数据,直到遇到文件结束标志时停止.它将读取的数据打印在标准输出上.完成这些操作后,在 ...
- 浏览器 HTTP 缓存原理分析
转自:http://www.cnblogs.com/tzyy/p/4908165.html 浏览器缓存原理: 1.浏览器第一次访问服务器资源/index.html,在浏览器中没有缓存文件,直接向服务器 ...
- Fiddler [Fiddler] Connection to localhost. failed.
原文地址:http://blog.chinaunix.net/uid-20675015-id-1899931.html 在用Fiddler调试本机的网站时,即访问http://localhost,返回 ...
- 利用python进行数据分析之数据加载存储与文件格式
在开始学习之前,我们需要安装pandas模块.由于我安装的python的版本是2.7,故我们在https://pypi.python.org/pypi/pandas/0.16.2/#downloads ...
- win10中的vmware桥接模式异常,不能设置同网段ip
今天发现centOS的桥接模式不正常了,配置 dhclient居然不成功,没有同个网段的ip,于是各种找原因. 突然灵光一现,前几天更新了win10,会不会神马驱动或者服务没安装? 找了一圈,我勒个去 ...
- Java—异常处理总结
异常处理是程序设计中一个非常重要的方面,也是程序设计的一大难点,从C开始,你也许已经知道如何用if...else...来控制异常了,也许是自发的,然而这种控制异常痛苦,同一个异常或者错误如果多个地方出 ...
- 【基础】常用的机器学习&数据挖掘知识点
Basis(基础): MSE(Mean Square Error 均方误差),LMS(LeastMean Square 最小均方),LSM(Least Square Methods 最小二乘法),ML ...
- CCNA实验(9) -- Frame Relay
帧中继的一些特点:1.中小企业常用的广域网线路2.通信费用较低3.配置较为复杂 1.将Cisco路由器配置为帧中继交换机2.帧中继基本配置.帧中继映射3.在帧中继的链路上运行RIPv24.帧中继的多点 ...