JavaEE-2020-06-websocket

该博客详细介绍了如何使用WebSocket实现实时的服务器到浏览器的信息推送,避免了HTTP协议下浏览器轮询的无效通信。内容涵盖WebSocket的原理、浏览器端编程、服务器端编程,以及广播功能的实现,展示了在不同设备上运行的效果。
摘要由CSDN通过智能技术生成

服务器端信息送

服务器端推送

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地址访问网站, 运行效果如下

在这里插入图片描述

移动设备浏览器

移动设备通过浏览器访问网站的运行效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值