1.客户端发送一个握手包告诉服务端它想升级成WebSocket,不知道服务端是否同意。这时服务端支持WebSocket协议,则会返回一个握手包告诉客户端没问题,升级已确认,然后,就成功建立了一条WebSocket连接,该连接支持双向通信,并且使用WebSocket协议的数据帧格式发送消息。
握手过程需要说明,为了让WebSocket协议能和现有HTTP协议Web架构互相兼容,WebSocket协议的握手要基于HTTP协议.
WebSocket特点如下:
- 单一TCP长连接,采用全双工通信模式
- 对代理、防火墙透明
- 无头部信息、消息更明确
- 通过ping/pong来保活
- 服务器可以主动推送消息给客户端,不在需要客户轮询
通过javaScript 中的API可以直接操作WebSocket对象,其示例如下:
var ws = new WebSocket(“ws://localhost:8080”); ws.onopen = function()// 建⽴成功之后触发的事件 { console.log(“打开连接”); ws.send("ddd"); // 发送消息 }; ws.onmessage = function(evt) { // 接收服务器消息 console.log(evt.data); }; ws.onclose = function(evt) { console.log(“WebSocketClosed!”); // 关闭连接 }; ws.onerror = function(evt) { console.log(“WebSocketError!”); // 连接异常 };
websocket请求头:
其中包含Upgrade:websocket就告诉服务器端客户端想升级协议。
服务端响应、并建立连接
此时如果服务端支持websocket协议,则它会发送一个同意客户端升级协议的报文。其中"Upgrade:websocket" 就告诉 客户端服务器同意客户端的升级协议。
连接状态查看:
通过ws.readyState可查看当前连接状态可选值如下:
1.CONNECT(0):表示还没建立连接
2.OPEN(1):已经建立连接,可以进行通讯
3.CLISING(2):通过关闭握手,正在关闭连接
4.CLOSED(3):连接已经关闭或无法打开
客户端JS 的实现:
if (!window.WebSocket && window.MozWebSocket) window.WebSocket=window.MozWebSocket; if (!window.WebSocket) alert("No Support "); var ws; $(document).ready(function(){ $("#sendbutton").attr("disabled", false); $("#sendbutton").click(sendMessage); startWebSocket(); }) /** * 发送消息按钮 * @returns */ function sendMessage() { var msg="wyy:"+$("#message").val(); send(msg); } function send(data) { console.log("Send:"+data); ws.send(data); } function startWebSocket() { ws = new WebSocket("ws://" + location.host + "/WebSocket/websocket/1"); ws.onopen = function(){ console.log("success open"); $("#sendbutton").attr("disabled", false); }; //接收后端的消息并处理 ws.onmessage = function(event) { alert("RECEIVE:"+event.data); handleData(event.data); }; ws.onclose = function(event) { console.log('Client notified socket has closed',event); }; } function handleData(data) { $("#msg").html(data); }
客户端JS 的实现: if (!window.WebSocket && window.MozWebSocket) window.WebSocket=window.MozWebSocket; if (!window.WebSocket) alert("No Support "); var ws; $(document).ready(function(){ $("#sendbutton").attr("disabled", false); $("#sendbutton").click(sendMessage); startWebSocket(); }) /** * 发送消息按钮 * @returns */ function sendMessage() { var msg="wyy:"+$("#message").val(); send(msg); } function send(data) { console.log("Send:"+data); ws.send(data); } function startWebSocket() { ws = new WebSocket("ws://" + location.host + "/WebSocket/websocket/1"); ws.onopen = function(){ console.log("success open"); $("#sendbutton").attr("disabled", false); }; //接收后端的消息并处理 ws.onmessage = function(event) { alert("RECEIVE:"+event.data); handleData(event.data); }; ws.onclose = function(event) { console.log('Client notified socket has closed',event); }; } function handleData(data) { $("#msg").html(data); }
import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.EndpointConfig; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; /** * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 * @ServerEndpoint 可以把当前类变成websocket服务类 */ @ServerEndpoint("/websocket/{userno}") public class WebSocketTest { // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static ConcurrentHashMap<String, WebSocketTest> webSocketSet = new ConcurrentHashMap<String, WebSocketTest>(); // 与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; // 当前发消息的人员编号 private String userno = ""; /** * 连接建立成功调用的方法 * * @param session * 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(@PathParam(value = "userno") String param, Session session, EndpointConfig config) { System.err.println(this); userno = param;// 接收到发送消息的人员编号 this.session = session; webSocketSet.put(param, this);// 加入map中 addOnlineCount(); // 在线数加1 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { if (!userno.equals("")) { webSocketSet.remove(userno); // 从set中删除 subOnlineCount(); // 在线数减1 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); } } /** * 收到客户端消息后调用的方法 * * @param message * 客户端发送过来的消息 * @param session * 可选的参数 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("来自客户端的消息:" + message); // session.get // 群发消息 sendAll(message); } /** * 给指定的人发送消息 可以在项目中直接调用这个方法时 * * @param message */ private void sendToUser(String message) { // 给指定的人推送消息,推送消息之前,肯定知道userno String now = getNowTime(); try { if (webSocketSet.get(1) != null) { webSocketSet.get(1).sendMessage(now + "用户" + userno + "发来消息:" + " <br/> " + message); } else { System.out.println("当前用户不在线"); } } catch (IOException e) { e.printStackTrace(); } } /** * 给所有人发消息 * * @param message */ private void sendAll(String message) { String now = getNowTime(); String sendMessage = message.split("[|]")[0]; // 遍历HashMap for (String key : webSocketSet.keySet()) { try { // 判断接收用户是否是当前发消息的用户 if (!userno.equals(key)) { webSocketSet.get(key).sendMessage(now + "用户" + userno + "发来消息:" + " <br/> " + sendMessage); System.out.println("key = " + key); } } catch (IOException e) { e.printStackTrace(); } } } /** * 获取当前时间 * * @return */ private String getNowTime() { Date date = new Date(); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = format.format(date); return time; } /** * 发生错误时调用 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { System.out.println("发生错误"); error.printStackTrace(); } /** * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。 * * @param message * @throws IOException */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); // this.session.getAsyncRemote().sendText(message); } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketTest.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketTest.onlineCount--; } }