服务器端信息送
服务器端推送
HTTP协议的交互特征是浏览器主动发送请求, 服务响应请求. 在一些场景中, 需要服务器向浏览器主动实时发送信息, 例如股票数据, 监控信息等. 如果采用浏览器轮询方式, 会导致有大量的无效通信和数据处理, 因为数据没有变化, 不需要浏览器更新数据. 如果轮询间隔较大, 难以保持数据的实时性. 可能数据已经改变了很久, 浏览器还没有来轮询. 服务器主动发送信息给浏览器是一个比较好的思路.
websocket
使用websocket实现服务器向浏览器主动发送信息, 而不需要浏览器定时查询服务器端状态的变化. Websocket是HTML5的组件之一. 目前的浏览器对websocket的支持都比较好. 360浏览器在极速模式支持websocket.
实际上websocket是一个和HTTP配合使用的另一个协议. 协议的名称为ws, 其URL为ws://服务器IP地址:端口/应用目录
.
浏览器端程序
Websocket浏览器端编程
浏览器端页面是标准的HTML5页面. 采用JavaScript编写WebSocket程序.
其中关键内容为
- 创建一个到服务器的websocket对象. 相当于与服务器建立websocket连接. 其参数是websocket服务器端的URL, 例如:
ws://192.168.1.4:8084/j06/chatbot
ws = new WebSocket(websocket服务器端的URL);
- 如果建立连接成功, 则执行定义的事件响应函数onopen.
ws.onopen = function () {
log('WebSocket连接服务器成功!');
};
- 如果连接不成功, 或者服务器端主动关闭连接. 则执行定义的事件响应函数onclose.
ws.onclose = function (event) {
log('WebSocket连接关闭, Code: ' + event.code);
};
- 如果连接成功, 当服务器端主动发送信息到浏览器时, 执行定义的事件响应函数onmessage. 服务器发送来的消息内容在变量
event.data
中保存的字符串.
ws.onmessage = function (event) {
log('服务器: ' + event.data);
};
- 通过websocket, 浏览器向服务器发送字符串message, 使用
ws.send(message);
完整的网页程序
完整的网页文件内容如下:
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="GB18030">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript">
var ws;
function connectToServer() {
var target = document.getElementById('target').value;
if ('WebSocket' in window) {
ws = new WebSocket(target);
} else {
alert('WebSocket is not supported by this browser.');
return;
}
ws.onopen = function () {
log('WebSocket连接服务器成功!');
};
ws.onmessage = function (event) {
log('服务器: ' + event.data);
};
ws.onclose = function (event) {
log('WebSocket连接关闭, Code: ' + event.code);
};
}
function disconnect() {
if (ws !== null) {
ws.close();
ws = null;
}
}
function log(message) {
document.getElementById('message').innerHTML += message + '<br>';
}
function send() {
var message = document.getElementById('send').value;
log("Send: " + message);
ws.send(message);
}
</script>
</head>
<body>
<input id="target" type="text" name="target" value= "" size="100"/>
<div id="message" ></div>
<input type="submit" value="连接" onclick="connectToServer()" />
<br/>
<input id="send" type="text" name="send" value="" size="100"/>
<input type="submit" value="发送" onclick="send()" />
<input type="submit" value="关闭" onclick="disconnect()" />
<script>
document.getElementById('target').value =
"ws:" + document.location.toString().substr(5) + "chatbot";
connectToServer();
</script>
</body>
</html>
服务器端
Websocket服务器端编程
- 编写一个类ChatbotWebSocketEndpoint作为websocket服务端程序. 注解
@ServerEndpoint("/chatbot")
代表了此程序监听的目录.
@ServerEndpoint("/chatbot")
public class ChatbotWebSocketEndpoint{}
- 当浏览器与此服务端程序的建立连接时, 服务器自动执行事件处理方法onOpen(Session session). 参数session中对象中包括服务器与浏览器通信的全部信息. 例如,
@OnOpen
public void onOpen(Session session) {
try {
session.getBasicRemote().sendText("世界,你好!(这是从服务器主动发来的信息)");
} catch (IOException ex) {
System.out.println(ex);
}
sessions.add(session);
System.out.println("onOpen");
}
其中,
session.getBasicRemote().sendText("世界,你好!(这是从服务器主动发来的信息)");
向客户端发送字符串"世界,你好!(这是从服务器主动发来的信息)"
.
其中,
sessions.add(session);
用于把当前session放入一个static List<Session> sessions
, 以便于以后做群发或者作为多个浏览器之间相互通信的中介.
- 当接收到浏览器发送来消息时, 执行事件处理方法onMessage(Session session, String message). 参数message代表浏览器传递来的字符串. onMessage的返回值会发送给浏览器. [^此处嵌入估值1个亿的AI聊天算法.]
@OnMessage
public String onMessage(Session session, String message) {
System.out.println(message);
return message.replace("吗", "").replace("?", "!").replace("?", "");
}
- 当websocket发生错误时, 执行事件处理方法onError(Throwable t). 其参数t代表具体的异常状态.
@OnError
public void onError(Throwable t) {
System.out.println("onError");
System.out.println(t);
}
- 当websocket连接关闭时, 执行事件处理方法onClose().
@OnClose
public void onClose() {
System.out.println("onClose");
}
WebSocket服务器端程序
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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("/chatbot")
public class ChatbotWebSocketEndpoint {
public static List<Session> sessions = new ArrayList<>();
@OnMessage
public String onMessage(Session session, String message) {
System.out.println(message);
return message.replace("吗", "").replace("?", "!").replace("?", "");
}
@OnOpen
public void onOpen(Session session) {
try {
session.getBasicRemote().sendText("世界,你好!(这是从服务器主动发来的信息)");
} catch (IOException ex) {
System.out.println(ex);
}
sessions.add(session);
System.out.println("onOpen");
}
@OnError
public void onError(Throwable t) {
System.out.println("onError");
System.out.println(t);
}
@OnClose
public void onClose() {
System.out.println("onClose");
}
public static void broadcastToWebsocket(String message) {
sessions.removeIf(session -> session == null);
sessions.removeIf(session -> session.isOpen() == false);
for (Session session : sessions) {
try {
session.getBasicRemote().sendText(message
+ "(这是从服务器向浏览器主动群发的消息)");
} catch (IOException e) {
System.out.println(e);
}
}
}
}
应用场景扩展-广播
广播代码讲解
在另一个servlet中调用
ChatbotWebSocketEndpoint.broadcastToWebsocket(message);
可以把字符串message, 发送给每一个与服务端/chatbot
建立了连接的websocket网页.
ChatbotWebSocketEndpoint的broadcastToWebsocket()方法中, 为避免sessions存储无效session.
- 在链表sessions中删除那些等于null的session.
sessions.removeIf(session -> session == null);
- 删除sessions中删除那些没有处于正常打开状态的session.
sessions.removeIf(session -> session.isOpen() == false);
- 然后对sessions中的每个session, 发送信息
session.getBasicRemote().sendText(message + "(这是从服务器向浏览器主动群发的消息)");
调用广播方法的BroadcastServlet
BroadcastServlet中调用
ChatbotWebSocketEndpoint.broadcastToWebsocket(message);
广播消息给每一个与服务端/chatbot
建立了连接的websocket网页.
BroadcastServlet内容如下
import java.io.IOException;
import java.io.PrintWriter;
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(name = "BroadcastServlet", urlPatterns = {"/broadcast"})
public class BroadcastServlet extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String parameterMessage = request.getParameter("message");
if (parameterMessage != null) {
String message =
new String(parameterMessage.getBytes("iso-8859-1"), "utf-8");
ChatbotWebSocketEndpoint.broadcastToWebsocket(message);
}
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>BroadcastServlet</title>");
out.println("</head>");
out.println("<body>");
String form
= " <form action=\"broadcast\" method=\"post\">\n"
+ " 信息<input type=\"text\" name=\"message\" value=\"输入群发消息\">\n"
+ " <input type=\"submit\" value=\"群发\">\n"
+ " </form>";
out.println(form);
out.println("</body>");
out.println("</html>");
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "BroadcastServlet";
}
}
其中,
String message = new String(parameterMessage.getBytes("iso-8859-1"), "utf-8");
处理从网页传递来的中文字符串, 重新按照utf-8解码. 以获得正确编码的中文字符串.
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>BroadcastServlet</title>");
out.println("</head>");
out.println("<body>");
String form
= " <form action=\"broadcast\" method=\"post\">\n"
+ " 信息<input type=\"text\" name=\"message\" value=\"输入群发消息\">\n"
+ " <input type=\"submit\" value=\"群发\">\n"
+ " </form>";
out.println(form);
out.println("</body>");
out.println("</html>");
}
是Servlet直接向浏览器输出构成网页的字符串. [^对于复杂的网页, 这是难以理解的任务.]
运行结果
用websocket与服务器交互
启动程序后, 打开第一个网页. 运行结果
向服务器发送信息, 服务器应答
服务器端主动群发
服务器端通过另一个url, 向websocket客户端群发信息
websocket客户端收到群发信息
服务器向多设备的浏览器群发
PC机浏览器
使用IP地址访问网站, 运行效果如下
移动设备浏览器
移动设备通过浏览器访问网站的运行效果