百度百科中这样定义WebSocket:WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。简单的说,WebSocket协议之前,双工通信是通过多个http链接来实现,这导致了效率低下,而WebSocket解决了这个问题。
1.1 思考: 传统web的请求和响应模式中, 我们如何实现实时信息传输, 如何实现服务器反推数据? 在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模拟双向通信,但效率较低,并需要服务器有较好的支持; flash中的socket和xmlsocket可以实现真正的双向通信,通过 flex ajax bridge,可以在javascript中使用这两项功能. 可以预见,如果websocket一旦在浏览器中得到实现,将会替代上面两项技术,得到广泛的使用.面对这种状况,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。在JavaEE7中也实现了WebSocket协议。
1.2 WebSocket的目标:**打破传统的web请求响应模型, 实现管道式的实时通信。打开一个浏览器和服务器的通信通道,持续连接! 服务器给浏览器推送数据 非常方便!web的实时消息通信: 聊天,股票,游戏,监控等等。** 1.3 软件需求 (1). 安装jdk7 或更高版本 (2). 下载tomcat7 两者保持一致(32、64位) 2.1 实现一个webSocket应用程序,我们要学会几个基本操作。 (1). 开启连接 (2). 客户端给服务器端发送数据 (3). 服务器端接收数据 (4). 服务器端给客户端发送数据 (5). 客户端接收数据 (6). 监听三类基本事件: onopen,onmessage,onclose 提示: onmessage 是发送数据时的响应事件。onopen是打开连接时的响应事件。onclose是关闭连接时的响应事件。
3.1 接下来正式实现聊天室功能,首先写一个登录页面
<%@ 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="jquery-1.4.4.min.js"></script> </head> <body> <form name="ff" action="LoginServlet" method="post" > 用户名:<input name="username" /><br/> <input type="submit" value="登录"/> </form> </body> </html>
3.2 在web.xml配置LoginServlet
<servlet> <description></description> <display-name>LoginServlet</display-name> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.bjsxt.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/LoginServlet</url-pattern> </servlet-mapping>
3.3 添加LoginServlet,用于处理登录请求以及跳转到聊天室界面
package com.bjsxt.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{ } public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{ String username = request.getParameter("username"); System.out.println("doPost当前登录用户为"+username); request.getSession().setAttribute("username",username); //这里只是简单地模拟登录,登陆之后直接跳转到聊天页面 response.sendRedirect("chat.jsp"); } }
3.4 添加聊天界面
<%@ 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="jquery-1.4.4.min.js"></script> <script type="text/javascript"> var ws; var userName = ${sessionScope.username}; //通过URL请求服务端(chat为项目名称) var url = "ws://localhost:8080/chat/chatSocket?username=${sessionScope.username}"; //进入聊天页面就是一个通信管道 window.onload = function() { if ('WebSocket' in window) { ws = new WebSocket(url); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(url); } else { alert('WebSocket is not supported by this browser.'); return; } //监听服务器发送过来的所有信息 ws.onmessage = function(event) { eval("var result=" + event.data); //如果后台发过来的alert不为空就显示出来 if (result.alert != undefined) { $("#content").append(result.alert + "<br/>"); } //如果用户列表不为空就显示 if (result.names != undefined) { //刷新用户列表之前清空一下列表,免得会重复,因为后台只是单纯的添加 $("#userList").html(""); $(result.names).each( function() { $("#userList").append( "<input type=checkbox value='"+this+"'/>" + this + "<br/>"); }); } //将用户名字和当前时间以及发送的信息显示在页面上 if (result.from != undefined) { $("#content").append( result.from + " " + result.date + " 说:<br/>" + result.sendMsg + "<br/>"); } }; }; //将消息发送给后台服务器 function send() { //拿到需要单聊的用户名 //alert("当前登录用户为"+userName); var ss = $("#userList :checked"); //alert("群聊还是私聊"+ss.size()); var to = $('#userList :checked').val(); if (to == userName) { alert("你不能给自己发送消息啊"); return; } //根据勾选的人数确定是群聊还是单聊 var value = $("#msg").val(); //alert("消息内容为"+value); var object = null; if (ss.size() == 0) { object = { msg : value, type : 1, //1 广播 2单聊 }; } else { object = { to : to, msg : value, type : 2, //1 广播 2单聊 }; } //将object转成json字符串发送给服务端 var json = JSON.stringify(object); //alert("str="+json); ws.send(json); //消息发送后将消息栏清空 $("#msg").val(""); } </script> </head> <body> <h3>欢迎 ${sessionScope.username }使用本聊天系统!!</h3> <div id="content" style="border: 1px solid black; 400px; height: 300px; float: left; color: #7f3f00;"></div> <div id="userList" style="border: 1px solid black; 120px; height: 300px; float: left; color: #00ff00;"></div> <div style="clear: both;" style="color:#00ff00"> <input id="msg" /> <button onclick="send();">发送消息</button> </div> </body> </html>
3.5 添加启动类
package com.bjsxt.init; import java.util.Set; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpointConfig; /** * 项目启动时会自动启动,类似与ContextListener. * 是webSocket的核心配置类。 * @author xiongzichao * */ public class ServerConfig implements ServerApplicationConfig { //扫描src下所有类@ServerEndPoint注解的类。 @Override public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scan) { System.out.println("扫描到"+scan.size()+"个服务端程序"); return scan; } //获取所有以接口方式配置的webSocket类。 @Override public Set<ServerEndpointConfig> getEndpointConfigs( Set<Class<? extends Endpoint>> point) { System.out.println("实现EndPoint接口的类数量:"+point.size()); return null; } }
3.6 添加客户端发送给服务端消息实体
package com.bjsxt.vo; /** * 客户端发送给服务端消息实体 * @author xiongzichao * */ public class ContentVo { private String to; private String msg; private Integer type; public String getTo() { return to; } public void setTo(String to) { this.to = to; } 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; } }
3.7 添加服务端发送给客户端消息实体
package com.bjsxt.vo; import java.util.Date; import java.util.List; /** * 服务端发送给客户端消息实体 * @author xiongzichao * */ public class Message { private String alert; // private List<String> names; private String sendMsg; private String from; private String date; public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getSendMsg() { return sendMsg; } public void setSendMsg(String sendMsg) { this.sendMsg = sendMsg; } public String getFrom() { return from; } public void setFrom(String from) { this.from = from; } public String getAlert() { return alert; } public void setAlert(String alert) { this.alert = alert; } public List<String> getNames() { return names; } public void setNames(List<String> names) { this.names = names; } public Message() { super(); } }
3.8 添加服务端程序
package com.bjsxt.server; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import com.bjsxt.vo.ContentVo; import com.bjsxt.vo.Message; import com.google.gson.Gson; /** * 总通信管道 * @author xiongzichao * */ @ServerEndpoint("/chatSocket") public class ChatSocket { //定义一个全局变量集合sockets,用户存放每个登录用户的通信管道 private static Set<ChatSocket> sockets=new HashSet<ChatSocket>(); //定义一个全局变量Session,用于存放登录用户的用户名 private Session session; //定义一个全局变量map,key为用户名,该用户对应的session为value private static Map<String, Session> map=new HashMap<String, Session>(); //定义一个数组,用于存放所有的登录用户,显示在聊天页面的用户列表栏中 private static List<String>names=new ArrayList<String>(); private String username; private Gson gson=new Gson(); /* * 监听用户登录 */ @OnOpen public void open(Session session){ this.session = session; //将当前连接上的用户session信息全部存到scokets中 sockets.add(this); //拿到URL路径后面所有的参数信息 String queryString = session.getQueryString(); System.out.println(); //截取=后面的参数信息(用户名),将参数信息赋值给全局的用户名 this.username = queryString.substring(queryString.indexOf("=")+1); //每登录一个用户,就将该用户名存入到names数组中,用于刷新好友列表 names.add(this.username); //将当前登录用户以及对应的session存入到map中 this.map.put(this.username, this.session); System.out.println("用户"+this.username+"进入聊天室"); Message message = new Message(); message.setAlert("用户"+this.username+"进入聊天室"); //将当前所有登录用户存入到message中,用于广播发送到聊天页面 message.setNames(names); //将聊天信息广播给所有通信管道(sockets) broadcast(sockets, gson.toJson(message) ); } /* * 退出登录 */ @OnClose public void close(Session session){ //移除退出登录用户的通信管道 sockets.remove(this); //将用户名从names中剔除,用于刷新好友列表 names.remove(this.username); Message message = new Message(); System.out.println("用户"+this.username+"退出聊天室"); message.setAlert(this.username+"退出当前聊天室!!!"); //刷新好友列表 message.setNames(names); broadcast(sockets, gson.toJson(message)); } /* * 接收客户端发送过来的消息,然后判断是广播还是单聊 */ @OnMessage public void receive(Session session,String msg) throws IOException{ //将客户端消息转成json对象 ContentVo vo = gson.fromJson(msg, ContentVo.class); //如果是群聊,就像消息广播给所有人 if(vo.getType()==1){ Message message = new Message(); message.setDate(new Date().toLocaleString()); message.setFrom(this.username); message.setSendMsg(vo.getMsg()); broadcast(sockets, gson.toJson(message)); }else{ Message message = new Message(); message.setDate(new Date().toLocaleString()); message.setFrom(this.username); message.setAlert(vo.getMsg()); message.setSendMsg("<font color=red>正在私聊你:</font>"+vo.getMsg()); String to = vo.getTo(); //根据单聊对象的名称拿到要单聊对象的Session Session to_session = this.map.get(to); //如果是单聊,就将消息发送给对方 to_session.getBasicRemote().sendText(gson.toJson(message)); } } /* * 广播消息 */ public void broadcast(Set<ChatSocket>sockets ,String msg){ //遍历当前所有的连接管道,将通知信息发送给每一个管道 for(ChatSocket socket : sockets){ try { //通过session发送信息 socket.session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); } } } }
4.0 实现界面
注意
这里一共需要三个jar包,分别是WebSocket-api.jar这个定义webSocket应用程序开发接口!tomcat7-webSocket.jar tomcat服务器对于webSocket接口的实现!!还有一个gson.jar用于序列化实体类信息。前面两个jar包在tomcat7的lib目录里面可以找到。