最近看到了WebSocket,难免想作些什么小功能,而后就选择了聊天室,首先固然先介绍什么是WebSockethtml
WebSocket 是 HTML5 开始提供的可在单个 TCP 链接上进行全双工通信的协议,其容许服务端主动向客户端推送数据,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输前端
注意:WebSocket 和 HTTP 的区别,WebSocket虽创建在HTTP上,但属于新的独立协议,只是其创建链接的过程须要用到HTTP协议java
为何须要WebSocket?web
解决HTTP协议的某些缺陷 ---- 通讯只能由客户端发起。不少网站为了实现推送技术,使用Ajax轮询,这样在没有新消息的状况下客户端也要发送请求,势必形成服务器的负担,而WebSokcet能够主动向客户端推送消息,是全双工通信,能更好的节省服务器资源和带宽ajax
ws://www.baidu.com
一:客服端请求协议升级spring
GET / HTTP/1.1 Host: localhost:8080 Origin: http://127.0.0.1:8080 Connection: Upgrade // 表示要升级协议 Upgrade: websocket // 表示升级的协议是websocket Sec-WebSocket-Version: 13 // websocket版本号 Sec-WebSocket-Key: w4v7O6xFTi36lqcgctw== // 随机生成,防止非故意的错误,链接错了
二:服务器响应apache
HTTP/1.1 101 Switching Protocols Upgrade: websocket // 表示能够升级对应的协议 Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUmm5OPpG2HaGWk= // 根据客户端key用函数计算出来
三:此后开始使用WebSocket协议api
ajax轮询:让浏览器间隔几秒就发送一次请求,来获取最新的响应浏览器
long poll:保持长链接来阻塞轮询。客户端发起请求不会马上响应,而是有数据才返回而后关闭链接,而后客户端再次发起long poll周而复始tomcat
这个代码是极简的,适合入门理解。WebSocket是一套已经规范好的标准的API,Tomcat、Spring等都实现了这套API,下面笔者用Springboot来操做
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
@Configuration // 配置类,用来注册服务 public class serverConfig { @Bean // 返回的bean会自动注册进容器 public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
重点就在这里,先说明一下:
@Component // 注解虽然单例,但仍是会建立多例 @ServerEndpoint(value = "/wechat/{username}") // 声明为服务器端点 public class MyServer { // 成员变量 private Session session; private String username; // 类变量 // 类变量涉及同步问题,用线程安全类 // 能够用<String room,<String username,MyServer> >来造成房间 private static AtomicInteger onlineCount = new AtomicInteger(0); private static ConcurrentHashMap<String, MyServer> map = new ConcurrentHashMap<>(); // 链接 @OnOpen public void onOpen(@PathParam("username") String username, Session session) throws IOException { this.session = session; this.username = username; map.put(username, this); addOnlineCount(); sendMessageAll(username + "加入了房间,当前在线人数:" + getOnlineCount()); } // 关闭 @OnClose public void onClose() throws IOException { subOnlineCount(); map.remove(username); sendMessageAll(username + "退出了房间,当前在线人数:" + getOnlineCount()); } // 发送错误 @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } // 默认群发 @OnMessage public void onMessage(String message) throws IOException { sendMessageAll(username + ":" + message); } // 群发 private void sendMessageAll(String message) throws IOException { for (MyServer value : map.values()) { value.session.getBasicRemote().sendText(message); // 阻塞式 // this.session.getAsyncRemote().sendText(message); // 非阻塞式 } } // 私发 private void sendMessageTo(String message, String to) throws IOException { MyServer toUser = map.get(to); toUser.session.getAsyncRemote().sendText(message); } public static synchronized int getOnlineCount() { return onlineCount.get(); } public static synchronized void addOnlineCount() { MyServer.onlineCount.getAndIncrement(); } public static synchronized void subOnlineCount() { MyServer.onlineCount.getAndDecrement(); } }
笔者写的前端不太靠谱,知道什么意思便可~
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>登陆页</title> </head> // 输入名字,url传参省事 <body> <label for="username">Username:</label> <input id="username" type="text" placeholder="请输入昵称"> <button id="submit" >ENTER</button> </body> <script> var submit = document.getElementById('submit'); submit.addEventListener('click',function(){ window.location.href = 'homepage.html?username=' + document.getElementById('username').value; }) </script> </html>
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>房间</title> </head> <body> <button onclick="wsClose()">退出房间</button> <br/><br/> <div id="showMessage"></div> <br/><br/> <input id="sendMessage" type="text"/> <button onclick="sendMessage()">发送消息</button> </body> <script> // 获取url参数的昵称 function getQueryVariable(variable) { var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i<vars.length;i++) { var pair = vars[i].split("="); if(pair[0] == variable){return pair[1];} } return(false); } var conn = "ws://localhost:8080/wechat/" + getQueryVariable("username"); // webSocket链接 var ws = new WebSocket(conn); // 链接错误要作什么呢? ws.onerror = function () { showMessageInnerHTML("发生未知错误错误"); } // 客户端链接须要干什么呢? ws.onopen = function () { showMessageInnerHTML("--------------------------"); } // 客户端关闭须要干什么呢? ws.onclose = function () { showMessageInnerHTML("退出了当前房间"); } // 收到消息 ws.onmessage = function (even) { showMessageInnerHTML(even.data); } // 关闭浏览器时 window.onbeforeunload = function () { ws.wsClose(); } // 网页上显示消息 function showMessageInnerHTML(msg) { document.getElementById('showMessage').innerHTML += msg + '<br/>'; } // 发送消息 function sendMessage() { var msg = document.getElementById('sendMessage').value; ws.send(msg); document.getElementById('sendMessage').value = ''; } // 关闭链接 function wsClose() { ws.close(); } </script> </html>
不想弄前端,凑合着看吧
参考
tomcat、Spring官网均有简介及API的详细介绍。推荐使用后者,后者符合spring规范并且更加优雅
http://tomcat.apache.org/tomcat-9.0-doc/websocketapi/index.html