webSocket
Posted 没昵称可用
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webSocket相关的知识,希望对你有一定的参考价值。
概述
什么是webSocket
WebSocket是为html5提供了一种在单个TCP上实现全双工的网络通信协议。
为什么需要webSocket
http协议是一种无状态、无连接的通信协议,使用的是请求/应答模型,客户端发出一次请求,服务器端作出响应,没有主动的向客户端推送信息的机制。这就导致客户端不能及时的获取后端数据。websocket实现了全双工主动向客户端推送信息的机制,是一种持久化的协议。
webSocket工作原理
客户端与服务端的只要建立一次连接,就可以实现客户端与服务度互相主动推送。WebSocket 协议本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息。运行图如下:
客户端发送给服务端的信息包括如下信息:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
其中Upgrade: websocket和Connection: Upgrade,告诉服务器:注意啦,我发起的是Websocket协议,快点帮我找到对应的助理处理~不是那个老土的HTTP。Sec-WebSocket-Key
是一个Base64 encode
的值,这个是浏览器随机生成的,告诉服务器,客户端要验证尼是不是真的是Websocket助理。Sec_WebSocket-Protocol
是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。Sec-WebSocket-Version
是告诉服务器所使用的Websocket Draft
(协议版本)
服务器会返回下列信息:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chatSec-WebSocket-Accept
这个则是经过服务器确认,并且加密过后的Sec-WebSocket-Key
。 服务器:好啦好啦,知道啦,给你看我的ID CARD来证明行了吧。。Sec-WebSocket-Protocol
则是表示最终使用的协议。
webSocket客户端
创建WebSocket对象
var Socket = new WebSocket(url, [protocol] ); //代码中的参数url为指定连接的URL,第二个参数为可接受的子协议。
webSocket属性
Socket.readyState:只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。
Socket.bufferedAmount:只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。
webSocket事件
事件 | 事件处理程序 | 描述 |
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端收到消息时触发 |
close | Socket.onclose | 客户端关闭连接时触发 |
error | Socket.onerror | 客户端出现连接错误时触发 |
webSocket方法
方法 | 描述 |
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
客户端实例
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>Java后端WebSocket的Tomcat实现</title> <meta content=\'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\' name=\'viewport\' /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> Welcome<br/><input id="text" type="text"/> <button onclick="send()">发送消息</button> <hr/> <button onclick="closeWebSocket()">关闭WebSocket连接</button> <hr/> <div id="message"></div> </body> <script type="text/javascript"> var websocket = null; //判断当前浏览器是否支持WebSocket if (\'WebSocket\' in window) { websocket = new WebSocket("ws://localhost:8080/qingyingyong/websocket"); } else { alert(\'当前浏览器 Not support websocket\') } //连接发生错误的回调方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket连接发生错误"); }; //连接成功建立的回调方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket连接成功"); } //接收到消息的回调方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } //连接关闭的回调方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket连接关闭"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById(\'message\').innerHTML += innerHTML + \'<br/>\'; } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); } //发送消息 function send() { var message = document.getElementById(\'text\').value; websocket.send(message); } </script> </html>
webSocket服务端
@ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端。
package qingyingyong; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/websocket") public class WebSocketTest { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; /** * 连接建立成功调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(Session session){ this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(){ webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 可选的参数 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("来自客户端的消息:" + message); //群发消息 for(WebSocketTest item: webSocketSet){ try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); continue; } } } /** * 发生错误时调用 * @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--; } }
webSocket代理
如果把 WebSocket 的通信看成是电话连接,nginx 的角色则像是电话接线员,负责将发起电话连接的电话转接到指定的客服。
Nginx 从 1.3 版开始正式支持 WebSocket 代理。如果你的 web 应用使用了代理服务器 Nginx,那么你还需要为 Nginx 做一些配置,使得它开启 WebSocket 代理功能。
以下为参考配置:
server { # this section is specific to the WebSockets proxying location /socket.io { proxy_pass http://app_server_wsgiapp/socket.io; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 600; } }
完整实例
1、聊天实例
在聊天时需要记录下好友的Session,在发送信息时,需要后台获取好友的Session,并通过Session进行推送。
后台代码:
package qingyingyong; import java.io.IOException; import java.util.Hashtable; import java.util.Map; import java.util.concurrent.CopyOnWriteArraySet; 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; import net.sf.json.JSONObject; @ServerEndpoint("/websocketnew") public class WebSocketNew { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static CopyOnWriteArraySet<WebSocketNew> webSocketSet = new CopyOnWriteArraySet<WebSocketNew>(); private static Map<String,Session> sessionMap = new Hashtable<String,Session>(); private Session session;//与某个客户端的连接会话,需要通过它来给客户端发送数据 private String userName;// 当前用户 /** * 连接建立成功调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据, */ @OnOpen public void onOpen(Session session){ this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); System.out.println("+++++++++++++++"+session.getId()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(){ webSocketSet.remove(this); //从set中删除 sessionMap.remove(userName);//关闭连接删除Session subOnlineCount(); //在线数减1 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 可选的参数 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("来自客户端的消息:" + message); //群发消息 JSONObject jsonObject = JSONObject.fromObject(message); String mString = jsonObject.getString("msg"); String name = jsonObject.getString("name"); String status = jsonObject.getString("status"); System.out.println("来自客2222户端的消息:" + status); if(status!=null&&"1".equals(status)){ sessionMap.put(name, session); userName = name; return; } Session session2 = sessionMap.get(name); try { session2.getBasicRemote().sendText(mString); } catch (IOException e) { e.printStackTrace(); } } /** * 发生错误时调用 * @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() { WebSocketNew.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketNew.onlineCount--; } }
前端代码
1、选择需要登录的账号和好友账号
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!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> </head> <body> <form action="index.jsp" method="post"> <table border="1"> <tr> <td>ç¨æ·åï¼</td><td><input type="text" name="loginName"/></td> <td>好åï¼</td><td><input type="text" name="name"/></td> </tr> <tr> <td colspan="2"><input type="submit" value="ç»å½"></td> </tr> </table> </form> </body> </html>
2、进行聊天
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>Java后端WebSocket的Tomcat实现</title> <meta content=\'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\' name=\'viewport\' /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> Welcome<br/><input id="text" type="text"/> <button onclick="send()">发送消息</button> <hr/> <button onclick="closeWebSocket()">关闭WebSocket连接</button> <hr/> <div id="message"></div> </body> <script type="text/javascript"> var loginName = \'<%=request.getParameter("loginName") %>\'; //获取登录账号 var name = \'<%=request.getParameter("name") %>\'; //获取好友 var websocket = null; //判断当前浏览器是否支持WebSocket if (\'WebSocket\' in window) { var wsUrl = "ws://localhost:8080/websocketdemo/websocketnew"; websocket = new WebSocket(wsUrl); alert(websocket.url); } else { alert(\'当前浏览器 Not support websocket\') } //连接发生错误的回调方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket连接发生错误"); }; //连接成功建立的回调方法 websocket.onopen = function () { var json = {"msg":"登陆成功","name":loginName,"status":1}; var message = JSON.stringify(json); websocket.send(message); setMessageInnerHTML("WebSocket连接成功"); } //接收到消息的回调方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } //连接关闭的回调方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket连接关闭"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById(\'message\').innerHTML += innerHTML + \'<br/>\'; } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); } //发送消息 function send() { var json = {"msg":document.getElementById(\'text\').value,"name":name,"status":0}; var message = JSON.stringify(json); websocket.send(message); } </script> </html>
以上是关于webSocket的主要内容,如果未能解决你的问题,请参考以下文章