`
473687880
  • 浏览: 484480 次
文章分类
社区版块
存档分类
最新评论

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类型。这个类型我们需要继承自己实现。打开源码观看这个类

有如下方法

Java代码收藏代码
  1. /**
  2. *Intendedtobeoverriddenbysub-classesthatwishtobenotified
  3. *whentheoutboundconnectionisestablished.Thedefaultimplementation
  4. *isaNO-OP.
  5. *
  6. *@paramoutboundTheoutboundWebSocketconnection.
  7. */
  8. protectedvoidonOpen(WsOutboundoutbound){
  9. //NO-OP
  10. }
  11. /**
  12. *Intendedtobeoverriddenbysub-classesthatwishtobenotified
  13. *whentheoutboundconnectionisclosed.Thedefaultimplementation
  14. *isaNO-OP.
  15. *
  16. *@paramstatusThestatuscodeoftheclosereason.
  17. */
  18. protectedvoidonClose(intstatus){
  19. //NO-OP
  20. }
  21. /**
  22. *ThismethodiscalledwhenthereisabinaryWebSocketmessageavailable
  23. *toprocess.Themessageispresentedviaastreamandmaybeformedfrom
  24. *oneormoreframes.Thenumberofframesusedtotransmitthemessageis
  25. *notmadevisibletotheapplication.
  26. *
  27. *@paramisTheWebSocketmessage
  28. *
  29. *@throwsIOExceptionIfaproblemoccursprocessingthemessage.Any
  30. *exceptionwilltriggertheclosingoftheWebSocket
  31. *connection.
  32. */
  33. protectedabstractvoidonBinaryData(InputStreamis)throwsIOException;
  34. /**
  35. *ThismethodiscalledwhenthereisatextualWebSocketmessageavailable
  36. *toprocess.Themessageispresentedviaareaderandmaybeformedfrom
  37. *oneormoreframes.Thenumberofframesusedtotransmitthemessageis
  38. *notmadevisibletotheapplication.
  39. *
  40. *@paramrTheWebSocketmessage
  41. *
  42. *@throwsIOExceptionIfaproblemoccursprocessingthemessage.Any
  43. *exceptionwilltriggertheclosingoftheWebSocket
  44. *connection.
  45. */
  46. protectedabstractvoidonTextData(Readerr)throwsIOException;

上面的方法都是要我们自己实现的。tomcat没有给我们实现。

仔细看都是onXxx格式,类似事件监听。其实也差不多。只是tomcat在得到消息或者链接发生变化的时候会去调用这些方法,实现方法“自动”触发。

仔细看源码还有很多函数可以使用,这里不一一介绍。感兴趣可以打开源码看看。

其实仔细看官方的例子,chat那个例子也能得到这个结论(tomcat的webSocket例子需要tomcat7.027才带有)

我们定义一个servlet

Java代码收藏代码
  1. @WebServlet(urlPatterns={"/chatWebSocket"})
  2. publicclassChatWebSocketServletextendsWebSocketServlet{
  3. privatestaticfinallongserialVersionUID=1L;
  4. OnLineUsertheUser;
  5. @Override
  6. protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
  7. throwsServletException,IOException{
  8. theUser=(OnLineUser)req.getSession().getAttribute("loginUser");
  9. super.doGet(req,resp);
  10. }
  11. @Override
  12. protectedStreamInboundcreateWebSocketInbound(StringsubProtocol){
  13. returnnewChatMessageInbound(theUser);
  14. }
  15. }

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

Java代码收藏代码
  1. @Override
  2. protectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
  3. throwsServletException,IOException{
  4. //Informationrequiredtosendtheserverhandshakemessage
  5. Stringkey;
  6. StringsubProtocol=null;
  7. List<String>extensions=Collections.emptyList();
  8. if(!headerContainsToken(req,"upgrade","websocket")){
  9. resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
  10. return;
  11. }
  12. if(!headerContainsToken(req,"connection","upgrade")){
  13. resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
  14. return;
  15. }
  16. if(!headerContainsToken(req,"sec-websocket-version","13")){
  17. resp.setStatus(426);
  18. resp.setHeader("Sec-WebSocket-Version","13");
  19. return;
  20. }
  21. key=req.getHeader("Sec-WebSocket-Key");
  22. if(key==null){
  23. resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
  24. return;
  25. }
  26. Stringorigin=req.getHeader("Origin");
  27. if(!verifyOrigin(origin)){
  28. resp.sendError(HttpServletResponse.SC_FORBIDDEN);
  29. return;
  30. }
  31. List<String>subProtocols=getTokensFromHeader(req,
  32. "Sec-WebSocket-Protocol-Client");
  33. if(!subProtocols.isEmpty()){
  34. subProtocol=selectSubProtocol(subProtocols);
  35. }
  36. //TODOReadclienthandshake-Sec-WebSocket-Extensions
  37. //TODOExtensionsrequiretheabilitytospecifysomething(APITBD)
  38. //thatcanbepassedtotheTomcatinternalsandprocessextension
  39. //datapresentwhentheframeisfragmented.
  40. //Ifwegotthisfar,allisgood.Accepttheconnection.
  41. resp.setHeader("upgrade","websocket");
  42. resp.setHeader("connection","upgrade");
  43. resp.setHeader("Sec-WebSocket-Accept",getWebSocketAccept(key));
  44. if(subProtocol!=null){
  45. resp.setHeader("Sec-WebSocket-Protocol",subProtocol);
  46. }
  47. if(!extensions.isEmpty()){
  48. //TODO
  49. }
  50. //SmallhackuntiltheServletAPIprovidesawaytodothis.
  51. StreamInboundinbound=createWebSocketInbound(subProtocol);
  52. ((RequestFacade)req).doUpgrade(inbound);
  53. }

注意倒数第三行,调用了createWebSocketInbound方法,我们重写这个方法。

Java代码收藏代码
  1. @Override
  2. protectedStreamInboundcreateWebSocketInbound(StringsubProtocol){
  3. returnnewChatMessageInbound(theUser);
  4. }

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

Java代码收藏代码
  1. publicfinalclassChatMessageInboundextendsMessageInbound{
  2. publicChatMessageInbound(OnLineUsertheUser){
  3. this.theUser=theUser;
  4. }
  5. @Override
  6. protectedvoidonOpen(WsOutboundoutbound){
  7. //添加链接到容器
  8. ChatMessageInboundtheBound=this;
  9. ChatContainer.addInbound(theBound.theUser,theBound);
  10. //向每个在线用户发送消息
  11. ChatContainer.eachAllBound(newContainerCallBack(){
  12. @Override
  13. publicvoideachCallBack(ChatMessageInboundtheBound,OnLineUsertheUser){
  14. ListUserMsglistUserMsg=newListUserMsg(ChatContainer.getUserList());
  15. WriteTookit.writeToBound(theBound,listUserMsg.toMsg());
  16. }
  17. });
  18. }
  19. @Override
  20. protectedvoidonClose(intstatus){
  21. ChatContainer.removeInbound(theUser);
  22. }
  23. @Override
  24. protectedvoidonBinaryMessage(ByteBuffermessage)throwsIOException{
  25. }
  26. @Override
  27. protectedvoidonTextMessage(CharBuffermessage)throwsIOException{
  28. //CHAT_MODEL.setMessage(message.toString());
  29. //ChatContainer.eachAllBound(newContainerCallBack(){
  30. //@Override
  31. //publicvoideachCallBack(ChatMessageInboundtheBound,OnLineUsertheUser){
  32. //WriteTookit.writeToBound(theBound,CHAT_MODEL.getSayMsg());
  33. //}
  34. //});
  35. }
  36. //变量区域
  37. privateOnLineUsertheUser;
  38. }

这里只是简单实现了一下,注释部分只是处理这个方法的部分,那里是一个容器,存档所有在线用户。并且提供遍历插入以及删除等方法,在自己实现的时候完全不需要这么写。

下面是容器代码

Java代码收藏代码
  1. publicfinalclassChatContainer{
  2. /**
  3. *保存服务器连接的用户的容器
  4. */
  5. privatestaticfinalMap<OnLineUser,ChatMessageInbound>CHAT_MAP=newHashMap<OnLineUser,ChatMessageInbound>();
  6. /**
  7. *取出用户的连接
  8. */
  9. publicstaticChatMessageInboundgetInbound(OnLineUsertheUser){
  10. returnCHAT_MAP.get(theUser);
  11. }
  12. /**
  13. *放入一个连接
  14. */
  15. publicstaticvoidaddInbound(OnLineUsertheUser,
  16. ChatMessageInboundoutbound){
  17. CHAT_MAP.put(theUser,outbound);
  18. System.out.println(CHAT_MAP.size());
  19. }
  20. /**
  21. *移除一个连接
  22. *
  23. *@paramtheUser
  24. *@return
  25. */
  26. publicstaticChatMessageInboundremoveInbound(OnLineUsertheUser){
  27. returnCHAT_MAP.remove(theUser);
  28. }
  29. /**
  30. *遍历所有连接
  31. */
  32. publicstaticvoideachAllBound(ContainerCallBackcallBackInter){
  33. Iterator<OnLineUser>keyIter=CHAT_MAP.keySet().iterator();
  34. while(keyIter.hasNext()){
  35. OnLineUsertheUser=keyIter.next();
  36. callBackInter.eachCallBack(CHAT_MAP.get(theUser),theUser);
  37. }
  38. }
  39. /**
  40. *回调函数的接口
  41. *
  42. *@authorWangZhenChong
  43. */
  44. publicinterfaceContainerCallBack{
  45. voideachCallBack(ChatMessageInboundtheBound,OnLineUsertheUser);
  46. }
  47. }

我定义了一种数据交约定,使用json 字符串,MsgType表示消息类型,类似windows的消息机制

Java代码收藏代码
  1. /**
  2. *前台和后台交互的信息类型常量
  3. *
  4. *@authorWangZhenChong
  5. *
  6. */
  7. publicfinalclassMsgTypeConstants{
  8. publicstaticshortGET_USER_LIST=1;//在线所有用户信息交互
  9. publicstaticshortSEND_ONE_TO_ONE=2;//对一个用户发送消息
  10. publicstaticshortSEND_ONE_TO_ALL=3;//对所有用户发送消息
  11. publicstaticshortSEND_SERVER_MSG=4;//发送系统消息
  12. }

余下的msgContent就是消息内容,比如列出现在用户这个内容就是[...,...,...,...]发送消息就是消息模型的内容。

这样解决单通道多操作的方法。

下面列出前台js核心内容。

使用jquery

Js代码收藏代码
  1. $(document).ready(function(){
  2. $("#connBtn").bind('click',function(){
  3. $.ajax({
  4. url:"/tomcatWebSocket/Login#?asdasdasd",
  5. type:"POST",
  6. processData:false,
  7. data:$.param({
  8. username:document.getElementById("usernameField").value
  9. }),
  10. success:function(msg,status){
  11. initChat();
  12. initUserList();
  13. $("#sendBtn").removeAttr("disabled");
  14. $("#connBtn").attr("disabled","disabled");
  15. $("#usernameField").attr("disabled","disabled");
  16. },
  17. error:function(jqXHR,textStatus,errorThrown){
  18. alert("服务器内部错误");
  19. }
  20. });
  21. });
  22. varChat={};
  23. Chat.socket=null;
  24. functioninitChat(){
  25. varwsURL='ws://'+window.location.host
  26. +'/tomcatWebSocket/chatWebSocket';
  27. if('WebSocket'inwindow){
  28. Chat.socket=newWebSocket(wsURL);
  29. }elseif('MozWebSocket'inwindow){
  30. Chat.socket=newMozWebSocket(wsURL);
  31. }else{
  32. alert("浏览器不支持");
  33. returnfalse;
  34. }
  35. Chat.socket.onopen=function(){
  36. };
  37. Chat.socket.onclose=function(){
  38. Chat.writeToConsole("断开连接了");
  39. initChat();
  40. };
  41. Chat.socket.onmessage=function(message){
  42. if(typeofmessage.data=="string"){//如果发送的是字符串信息.
  43. varmsgObj=eval("("+message.data+")");
  44. switch(msgObj.MsgType){
  45. caseMsgTypeConstants.GET_USER_LIST://所有用户信息
  46. Chat.preUserList(msgObj.userList);
  47. break;
  48. caseMsgTypeConstants.SEND_ONE_TO_ALL:
  49. Chat.writeToConsole(msgObj.msgContext);
  50. break;
  51. default:
  52. alert("未知错误,请刷新页面");
  53. }
  54. }
  55. };
  56. Chat.sendMessage=function(){
  57. Chat.socket.send(ueditor.getContentTxt());
  58. };
  59. }
  60. Chat.writeToConsole=function(message){
  61. <spanstyle="white-space:pre;"></span>//往控制台打印得到的聊天消息
  62. };
  63. /**
  64. *处理刷新用户信息的方法。
  65. */
  66. Chat.preUserList=function(userList){
  67. //用户信息列表
  68. };

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


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics