设为首页 收藏本站
查看: 990|回复: 0

[经验分享] tomcat7.027-webSocket应用程序构建01

[复制链接]

尚未签到

发表于 2017-3-1 10:20:25 | 显示全部楼层 |阅读模式
  前几天研究了一下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) {
//用户信息列表
};

  这些代码只是参考内容,实际上不可能拷贝下来直接运行,
  如果有什么不理解的地方可以参看,有什么不对希望指出。有什么疑问希望提出。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348691-1-1.html 上篇帖子: 主题:Java源码阅读的真实体会(转) 下篇帖子: HDFS namenode源码分析
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表