WE

众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客端浏览器将信息呈现。但是对于实时性要求较高、海量并发的应用,比如金融证券的实时信息,web导航应用中地理位置获取,社交网络的实时消息推送等。


方案一:轮询,客户端用js代码每隔一定时间向服务器发送请求,这样会造成资源浪费(浪费带宽),在高并发的情况下还可能造成服务器奔溃。

方案二:基于Flash、AdobeFlash,通过socket实现数据信息交互,再利用Flash暴露的接口供js调用,但是Flash在移动互联网上的支持不好,IOS和Android都不支持Flash了。

方案三:WebSocket,2014年开始,各大应用服务器和浏览器厂商逐步统一,J2EE7也实现了WebSocket协议,无论客户端还是服务器都提供了对其的支持。


WebSocket介绍与原理

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和HTTP 最大不同是:

WebSocket 是一种双向通信协议,在基于http建立连接后,WebSocket 服务器和 browser都能主动向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信,实现长连接。


WebSocket 客户端连接报文

GET /webfin/websocket/ HTTP/1.1

Host: localhost

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==

Origin: http://localhost:8080

Sec-WebSocket-Version: 13


可以看到,客户端发起的 WebSocket 连接报文类似传统 HTTP 报文, ”Upgrade:websocket”参数值表明这是 WebSocket 类型请求, “Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文, 要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答, 否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。

WebSocket 服务端响应报文

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=


“Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的, “HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接, 经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。

下载javax.websocket.jar,使用注解方式实现了一个简单的多房间聊天demo,demo只有一个服务端类和一个前端chat.html页面,打开多个chat.html页面,输入相同的房间名,进入房间后可以相互通信,不同房间不能互相通信,不同用户我用websocket的session自己分配的id来区分,因为一个用户连接到webSocket服务器就对应一个session,实际开发可以用http的session中登录的用户名来区分,连接到服务器的url中,roomName是一个路径参数,即在chat.html中获取到房间名。多房间的原理其实就是把多个用(session)放在roomName对应的set集合中,每次广播信息只在房间名对应的set集合中广播,实现房间聊天信息的隔离。

