分布式-- WebSocket 全双工通讯

1. WebSocket

1). 背景

B/S结构的软件项目中有时客户端需要实时的获得服务器消息,但默认HTTP协议只支持请求响应模式,这样做可以简化Web服务器,减少服务器的负担,加快响应速度,因为服务器不需要与客户端长时间建立一个通信链接,但不容易直接完成实时的消息推送功能,如聊天室、后台信息提示、实时更新数据等功能,但通过polling、Long polling、长连接、Flash Socket以及HTML5中定义的WebSocket能完成该功能需要。

2). OSI 模型与 TCP/IP下载 pdf
分布式-- WebSocket 全双工通讯_第1张图片
图1.png
3). Socket简介

Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。Socket的英文原义是“孔”或“插座”,作为UNIX的进程通信机制。Socket可以实现应用程序间网络通信。


分布式-- WebSocket 全双工通讯_第2张图片
图2.png

I. Socket可以使用TCP/IP协议或UDP协议。
TCP/IP协议

  • TCP/IP协议是目前应用最为广泛的协议,是构成Internet国际互联网协议的最为基础的协议,由TCP和IP协议组成:
    => TCP协议:面向连接的、可靠的、基于字节流的传输层通信协议,负责数据的可靠性传输的问题。
    => IP协议:用于报文交换网络的一种面向数据的协议,主要负责给每台网络设备一个网络地址,保证数据传输到正确的目的地。
  • UDP协议
    UDP特点:无连接、不可靠、基于报文的传输层协议,优点是发送后不用管,速度比TCP快。

II. B/S架构的系统多使用HTTP协议,HTTP协议的特点(底层通信使用Socket):

  • 无状态协议
  • 用于通过 Internet 发送请求消息和响应消息
  • 使用端口接收和发送消息,默认为80端口


    分布式-- WebSocket 全双工通讯_第3张图片
    图3.png
4).双向通信与消息推送
  • 轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
    �优点:后端程序编写比较容易。
    �缺点:请求中有大半是无用,浪费带宽和服务器资源。
    �实例:适于小型应用。
  • 长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
    �优点:在无消息的情况下不会频繁的请求,耗费资小。
    �缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。 Comet异步的ashx,
    �实例:WebQQ、Hi网页版、Facebook IM。
  • 长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。
    �优点:消息即时到达,不发无用请求;管理起来也相对便。
    �缺点:服务器维护一个长连接会增加开销。
    �实例:Gmail聊天
  • Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。
    �优点:实现真正的即时通信,而不是伪即时。
    �缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
    �实例:网络互动游戏。
  • Websocket: WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。
    �特点: 事件驱动、异步、使用ws或者wss协议的客户端socket、能够实现真正意义上的推送功能。
    �缺点:少部分浏览器不支持,浏览器支持的程度与方式有区别。
图4.png

2. 示例

1). 客户端

websocket允许通过JavaScript建立与远程服务器的连接,从而实现客户端与服务器间双向的通信。在websocket中有两个方法:  
    1、send() 向远程服务器发送数据
    2、close() 关闭该websocket链接
  websocket监听函数:
    1、onopen 当网络连接建立时触发该事件
    2、onerror 当网络发生错误时触发该事件
    3、onclose 当websocket被关闭时触发该事件
    4、onmessage 当websocket接收到服务器发来的消息的时触发的事件,也是通信中最重要的一个监听事件。msg.data
  websocket还定义了一个readyState属性,这个属性可以返回websocket所处的状态:
    1、CONNECTING(0) websocket正尝试与服务器建立连接
    2、OPEN(1) websocket与服务器已经建立连接
    3、CLOSING(2) websocket正在关闭与服务器的连接
    4、CLOSED(3) websocket已经关闭了与服务器的连接
  websocket的url开头是ws,如果需要ssl加密可以使用wss,当我们调用websocket的构造方法构建一个websocket对象(new WebSocket(url))的之后,就可以进行即时通信了。

2). 创建Web工程

I. 创建chat.html页面



    
        
        趣味聊天室
        
    
    
        
        
趣味聊天室
聊天记录
自己的DIY聊天室
发送(S)
关闭(C)

II. 图片资源


图5.png

III. 目录结构


