原理
很多网站为了实现即时聊天,使用的是轮询方式(在特定的时间间隔,由浏览器向服务器端发出 Http request,然后由服务器返回最新的数据)实现。这种传统的 Http request的方式有个明显的缺点,浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。 比较新的方式是Comet---用了Ajax,但是还是要发出请求。 但是在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。这样做的两大好处是
- Header 互相沟通的Header是很小的-大概只有 2 Bytes
- Server Push 服务器的推送,服务器不再被动的接收到浏览器的request之后才返回数据,而是在有新数据时就主动推送给浏览器。
开发基于WebSocket协议的聊天室
开发环境
MyEclipse2014、JDK1.7.45 64位、Tomcat8
WebSocketConfig
package com.sx2.websocket;import java.util.Set;import javax.websocket.Endpoint;import javax.websocket.server.ServerApplicationConfig;import javax.websocket.server.ServerEndpointConfig;/** * * @author Jin * 在Tomcat启动的时候会加载此类(因为实现了ServerApplicationConfig接口) */public class WebSocketConfig implements ServerApplicationConfig{ @Override public Set> getAnnotatedEndpointClasses(Set > scan) { /* * 这里会自动扫描带有@ServerEndpoint注解的类 * 返回它们的类名集合,这里可以做一些过滤,比如过滤测试用的带有@ServerEndpoint注解的类 * 最后一定要返回过滤后的set集合,相当于注册服务 * 最后一定要返回过滤后的set集合,相当于注册服务 * 最后一定要返回过滤后的set集合,相当于注册服务 */ return scan; } @Override public Set getEndpointConfigs( Set > arg0) { return null; }}
chatSocket.java
import java.io.IOException;import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.util.Date;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;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.alibaba.fastjson.JSONObject;import com.sx2.pojo.WebSocketResult;/** * * @author Jin * 这里带有@ServerEndpoint注解,会自动被扫描到 */@ServerEndpoint("/chatSocket")public class ChatSocket { //所有的实例的集合 private static Setsockets=new HashSet (); //保存每个username对应的用户头像,这里只是这个项目中需要,如果实际项目中不需要,此变量无用 private static Map photos = new HashMap (); //保存每个用户的用户名 private static Set names=new HashSet (); //每个用户进入聊天室,都会开启一个session,要注意的是这里的session是javax.websocket.Session private Session session; //当有用户进入聊天室时,保存用户名 private String username; //当有用户进入聊天室时,保存用户头像,不是必须的 private String photo; /** * 每当有用户进入聊天室时,都会执行一次此函数 * @param session */ @OnOpen public void open(Session session){ this.session=session; sockets.add(this); //这里的逻辑是得到请求路径后的参数 //比如:ws://localhost:8080/SX2/chatSocket?username=jin&photo=/image/user/b3.png",那么queryString就是username=jin&photo=/image/user/b3.png //username为用户的用户名,是必须的。photo为用户的头像,看实际需要并不是必须的 String queryString = session.getQueryString(); //对得到queryString进行切割,得到用户名和头像 String[] infos = queryString.split("&"); this.username = infos[0].substring(infos[0].indexOf("=") + 1); this.photo = infos[1].substring(infos[1].indexOf("=") + 1); //WebSocket返回值的包装类 WebSocketResult message = new WebSocketResult(); String encodeUsername = ""; try { if(this.username.matches("[A-Za-z]")){ //如果用户的用户名是纯英文,比如Tom,Jerry,不用编码 encodeUsername = this.username; }else{ //如果用户的用户名不是纯英文的,则需要编码 encodeUsername = URLDecoder.decode(this.username,"UTF-8"); } this.username = encodeUsername; //将经过处理的用户名,添加到用户名集合中 names.add( encodeUsername); //将用户名集合放到返回值中 message.setNames(names); photos.put(encodeUsername, photo); message.setPhotos(photos); //将欢迎信息放到返回值中 message.setWelCome(encodeUsername+"进入聊天室!!"); //将返回值的包装类转为json字符串,向所有人打印欢迎信息。 broadcast(sockets, JSONObject.toJSONString(message)); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block message.setWelCome("服务器异常!"); broadcast(sockets, JSONObject.toJSONString(message)); } } @SuppressWarnings("deprecation") /** * 当有用户发送消息时,会执行此函数 * @param session 为此用户开启的session * @param msg 用户发送的消息文本 */ @OnMessage public void receive(Session session,String msg ){ WebSocketResult message=new WebSocketResult(); //将消息文本放到返回值中 message.setSendMsg(msg); //将来自哪个用户放到返回值中 message.setFrom(this.username); //将发送日期放到返回值中 message.setDate(new Date().toLocaleString()); //用户头像 message.setPhotos(photos); //向所有人打印发送的消息 broadcast(sockets, JSONObject.toJSONString(message)); } /** * * @param session 为此用户开启的session */ @OnClose public void close(Session session){ //从实例集合中移除此实例 sockets.remove(this); //从用户名集合中移除此用户名 names.remove(this.username); //从头像集合中移除此用户的头像 photos.remove(this.username); WebSocketResult message=new WebSocketResult(); //将欢送消息放到返回值中 message.setWelCome(this.username+"退出聊天室!!"); //重新设置用户名集合 message.setNames(names); //重新设置头像集合 message.setPhotos(photos); //向所有人打印欢送消息 broadcast(sockets, JSONObject.toJSONString(message)); } /** * * @param ss 所有实例集合 * @param msg 要向浏览器端发送的json字符串 */ public void broadcast(Set ss ,String msg ){ for (Iterator iterator = ss.iterator(); iterator.hasNext();) { ChatSocket chatSocket = (ChatSocket) iterator.next(); try { //使用session的getBasicRemote方法向浏览器端发送json字符串 chatSocket.session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); } } }}
WebSocketResult.java
import java.util.Map;import java.util.Set;/** * * @author Jin * WebSocket返回值的包装类 */public class WebSocketResult { private String sendMsg; private String date; private String from; private String welCome; private Setnames; private Map photos; public String getWelCome() { return welCome; } public void setWelCome(String welCome) { this.welCome = welCome; } public String getSendMsg() { return sendMsg; } public void setSendMsg(String sendMsg) { this.sendMsg = sendMsg; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getFrom() { return from; } public void setFrom(String from) { this.from = from; } public Set getNames() { return names; } public void setNames(Set names) { this.names = names; } public Map getPhotos() { return photos; } public void setPhotos(Map photos) { this.photos = photos; }}
客户端代码
Apache Tomcat WebSocket Examples: Chat