1) 什么是websocket?
websocket是一个基于HTML5的双向的通讯协议,它建立在TCP之上,和HTTP一样通过TCP来传输数据。在浏览器与服务器之间建立了一条管道 - - - 不可轻易关闭
webSocket以字符串、文本传输数据 socket以 io流传输
2) websocket原理概述:
websocket在TCP之上定义了帧协议,从而能够支持双向的通信。由于websocket本质上是一个基于TCP的协议,在建立Websocket连接前,浏览器或者其他客户端需要向服务
器发起请求,服务器解析请求后产生应答信息返回给,客户端,由此客户端与服务端的websocket连接建立成功,客户端和服务器都可以通过这条连接主动传递信息直至某一方主动关闭连接。
这次连接就是一次握手过程:
一、Websocket客户端首先发起一个请求。请求数据中包括服务器的IP和端口号、以及客户端随机生成的key等。
二、服务器端根据客户端的key生成密钥,并将密钥返回给客户端,握手成功。
3) 其他通讯方案:
1、轮询
客户端以一定的时间间隔向服务器发送请求,以频繁请求方式来保持客户端和服务器端的数据同步。
轮询缺点:当客户端以固定频率向服务器发送请求时,服务器的数据可能并没有更新,这样会带来很多多余的请求,浪费宽带、效率低
2、长轮询
是对定时轮询的改进和提高,同时是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期,直到数据或状态改变或者时间过期。通过这种机制来减少无效的客户端的服务器间的交互。
长轮询缺点:如果服务器的数据变更非常频繁的话,没有明显的提高。
3、流
在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求,服务器端接到这个请求后作出反应并不断更新连接状态以保证客户端和服务器端的连接不过期
流 缺点:需要针对不同的浏览器设计不同的方案来改进用户体验,同时这种机制在并发比较大的情况下,对服务器端的资源是一个极大考验。
4、基于Flash的实时通讯方式
Flash有自己的socket实现,可以利用Flash完成数据交换,再利用Flash暴露出相应的接口,方便javascript调用来达到实时传输数据的目的
4) websocket优势:
由于websocket的连接本质是一个TCP连接,所以与传统的轮询方式比,这种方式对于数据传输的稳定性和数量大小方面都有一定的优势,在流量比较大的情况下 使用websocket具有很大的性能优势
实现简单的通道连接和关闭
<span style="font-size:18px;">package com.ws.config; import java.util.Set; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpointConfig; public class WebSocketConfig implements ServerApplicationConfig{ <span style="white-space:pre"> </span>//实现了ServerApplicationConfig接口后,在项目启动时,此类自动执行,这样随着Tomcat的启动来启动WebSocket <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//注解方式的启动 <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scan) { <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//输出被扫描到的带注解的类的个数 <span style="white-space:pre"> </span>System.out.println("config....."+scan.size()); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>//返回被扫描到的所有带@ServerEndpoint注解的类 方便服务器端注册websocket server <span style="white-space:pre"> </span>return scan; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>//接口方式的启动 <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> arg0) { <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>return null; <span style="white-space:pre"> </span>} }</span>
<span style="font-size:18px;">package com.bwie.socket; import java.io.IOException; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * @author MSH * * */ @ServerEndpoint("/echo")//前台请求地址 public class EchoSocket { public EchoSocket(){ System.out.println("echoSocket.EchoSocket()"); } //前台访问后台新建一个session,相当于打开一条管道 @OnOpen public void open (Session session){ System.out.println("sessionid"+session.getId()); } //前台退出,session关闭,通道关闭 @OnClose public void close(Session session){ System.out.println("sessionid"+session.getId()+"关闭了"); } //开启通道,前后台响应传输信息,相当于一次握手 @OnMessage public void message(Session session,String msg){ System.out.println("客户端:"+msg ); try { session.getBasicRemote().sendText("服务器:nihao too"); } catch (IOException e) { e.printStackTrace(); } } }</span><span style="font-size:24px;"> </span>
<span style="font-size:18px;"><%@ page language="java" contentType="text/html; charset=UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> <script type="text/javascript"> var ws; //访问后台地址 var target="ws://localhost:8080/webSocket_01/echo"; function subOpen(){ //进入聊天页面,就打开socket通道 // 判断浏览器是IE还是火狐 if ('WebSocket' in window) { ws = new WebSocket(target); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(target); } else { alert('WebSocket is not supported by this browser.'); return; } ws.onmessage=function(event){ var dv=document.getElementById("dv"); dv.innerHTML+=event.data; }; } function subSend(){ var msg=document.getElementById("msg").value; ws.send(msg);//向后台发送消息 document.getElementById("msg").value=""; } </script> </head> <body> <button onclick="subOpen()">open</button> <input id="msg"><button onclick="subSend()">send</button> <span id="dv"></span> </body> </html></span>
一个通信管道 发送多次消息,消息内容不一样!
消息 结构化对消息进行分类 : type : ‘公告’,通知,聊天内容
对消息进行包装:xml json {type:‘聊天内容‘,from:‘laotie‘,to:‘wangshuai‘,content:‘nihao‘,date:‘2016-0624‘}
websocket实现广播群聊单聊
<span style="font-size:18px;"><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="<%=request.getContextPath() %>/LoginServlet" method="get"> 用户名:<input type="text" name="username"> <input type="submit" value="登录"> </form> </body> </html></span>
<span style="font-size:18px;">package com.ws.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ //前台获取登录名 String username = request.getParameter("username"); //将登录名放入session中 request.getSession().setAttribute("username", username); //跳转到聊天页面 response.sendRedirect("chat.jsp"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ doGet(request, response); } }</span>
<span style="font-size:18px;">package com.ws.socket; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import com.google.gson.Gson; import com.ws.bean.Content; import com.ws.bean.Message; @ServerEndpoint("/chatSocket") public class ChatSocket { //用户名 private String username; //session集合 private static List<Session> sessions = new ArrayList<>(); //用户列表集合 private static List<String> names = new ArrayList<String>(); //用户名与session的Map private static Map<String,Session> map = new HashMap<String,Session>(); @OnOpen public void open(Session session) throws UnsupportedEncodingException{ //当前websocket的session对象,不是servlet的session,这里的一个session代表一个通信会话! String queryString = session.getQueryString(); //获取当前登录的用户名 username = queryString.split("=")[1]; //将用户名放入用户列表集合 this.names.add(username); //将当前session放入session集合 this.sessions.add(session); //将用户名和对应session放入map中 map.put(username, session); //进入聊天室欢迎语 String msg = "欢迎"+this.username+"进入聊天室!!<br/>"; //创建message对象 Message message = new Message(); message.setWelcome(msg); message.setUsernames(this.names); //广播 this.broadcast(sessions, message.toJson()); } //创建Gson对象 private static Gson gson = new Gson(); @OnMessage public void message(Session session,String json){ //将json串转成java对象 Content content = gson.fromJson(json, Content.class); if(content.getType()==1){ //广播 Message message = new Message(); message.setUsernames(this.names); message.setContent(this.username,content.getMsg()); broadcast(this.sessions,message.toJson()); }else{ //单聊 //根据username找到对应的session对象 String chatToWho = content.getChatToWho(); Session to_session = map.get(chatToWho); Message message = new Message(); message.setUsernames(this.names); message.setContent(this.username,"<font color='red'>"+content.getMsg()+"</font>"); //向目标发送信息 try { to_session.getBasicRemote().sendText(message.toJson()); } catch (IOException e) { e.printStackTrace(); } } } @OnClose public void close(Session session){ //session集合清除当前用户 sessions.remove(session); //用户列表集合清除当前用户 names.remove(username); //退出聊天室提示语 String msg = username+"退出聊天室!!<br/>"; //创建message对象 Message message = new Message(); message.setWelcome(msg); message.setUsernames(this.names); //广播 broadcast(this.sessions,message.toJson()); } public void broadcast(List<Session> ss,String msg){ //遍历session集合 for (Session session : ss) { try { //服务端向客户端发送消息 session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); } } } }</span>
<span style="font-size:18px;"><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <script type="text/javascript" src="<%=request.getContextPath()%>/jquery/jquery.min.js"></script> <script type="text/javascript"> //获取用户名 var username = "${sessionScope.username}"; //一个ws对象就是一个通信管道 var ws; //服务器端EndPoint的URL var target="ws://169.254.187.126:8080/chat/chatSocket?username="+username; window.onload=function(){ //进入聊天页面,就打开socket通道 // 判断浏览器是IE还是火狐 if ('WebSocket' in window) { ws = new WebSocket(target); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(target); } else { alert('WebSocket is not supported by this browser.'); return; } ws.onmessage=function(event){ //将gson转成字符串 eval("var msg="+event.data+";"); //进入聊天室的欢迎语 if(undefined!=msg.welcome){ $("#content").append(msg.welcome) } //用户列表 if(undefined!=msg.usernames){ $("#userList").html(""); $(msg.usernames).each(function(){ $("#userList").append("<input type='checkbox' value='"+this+"'>"+this+"<br/>") }) } //服务端 发送到客户端的内容 if(undefined!=msg.content){ $("#content").append(msg.content) } } } //发送方法 function subSend(){ var ss = $("#userList :checked"); var msg = $("#msg").val(); var obj = null; if(ss.size()==0){ obj={ msg:msg, type:1 //1 广播 2 单聊 } }else{ var chatToWho = $("#userList :checked").val(); obj={ chatToWho:chatToWho, msg:msg, type:2 //1 广播 2 单聊 } } //将js对象转成json串 var str = JSON.stringify(obj); ws.send(str); $("#msg").val(""); } //退出方法 function exit(){ location.href="<%=request.getContextPath()%>/login.jsp"; } </script> </head> <body> <div id="container" style="border:1px solid black; width:400px; height:400px; float:left;"> <div id="content" style="height:350px;"></div> <div style="border-top:1px solid black; width:400px; height:50px;"> <input id="msg"/><button onclick="subSend();">发送</button> <button onclick="exit();">退出</button> </div> </div> <div id="userList" style="border:1px solid black; width:100px; height:400px; float:left;"></div> </body> </html></span>
package com.ws.bean; import java.util.Date; import java.util.List; import com.google.gson.Gson; public class Message { //第一句欢迎语 private String welcome; //进入聊天室的用户列表 private List<String> usernames; //聊天内容(发送者,发送时间,内容) private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; } public void setContent(String name,String msg) { this.content = name +" "+new Date().toLocaleString()+"<br/>"+msg+"<br/>"; } public String getWelcome() { return welcome; } public void setWelcome(String welcome) { this.welcome = welcome; } public List<String> getUsernames() { return usernames; } public void setUsernames(List<String> usernames) { this.usernames = usernames; } //创建Gson对象 private static Gson gson = new Gson(); //将java对象转成json串 public String toJson(){ String json = gson.toJson(this); return json; } } package com.ws.bean; public class Content { //单聊时 信息发送的目标 private String chatToWho; //聊天内容 private String msg; //聊天类型(群聊或者单聊) private Integer type; public String getChatToWho() { return chatToWho; } public void setChatToWho(String chatToWho) { this.chatToWho = chatToWho; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Integer getType() { return type; } public void setType(Integer type) { this.type = type; } }