代码如下:


 
 
  1. package webSocketTest;
  2. import javax.websocket.OnClose;
  3. import javax.websocket.OnMessage;
  4. import javax.websocket.OnOpen;
  5. import javax.websocket.Session;
  6. import javax.websocket.server.PathParam;
  7. import javax.websocket.server.ServerEndpoint;
  8. import java.util.HashSet;
  9. import java.util.Set;
  10. import java.util.Map;
  11. import java.util.concurrent.ConcurrentHashMap;
  12. import java.util.concurrent.CopyOnWriteArraySet;
  13. /**
  14. * writer: holien
  15. * Time: 2017-08-01 13:00
  16. * Intent: webSocket服务器
  17. */
  18. @ServerEndpoint( "/webSocket/chat/{roomName}")
  19. public class WsServer {
  20. // 使用map来收集session,key为roomName,value为同一个房间的用户集合
  21. // concurrentMap的key不存在时报错,不是返回null
  22. private static final Map<String, Set<Session>> rooms = new ConcurrentHashMap();
  23. @OnOpen
  24. public void connect(@PathParam("roomName") String roomName, Session session) throws Exception {
  25. // 将session按照房间名来存储,将各个房间的用户隔离
  26. if (!rooms.containsKey(roomName)) {
  27. // 创建房间不存在时,创建房间
  28. Set<Session> room = new HashSet<>();
  29. // 添加用户
  30. room.add(session);
  31. rooms.put(roomName, room);
  32. } else {
  33. // 房间已存在,直接添加用户到相应的房间
  34. rooms.get(roomName).add(session);
  35. }
  36. System.out.println( "a client has connected!");
  37. }
  38. @OnClose
  39. public void disConnect(@PathParam("roomName") String roomName, Session session) {
  40. rooms.get(roomName).remove(session);
  41. System.out.println( "a client has disconnected!");
  42. }
  43. @OnMessage
  44. public void receiveMsg(@PathParam("roomName") String roomName,
  45. String msg, Session session) throws Exception {
  46. // 此处应该有html过滤
  47. msg = session.getId() + ":" + msg;
  48. System.out.println(msg);
  49. // 接收到信息后进行广播
  50. broadcast(roomName, msg);
  51. }
  52. // 按照房间名进行广播
  53. public static void broadcast(String roomName, String msg) throws Exception {
  54. for (Session session : rooms.get(roomName)) {
  55. session.getBasicRemote().sendText(msg);
  56. }
  57. }
  58. }

 
 
  1. <!DOCTYPE html><html lang="en">
  2. <head>
  3. <meta charset="UTF-8">
  4. <title>网络聊天室 </title>
  5. </head>
  6. <style type="text/css">
  7. .msg_board {
  8. width: 322px;
  9. height: 100px;
  10. border: solid 1px darkcyan;
  11. padding: 5px;
  12. overflow-y: scroll;
  13. // 文字长度大于div宽度时换行显示
  14. word-break: break-all;
  15. }
  16. /*set srcoll start*/
  17. ::-webkit-scrollbar
  18. {
  19. width: 10px;
  20. height: 10px;
  21. background-color: #D6F2FD;
  22. }
  23. ::-webkit-scrollbar-track
  24. {
  25. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
  26. /*border-radius: 5px;*/
  27. background-color: #D6F2FD;
  28. }
  29. ::-webkit-scrollbar-thumb
  30. {
  31. height: 20px;
  32. /*border-radius: 10px;*/
  33. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
  34. background-color: #89D7F7;
  35. }
  36. /*set srcoll end*/
  37. </style>
  38. <body>
  39. <label>房间名 </label>
  40. <input id="input_roomName" size="10" maxlength="10">
  41. <input type="button" value="进入聊天室" onclick="initWebSocket()" />
  42. <input type="button" value="退出聊天室" onclick="closeWs()" /> <br>
  43. <div class="msg_board"> </div>
  44. <input id="input_msg" size="43" maxlength="40">
  45. <input type="button" value="发送" onclick="send_msg()" />
  46. </body>
  47. <script type="text/javascript">
  48. var webSocket;
  49. function send_msg() {
  50. if (webSocket != null) {
  51. var input_msg = document.getElementById( "input_msg").value.trim();
  52. if (input_msg == "") {
  53. return;
  54. }
  55. webSocket.send(input_msg);
  56. // 清除input框里的信息
  57. document.getElementById( "input_msg").value = "";
  58. } else {
  59. alert( "您已掉线,请重新进入聊天室...");
  60. }
  61. };
  62. function closeWs() {
  63. webSocket.close();
  64. };
  65. function initWebSocket() {
  66. var roomName = document.getElementById( "input_roomName").value;
  67. // 房间名不能为空
  68. if (roomName == null || roomName == "") {
  69. alert( "请输入房间名");
  70. return;
  71. }
  72. if ( "WebSocket" in window) {
  73. // alert("您的浏览器支持 WebSocket!");
  74. if (webSocket == null) {
  75. var url = "ws://localhost:8080/webSocket/chat/" + roomName;
  76. // 打开一个 web socket
  77. webSocket = new WebSocket(url);
  78. } else {
  79. alert( "您已进入聊天室...");
  80. }
  81. webSocket.onopen = function () {
  82. alert( "已进入聊天室,畅聊吧...");
  83. };
  84. webSocket.onmessage = function (evt) {
  85. var msg_board = document.getElementsByClassName( "msg_board")[ 0];
  86. var received_msg = evt.data;
  87. var old_msg = msg_board.innerHTML;
  88. msg_board.innerHTML = old_msg + received_msg + "<br>";
  89. // 让滚动块往下移动
  90. msg_board.scrollTop = msg_board.scrollTop + 40;
  91. };
  92. webSocket.onclose = function () {
  93. // 关闭 websocket,清空信息板
  94. alert( "连接已关闭...");
  95. webSocket = null;
  96. document.getElementsByClassName( "msg_board")[ 0].innerHTML = "";
  97. };
  98. }
  99. else {
  100. // 浏览器不支持 WebSocket
  101. alert( "您的浏览器不支持 WebSocket!");
  102. }
  103. }
  104. </script>
  105. </html>

同时打开3个chat.html页面,2个页面进入名为“技术分享会”的房间,剩下一个进入名为“spring源码分析”的房间,只有相同房间的信息可以互通,截图如下:





发觉要更好的学习一种技术,得把该技术运用在具体功能实现上,这样会迫使我们去找资料和API,使得对该技术的认识更深刻...


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值