图6.png
3). 服务器ChatServer代码
/**
 * 趣味聊天的服务端程序
 * 
 * @author mazaiting
 */
// 声明WebSocket服务器的地址
@ServerEndpoint("/chatServer")
public class ChatServer {
    /** 是否是第一次进入 */
    private boolean isFirstFlag = true;
    private Session session;
    /** 用户名 */
    private String userName;
    /**
     * 记录此次聊天室的服务端有多少个连接 key代表此次客户端的Session ID,value代表此次连接对象
     */
    private static final HashMap connectMap = new HashMap<>();
    /**
     * 保存所有用户的昵称信息 key是Session ID,value才是用户名
     */
    private static final HashMap userMap = new HashMap<>();

    /**
     * 服务端收到客户端连接请求,连接成功后会执行此方法
     * 
     * @param session
     */
    @OnOpen
    public void start(Session session) {
        this.session = session;
        connectMap.put(session.toString(), this);
    }

    /**
     * 发送消息
     * 
     * @param clientMessage
     *            消息/用户名
     * @param session
     *            会话
     */
    @OnMessage
    public void chat(String clientMessage, Session session) {
        // 消息
        String message;
        // 判断是否为第一次发送消息
        if (isFirstFlag) {
            // 用户名赋值
            this.userName = clientMessage;
            // 将新进来的用户保存到用户map
            userMap.put(session.getId(), userName);
            // 构造发送给客户端的提示信息
            message = htmlMessage("系统消息", userName + "进入聊天室");
            // 输入昵称后,代表isFirstFlag=false
            isFirstFlag = false;
        } else {
            // 构造发送给客户端的提示信息
            message = htmlMessage(userMap.get(session.getId()), clientMessage);
        }
        sendMessageForAll(message);
    }

    /**
     * ws.close()方法调用后,会触发后台的标注OnClose方法
     * 
     * @param session
     */
    @OnClose
    public void close(Session session) {
        // 当某个用户退出时,对其他用户进行广播
        String message = htmlMessage("系统消息", userMap.get(session.getId()) + "退出了聊天室");
        // 用户map移除
        userMap.remove(session.getId());
        // 链接map移除
        connectMap.remove(session.getId());
        sendMessageForAll(message);
    }

    /**
     * 格式化消息
     * @param userName 用戶名
     * @param message 消息
     * @return 消息
     */
    private String htmlMessage(String userName, String message) {
        StringBuffer messageBuffer = new StringBuffer();
        SimpleDateFormat sFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        messageBuffer.append("
"); messageBuffer.append("

"); messageBuffer.append("" + sFormat.format(new Date()) + ""); messageBuffer.append("

"); messageBuffer.append("
"); messageBuffer.append("" + userName + ""); messageBuffer.append("

"); messageBuffer.append("" + message + ""); messageBuffer.append("

"); messageBuffer.append("
"); messageBuffer.append("
"); return messageBuffer.toString(); } /** * 为所有用户发送消息 * * @param message * 消息 */ private void sendMessageForAll(String message) { // 当前对象 ChatServer client = null; // 将消息广播给所有人 for (String connectKey : connectMap.keySet()) { // 获取客户端 client = (ChatServer) connectMap.get(connectKey); // 给对应的web端发送一个文本信息 try { client.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } ; } } }
4). 服务端注解
  • @ServerEndpoint: 绑定服务器地址的注解方法
  • @OnOpen: WebSocket连接执行的注解方法
  • @OnMessage: 发送消息时执行的方法
  • @OnClose: 关闭时执行的方法
5). 测试
图7.png

3. 开源框架

1). 开源Java消息推送框架 Pushlet

Pushlet 是一个开源的 Comet 框架,Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。
Pushlet是一种comet实现:在Servlet机制下,数据从server端的Java对象直接推送(push)到(动态)HTML页面,而无需任何Javaapplet或者插件的帮助。它使server端可以周期性地更新client的web页面,这与传统的request/response方式相悖。浏览器client为兼容JavaScript1.4版本以上的浏览器(如InternetExplorer、FireFox),并使用JavaScript/DynamicHTML特性。而底层实现使用一个servlet通过Http连接到JavaScript所在的浏览器,并将数据推送到后者。

代码下载

你可能感兴趣的:(分布式-- WebSocket 全双工通讯)