用Java和JavaScript基于WebSocket完成聊天室Democss
什么是WebSocket,WebSocket是一种基于TCP的网络协议,就像HTTP同样,它与HTTP最大的不一样就是它是全双工的,也就是服务器能够主动发送数据给浏览器(是否是像Java中的Socket)。在HTTP中,浏览器发起请求以后服务器才能响应,给浏览器发送数据,服务器不能主动给浏览器发送数据。
可是在不少时候,最简单的就好比聊天室,在Http中只能采用轮训的方式,也就是浏览器不停地访问服务器查询有没有消息,这样作效率很低,并且很是浪费流量,WebSocket就是解决“服务器没法主动推送数据”这一难点而发明的。html
目前浏览器基本都支持WebSocket,这种协议有着以下特色:前端
websocket以ws
开头,一个标准的ws网址像这样:java
ws://ip:port/path
其中IP能够被域名代替。下面我会给出一个websocket下的聊天室Demojquery
环境:JDK 1.8.0_211
开发工具:IDEA
项目管理工具:maven
前端页面:bootstrapweb
代码有详细的注释,应该比较好懂的。json
前端页面:
bootstrap
<!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>
前端技术有点水,只能高出这么简陋的页面了。后端
即便这样也要搞个漂亮的首页😆:
api
<!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>
接下来是后端,首先添加依赖:
<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标签来达到发送图片的目的🤣