初识WebSocket

白昼怎懂夜的黑 提交于 2019-11-28 03:46:54

初识WebSocket

用Java和JavaScript基于WebSocket完成聊天室Demo

  什么是WebSocket,WebSocket是一种基于TCP的网络协议,就像HTTP一样,它与HTTP最大的不同就是它是全双工的,也就是服务器可以主动发送数据给浏览器(是不是像Java中的Socket)。在HTTP中,浏览器发起请求之后服务器才能响应,给浏览器发送数据,服务器不能主动给浏览器发送数据。
  但是在很多时候,最简单的就比如聊天室,在Http中只能采用轮训的方式,也就是浏览器不停地访问服务器查询有没有消息,这样做效率很低,而且非常浪费流量,WebSocket就是解决“服务器无法主动推送数据”这一难点而发明的。

  目前浏览器基本都支持WebSocket,这种协议有着如下特点:

  • 建立在 TCP 协议之上,服务器端的实现比较容易。
  • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  • 数据格式比较轻量,性能开销小,通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。
  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

websocket以ws开头,一个标准的ws网址像这样:

ws://ip:port/path

  其中IP可以被域名代替。下面我会给出一个websocket下的聊天室Demo

基于WebSocket的聊天室Demo

环境:JDK 1.8.0_211
开发工具:IDEA
项目管理工具:maven
前端页面:bootstrap

代码有详细的注释,应该比较好懂的。

前端页面:


chat.html

<!DOCTYPE html> <html lang="zh"> <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1">      <title>基于WebSocket的在线聊天室</title>     <link href="css/bootstrap.min.css" rel="stylesheet">      <style>         .chatFont {             /* 消息字体大小 */             font-size: 16px;         }          .input {             /* 输入框绝对定位 */             position: fixed;             top: 75%;             right: 3%;             width: 70%;             font-size: 16px;         }     </style> </head> <body> <!--侧边栏--> <div class="column col-xs-3" id="sidebar">     <h3 class="text-center">在线人员</h3>     <ul class="nav" id="onlineUser">     <!-- 在线用户显示在这里 -->     </ul> </div>  <div class="col-xs-9">     <div class="panel panel-info">         <div class="panel-heading"><h3>聊天室</h3></div>         <div class="panel-body" id="show" style="height: 380px;">         <!-- 消息显示在这里 -->             <div class="chatFont">聊天记录<br></div>         </div>     </div>     <div class="input">         <label for="msg">请输入</label>         <textarea class="form-control" rows="5" id="msg"></textarea>     </div> </div>  </body> <script src="js/jquery-3.4.1.js"></script> <script src="js/bootstrap.min.js"></script> <script>     //这个num是用来限制消息条数,不然消息会撑破面板(前端技术太渣只能用这种笨办法)     var num = 1;     // 创建WebSocket对象     var socket = new WebSocket("ws://localhost:8080/chat");     //发送消息     var sendMsg = function () {         var input = $('#msg');         if (input.val()) {             socket.send(input.val());         } else {             alert('消息不能为空')         }         //清空输入框         input.val("");     };      //输入框键盘事件检测     var keyDown = function (e) {         if (!e.ctrlKey && e.keyCode == 13) { // enter 键             sendMsg();         } else if (e.keyCode == 13 && e.ctrlKey) {             //实现换行,这个没写             alert('ctrl enter');         }     };     //绑定输入框键盘事件     $('#msg').keydown(keyDown);      //websocket监听事件,收到消息时触发     socket.onmessage = function (ev) {         //console.log(ev);         showMsg(ev.data);     };     //显示消息     var showMsg = function (data) {         //后端使用Json传输         data = JSON.parse(data);         //如果有消息则显示在消息框         if (data.msg) {             var text = '<div class="chatFont">' + '[' + data.userName + ']:' + data.msg + '<br></div>';             $('#show').append(text);         }         //如果是新用户则添加在线成员,针对新用户         if (data.onlineUser) {             console.log(data.onlineUser);             var online = data.onlineUser;             $.each(online, function (index, element) {                 $('#onlineUser').append('<li class="active" id="' + element + '"><a href="#">' + element + '</a></li>');             })         }         //如果有新用户进来则添加在线成员,针对已在线成员         if (data.addUser) {             $('#onlineUser').append('<li class="active" id="' + data.userName + '"><a href="#">' + data.userName + '</a></li>');         }         //如果有用户离开则移除在线成员         if (data.removeUser) {             $('#' + data.userName + '').remove();         }         //消息大于15条时删除最上边的,防止撑破消息栏         if ($('#show').children('div').length>15){             $('#show').children('div:first').remove();         }     };     //连接断开时触发,清空绑定     socket.onclose = function (ev) {         $('#msg').unbind();         $('#show').text('连接已断开');     };     //连接时触发,创建用户名     socket.onopen = function (ev) {         var name = prompt('请输入昵称:');         if (name) {             socket.send(name);         } else {             socket.send('游客:' + Math.random() * 100000000000000000);         }     } </script> </html>

前端技术有点水,只能高出这么简陋的页面了。

即使这样也要搞个漂亮的首页😆:


index.html

<!DOCTYPE html> <html lang="zh-CN"> <head>     <meta charset="utf-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1">     <title>首页</title>      <link href="css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="jumbotron">     <div class="container text-center" >         <h2 class="text-info" style="font-family:宋体;font-weight:bold;font-size:49px">基于WebSocket的在线聊天室</h2>         <br>         <div class="text-muted">与世界分享你的逼格</div>         <br>         <br>     </div>     <div class="container text-center">         <button class="btn btn-primary" id="enter">进入</button>     </div> </div> </body> <script src="js/jquery-3.4.1.js"></script> <script src="js/bootstrap.min.js"></script> <script>     $('#enter').click(function () {         location.href = 'chat.html';     }); </script> </html>

接下来是后端,首先添加依赖:


pom.xml

<dependency>     <groupId>javax.websocket</groupId>     <artifactId>javax.websocket-api</artifactId>     <version>1.1</version>     <scope>provided</scope> </dependency>  <dependency>     <groupId>com.alibaba</groupId>     <artifactId>fastjson</artifactId>     <version>1.2.59</version> </dependency>

然后是服务端主体:


后端实现

面向对象,对消息进行封装:

public class Message {     private String userName;//用户     private String msg;//消息主体     private boolean addUser;//用户加入     private boolean removeUser;//用户离开      //清空状态     public void clearMsg(){         msg = null;         addUser = false;         removeUser = false;     }      public String getUserName() {         return userName;     }      public void setUserName(String userName) {         this.userName = userName;     }      public String getMsg() {         return msg;     }      public void setMsg(String msg) {         this.msg = msg;     }      public boolean isAddUser() {         return addUser;     }      public void setAddUser(boolean addUser) {         this.addUser = addUser;     }      public boolean isRemoveUser() {         return removeUser;     }      public void setRemoveUser(boolean removeUser) {         this.removeUser = removeUser;     }      @Override     public String toString() {         return "Message{" +                 "userName='" + userName + '\'' +                 ", msg='" + msg + '\'' +                 ", addUser=" + addUser +                 ", removeUser=" + removeUser +                 '}';     } }

服务主体:

import com.alibaba.fastjson.JSON; import com.bilibili.pojo.Message;  import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;  //访问路径,类似http中的@WebServlet()注解 @ServerEndpoint("/chat") public class Chat {     //每一个连接都会创建一个Chat对象,所以创建一个静态Map来保存已连接用户     private static final Map<String, Chat> clientMap = new HashMap<>();      private boolean firstFlag = true;//是否第一次访问     private String name;     private Session session;//这里的Session和servlet中的Session不是同一个种     private Message message = new Message();      /**      * 客户端连接时执行的方法      * @param session 客户端session      * @throws IOException      */     @OnOpen     public void start(Session session) throws IOException {         this.session = session;         System.out.println("连接");     }      /**      * 客户端断开      */     @OnClose     public void end() {         //从连接对象中移除         clientMap.remove(name, this);         //向所有人发送一个有人离开的消息         message.clearMsg();         message.setUserName(name);         message.setMsg("离开了聊天室!");         message.setRemoveUser(true);         // 发送消息         sendMsg(JSON.toJSONString(message));         System.out.println("断开");     }      /**      * 服务端收到消息      * @param msg      */     @OnMessage     public void receive(String msg) {         if (firstFlag) {             //把第一次的消息作为用户名             name = msg;             //构造发送给所有人的消息             message.setMsg("加入了聊天室!");             message.setUserName(name);             message.setAddUser(true);             //获取当前在线用户             List<String> onlineUser = new ArrayList<>(clientMap.keySet());             clientMap.put(name, this);             try {                 //直接构造Json,给新连接的用户发送刷新在线用户的消息                 session.getBasicRemote().sendText("{\"onlineUser\":"+JSON.toJSONString(onlineUser)+"}");             } catch (IOException e) {                 e.printStackTrace();             }             // 给所有用户发送有人进入的消息             sendMsg(JSON.toJSONString(message));             firstFlag = false;         } else {             //不是第一次则直接发送消息             message.clearMsg();             message.setMsg(msg);             sendMsg(JSON.toJSONString(message));         }      }      // 当客户端通信出现错误时,激发该方法     @OnError     public void onError(Throwable t) throws Throwable {         System.out.println("WebSocket服务端错误 " + t);     }      //发送消息的方法     public void sendMsg(String msg) {         // 遍历服务器关联的所有客户端         Chat client = null;         for (String nickname : clientMap.keySet()) {             try {                 client = clientMap.get(nickname);                 synchronized (client) {                     // 发送消息                     client.session.getBasicRemote().sendText(msg);                 }             } catch (IOException e) {                 System.out.println("聊天错误,向客户端 " + client + " 发送消息出现错误。");                 clientMap.remove(name, client);                 try {                     client.session.close();                 } catch (IOException e1) {                 }                 Message newMessage = new Message();                 newMessage.setMsg("["+client.name+"]已经被断开了连接。");                 sendMsg(JSON.toJSONString(newMessage));             }         }     } }

这样就简单实现了一个聊天室。

效果图如下:

这么写可以发送html标签来达到发送图片的目的🤣

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!