WebSocket与消息推送

图片 7

介绍

现在很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP
request,然后由服务器返回最新的数据给客服端的浏览器。

这种传统的HTTP request 的模式带来很明显的缺点 –
浏览器需要不断的向服务器发出请求,然而HTTP request
的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽。

而最比较新的技术去做轮询的效果是comet
– 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求。

在 WebSocket
API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

时间: 2018-06-13阅读: 986标签: 聊天

转自张果原文
WebSocket与消息推送

运行环境:

背景

 

客户端

实现了websocket的浏览器

Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+

在浏览器中通过http仅能实现单向的通信,comet可以一定程度上模拟双向通信,但效率较低,并需要服务器有较好的支持;
flash中的socket和xmlsocket可以实现真正的双向通信,通过 flex ajax
bridge,可以在javascript中使用这两项功能.
可以预见,如果websocket一旦在浏览器中得到实现,将会替代上面两项技术,得到广泛的使用.面对这种状况,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽并达到实时通讯。

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

服务端

在JavaEE7中也实现了WebSocket协议。

一、Socket简介

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

图片 1

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

TCP/IP协议

TCP/IP协议是目前应用最为广泛的协议,是构成Internet国际互联网协议的最为基础的协议,由TCP和IP协议组成:
TCP协议:面向连接的、可靠的、基于字节流的传输层通信协议,负责数据的可靠性传输的问题。

IP协议:用于报文交换网络的一种面向数据的协议,主要负责给每台网络设备一个网络地址,保证数据传输到正确的目的地。

UDP协议

UDP特点:无连接、不可靠、基于报文的传输层协议,优点是发送后不用管,速度比TCP快。

依赖

Tomcat 7.0.47以上 + J2EE7

<dependency>
    <groupId>org.apache.tomcat</groupId>  
    <artifactId>tomcat-websocket-api</artifactId>  
    <version>7.0.47</version>  
    <scope>provided</scope>  
</dependency>  

<dependency>  
    <groupId>javax</groupId>  
    <artifactId>javaee-api</artifactId>  
    <version>7.0</version>  
    <scope>provided</scope>  
</dependency>

注意:早前业界没有统一的标准,各服务器都有各自的实现,现在J2EE7的JSR356已经定义了统一的标准,请尽量使用支持最新通用标准的服务器。

详见:

我是用的Tomcat 7.0.57 + Java7
必须是Tomcat 7.0.47以上
详见:

ps:最早我们是用的Tomcat 7自带的实现,后来要升级Tomcat
8,结果原来的实现方式在Tomcat 8不支持了,就只好切换到支持Websocket
1.0版本的Tomcat了。

主流的java web服务器都有支持JSR365标准的版本了,请自行Google。

用nginx做反向代理的需要注意啦,socket请求需要做特殊配置的,切记!

Tomcat的处理方式建议修改为NIO的方式,同时修改连接数到合适的参数,请自行Google!

服务端不需要在web.xml中做额外的配置,Tomcat启动后就可以直接连接了。

原理

二、WebSocket简介与消息推送

B/S架构的系统多使用HTTP协议,HTTP协议的特点:

1 无状态协议
2 用于通过 Internet 发送请求消息和响应消息
3 使用端口接收和发送消息,默认为80端口
底层通信还是使用Socket完成。

图片 2

HTTP协议决定了服务器与客户端之间的连接方式,无法直接实现消息推送(F5已坏),一些变相的解决办法:

双向通信与消息推送

轮询:客户端定时向服务器发送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

能够实现真正意义上的推送功能

缺点:

少部分浏览器不支持,浏览器支持的程度与方式有区别。

图片 3

实现

import com.dooioo.websocket.utils.SessionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

/**
 * 功能说明:websocket处理类, 使用J2EE7的标准
 *         切忌直接在该连接处理类中加入业务处理代码
 * 作者:liuxing(2014-11-14 04:20)
 */
//relationId和userCode是我的业务标识参数,websocket.ws是连接的路径,可以自行定义
@ServerEndpoint("/websocket.ws/{relationId}/{userCode}")
public class WebsocketEndPoint {

    private static Log log = LogFactory.getLog(WebsocketEndPoint.class);

    /**
     * 打开连接时触发
     * @param relationId
     * @param userCode
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("relationId") String relationId,
                       @PathParam("userCode") int userCode,
                       Session session){
        log.info("Websocket Start Connecting: " + SessionUtils.getKey(relationId, userCode));
        SessionUtils.put(relationId, userCode, session);
    }

    /**
     * 收到客户端消息时触发
     * @param relationId
     * @param userCode
     * @param message
     * @return
     */
    @OnMessage
    public String onMessage(@PathParam("relationId") String relationId,
                            @PathParam("userCode") int userCode,
                            String message) {
        return "Got your message (" + message + ").Thanks !";
    }

    /**
     * 异常时触发
     * @param relationId
     * @param userCode
     * @param session
     */
    @OnError
    public void onError(@PathParam("relationId") String relationId,
                        @PathParam("userCode") int userCode,
                        Throwable throwable,
                        Session session) {
        log.info("Websocket Connection Exception: " + SessionUtils.getKey(relationId, userCode));
        log.info(throwable.getMessage(), throwable);
        SessionUtils.remove(relationId, userCode);
    }

    /**
     * 关闭连接时触发
     * @param relationId
     * @param userCode
     * @param session
     */
    @OnClose
    public void onClose(@PathParam("relationId") String relationId,
                        @PathParam("userCode") int userCode,
                        Session session) {
        log.info("Websocket Close Connection: " + SessionUtils.getKey(relationId, userCode));
        SessionUtils.remove(relationId, userCode);
    }

}

工具类用来存储唯一key和连接

这个是我业务的需要,我的业务是服务器有对应动作触发时,推送数据到客户端,没有接收客户端数据的操作。

import javax.websocket.Session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 功能说明:用来存储业务定义的sessionId和连接的对应关系
 *          利用业务逻辑中组装的sessionId获取有效连接后进行后续操作
 * 作者:liuxing(2014-12-26 02:32)
 */
public class SessionUtils {

    public static Map<String, Session> clients = new ConcurrentHashMap<>();

    public static void put(String relationId, int userCode, Session session){
        clients.put(getKey(relationId, userCode), session);
    }

    public static Session get(String relationId, int userCode){
        return clients.get(getKey(relationId, userCode));
    }

    public static void remove(String relationId, int userCode){
        clients.remove(getKey(relationId, userCode));
    }

    /**
     * 判断是否有连接
     * @param relationId
     * @param userCode
     * @return
     */
    public static boolean hasConnection(String relationId, int userCode) {
        return clients.containsKey(getKey(relationId, userCode));
    }

    /**
     * 组装唯一识别的key
     * @param relationId
     * @param userCode
     * @return
     */
    public static String getKey(String relationId, int userCode) {
        return relationId + "_" + userCode;
    }

}

推送数据到客户端

在其他业务方法中调用

/**
 * 将数据传回客户端
 * 异步的方式
 * @param relationId
 * @param userCode
 * @param message
 */
public void broadcast(String relationId, int userCode, String message) {
    if (TelSocketSessionUtils.hasConnection(relationId, userCode)) {
        TelSocketSessionUtils.get(relationId, userCode).getAsyncRemote().sendText(message);
    } else {
        throw new NullPointerException(TelSocketSessionUtils.getKey(relationId, userCode) + " Connection does not exist");
    }
}

我是使用异步的方法推送数据,还有同步的方法

详见:

客户端代码

var webSocket = null;
var tryTime = 0;
$(function () {
    initSocket();

    window.onbeforeunload = function () {
        //离开页面时的其他操作
    };
});

/**
 * 初始化websocket,建立连接
 */
function initSocket() {
    if (!window.WebSocket) {
        alert("您的浏览器不支持websocket!");
        return false;
    }

    webSocket = new WebSocket("ws://127.0.0.1:8080/websocket.ws/" + relationId + "/" + userCode);

    // 收到服务端消息
    webSocket.onmessage = function (msg) {
        console.log(msg);
    };

    // 异常
    webSocket.onerror = function (event) {
        console.log(event);
    };

    // 建立连接
    webSocket.onopen = function (event) {
        console.log(event);
    };

    // 断线重连
    webSocket.onclose = function () {
        // 重试10次,每次之间间隔10秒
        if (tryTime < 10) {
            setTimeout(function () {
                webSocket = null;
                tryTime++;
                initSocket();
            }, 500);
        } else {
            tryTime = 0;
        }
    };

}

其他调试工具

Java实现一个websocket的客户端

依赖:

<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.3.0</version>
</dependency>

代码:

import java.io.IOException;  
import javax.websocket.ClientEndpoint;  
import javax.websocket.OnError;  
import javax.websocket.OnMessage;  
import javax.websocket.OnOpen;  
import javax.websocket.Session;  

@ClientEndpoint  
public class MyClient {  
    @OnOpen  
    public void onOpen(Session session) {  
        System.out.println("Connected to endpoint: " + session.getBasicRemote());  
        try {  
            session.getBasicRemote().sendText("Hello");  
        } catch (IOException ex) {  
        }  
    }  

    @OnMessage  
    public void onMessage(String message) {  
        System.out.println(message);  
    }  

    @OnError  
    public void onError(Throwable t) {  
        t.printStackTrace();  
    }  
}


import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.net.URI;  
import javax.websocket.ContainerProvider;  
import javax.websocket.DeploymentException;  
import javax.websocket.Session;  
import javax.websocket.WebSocketContainer;  

public class MyClientApp {  

    public Session session;  

    protected void start()  
             {  

            WebSocketContainer container = ContainerProvider.getWebSocketContainer();  

            String uri = "ws://127.0.0.1:8080/websocket.ws/relationId/12345";  
            System.out.println("Connecting to " + uri);  
            try {  
                session = container.connectToServer(MyClient.class, URI.create(uri));  
            } catch (DeploymentException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }               

    }  
    public static void main(String args[]){  
        MyClientApp client = new MyClientApp();  
        client.start();  

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
        String input = "";  
        try {  
            do{  
                input = br.readLine();  
                if(!input.equals("exit"))  
                    client.session.getBasicRemote().sendText(input);  

            }while(!input.equals("exit"));  

        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
}

chrome安装一个websocket客户端调试

图片 4

现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP
request,然后由服务器返回最新的数据给客户端的浏览器。这种传统的HTTP
request 的模式带来很明显的缺点 –
浏览器需要不断的向服务器发出请求,然而HTTP request
的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。

三、WebSocket客户端

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))的之后,就可以进行即时通信了。

<!DOCTYPE html>
<html>

    <head>
        <meta name="viewport" content="width=device-width" />
        <title>WebSocket 客户端</title>
    </head>

    <body>
        <div>
            <input type="button" id="btnConnection" value="连接" />
            <input type="button" id="btnClose" value="关闭" />
            <input type="button" id="btnSend" value="发送" />
        </div>
        <script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            var socket;
            if(typeof(WebSocket) == "undefined") {
                alert("您的浏览器不支持WebSocket");
                return;
            }

            $("#btnConnection").click(function() {
                //实现化WebSocket对象,指定要连接的服务器地址与端口
                socket = new WebSocket("ws://192.168.1.2:8888");
                //打开事件
                socket.onopen = function() {
                    alert("Socket 已打开");
                    //socket.send("这是来自客户端的消息" + location.href + new Date());
                };
                //获得消息事件
                socket.onmessage = function(msg) {
                    alert(msg.data);
                };
                //关闭事件
                socket.onclose = function() {
                    alert("Socket已关闭");
                };
                //发生了错误事件
                socket.onerror = function() {
                    alert("发生了错误");
                }
            });

            //发送消息
            $("#btnSend").click(function() {
                socket.send("这是来自客户端的消息" + location.href + new Date());
            });

            //关闭
            $("#btnClose").click(function() {
                socket.close();
            });
        </script>
    </body>

</html>

最后

为了统一的操作体验,对于一些不支持websocket的浏览器,请使用socketjs技术做客户端开发。

而比较新的技术去做轮询的效果是Comet –
用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求。

四、WebSocket服务器端

JSR356定义了WebSocket的规范,Tomcat7中实现了该标准。JSR356 的 WebSocket
规范使用 javax.websocket.*的 API,可以将一个普通 Java 对象(POJO)使用
@ServerEndpoint 注释作为 WebSocket 服务器的端点。

@ServerEndpoint("/push")
 public class EchoEndpoint {

 @OnOpen
 public void onOpen(Session session) throws IOException {
 //以下代码省略...
 }

 @OnMessage
 public String onMessage(String message) {
 //以下代码省略...
 }

 @Message(maxMessageSize=6)
 public void receiveMessage(String s) {
 //以下代码省略...
 } 

 @OnError
 public void onError(Throwable t) {
 //以下代码省略...
 }

 @OnClose
 public void onClose(Session session, CloseReason reason) {
 //以下代码省略...
 } 

 }

上面简洁代码即建立了一个WebSocket的服务端,@ServerEndpoint(“/push”)的annotation注释端点表示将WebSocket服务端运行在ws://[Server端IP或域名]:[Server端口]/项目/push的访问端点,客户端浏览器已经可以对WebSocket客户端API发起HTTP长连接了。
使用ServerEndpoint注释的类必须有一个公共的无参数构造函数,@onMessage注解的Java方法用于接收传入的WebSocket信息,这个信息可以是文本格式,也可以是二进制格式。
OnOpen在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。Session表明两个WebSocket端点对话连接的另一端,可以理解为类似HTTPSession的概念。
OnClose在连接被终止时调用。参数closeReason可封装更多细节,如为什么一个WebSocket连接关闭。
更高级的定制如@Message注释,MaxMessageSize属性可以被用来定义消息字节最大限制,在示例程序中,如果超过6个字节的信息被接收,就报告错误和连接关闭。

package action;

import javax.websocket.CloseReason;
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;

//ws://127.0.0.1:8087/Demo1/ws/张三
@ServerEndpoint("/ws/{user}")
public class WSServer {
    private String currentUser;

    //连接打开时执行
    @OnOpen
    public void onOpen(@PathParam("user") String user, Session session) {
        currentUser = user;
        System.out.println("Connected ... " + session.getId());
    }

    //收到消息时执行
    @OnMessage
    public String onMessage(String message, Session session) {
        System.out.println(currentUser + ":" + message);
        return currentUser + ":" + message;
    }

    //连接关闭时执行
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        System.out.println(String.format("Session %s closed because of %s", session.getId(), closeReason));
    }

    //连接错误时执行
    @OnError
    public void onError(Throwable t) {
        t.printStackTrace();
    }
}

url中的字符张三是的路径参数,响应请求的方法将自动映射。

在 WebSocket
API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket
协议中,为我们实现即时服务带来了两大好处:

五、测试运行

图片 5

图片 6

1. Header

六、小结与消息推送框架

 Socket在应用程序间通信被广泛使用,如果需要兼容低版本的浏览器,建议使用反向ajax或长链接实现;如果纯移动端或不需考虑非现代浏览器则可以直接使用websocket。Flash实现推送消息的方法不建议使用,因为依赖插件且手机端支持不好。关于反向ajax也有一些封装好的插件如“Pushlet”

互相沟通的Header是很小的-大概只有 2 Bytes

6.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所在的浏览器,并将数据推送到后者。

2. Server Push

6.2、开源DotNet消息推送框架SignalR

SignalR是一个ASP .NET下的类库,可以在ASP
.NET的Web项目中实现实时通信。在Web网页与服务器端间建立Socket连接,当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用长轮询来保证达到相同效果。

官网:

源码:

 图片 7

服务器的推送,服务器不再被动的接收到浏览器的request之后才返回数据,而是在有新数据时就主动推送给浏览器。

七、代码下载

一、项目简介

7.1、Java实现的服务器端代码与客户端代码下载

点击下载服务器端代码

点击下载客户端代码

WebSocket是HTML5一种新的协议,它实现了浏览器与服务器全双工通信,这里就将使用WebSocket来开发网页聊天室,前端框架会使用AmazeUI,后台使用Java,编辑器使用UMEditor。

7.2、DotNet服务器端手动连接实现代码下载

点击下载DotNet服务器端手动连接实现代码

二、涉及知识点

7.3、DotNet下使用SuperWebSocket三方库实现代码下载

点击下载DotNet下使用SuperWebSocket三方库实现代码

 

 

 

 

 

 

 

网页前端(HTML+CSS+JS)和JavaSE

三、软件环境 Tomcat 7 JDK 7 Eclipse JavaEE 现代浏览器

四、效果截图

效果1

五、项目实战

1. 新建项目

打开Eclipse JavaEE,新建一个名为Chat的Dynamic Web
Project,然后导入处理JSON格式字符串所需要的包,把commons-beanutils-1.8.0.jar、commons-collections-3.2.1.jar、commons-lang-2.5.jar、commons-logging-1.1.1.jar、ezmorph-1.0.6.jar和json-lib-2.4-jdk15.jar这几个包放在WebContent/WEB-INF/lib目录下,最后把项目发布到Tomcat服务器上,到此空项目就搭建完成了。

2. 编写前端页面

在WebContent目录下新建一个名为index.jsp的页面,这里使用了AmazeUI框架,它是一个跨屏自适应的前端框架,消息输入框使用了UMEditor,它是一个富文本在线编辑器,能让我们的消息内容多姿多彩。

首先从 AmazeUI官网
下载压缩包,然后解压把assets文件夹拷贝到WebContent目录下,这样我们就能使用AmazeUI了。

再从 UEditer官网
下载Mini版的JSP版本压缩包,解压后把整个目录拷贝到WebContent目录下,接下来就可以编写前端代码了,代码如下(你可以按照自己的喜好编写):

%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%!DOCTYPE htmlhtml lang="zh"headmeta charset="utf-8"meta http-equiv="X-UA-Compatible" content="IE=edge"meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"titleShiYanLou Chat/title!-- Set render engine for 360 browser --meta name="renderer" content="webkit"!-- No Baidu Siteapp--meta http-equiv="Cache-Control" content="no-siteapp" /link rel="alternate icon" href="assets/i/favicon.ico"link rel="stylesheet" href="assets/css/amazeui.min.css"link rel="stylesheet" href="assets/css/app.css"!-- umeditor css --link href="umeditor/themes/default/css/umeditor.css" rel="stylesheet"style.title { text-align: center;}.chat-content-container { height: 29rem; overflow-y: scroll; border: 1px solid silver;}/style/headbody !-- title start -- div  div  div  h1 ShiYanLou Chat/h1 /div /div /div !-- title end -- !-- chat content start -- div  div  div  ul /ul /div /div /div !-- chat content start -- !-- message input start -- div  div  div  form  div  script type="text/plain" /script /div /form /div /div div  div  div  span i /i/span input type="text" placeholder="Please enter nickname"/ /div /div div  button type="button"  i /i Send /button /div /div /div !-- message input end -- !--[if (gte IE 9)|!(IE)]!-- script src="assets/js/jquery.min.js"/script !--![endif]-- !--[if lte IE 8 ] script src=""/script ![endif]-- !-- umeditor js -- script charset="utf-8" src="umeditor/umeditor.config.js"/script script charset="utf-8" src="umeditor/umeditor.min.js"/script script src="umeditor/lang/zh-cn/zh-cn.js"/script script $(function() { // 初始化消息输入框 var um = UM.getEditor('myEditor'); // 使昵称框获取焦点 $('#nickname')[0].focus(); }); /script/body/html

编写完成之后启动Tomcat服务器,然后访问 ,会看到界面。

3. 编写后台代码

新建一个com.shiyanlou.chat的包,在包中创建一个名为ChatServer的类,从JavaEE
7开始就统一了WebSocket的API,因此无论是什么服务器,用Java写的代码都是一样的,代码如下:

package com.shiyanlou.chat;import java.text.SimpleDateFormat;import java.util.Date;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;import net.sf.json.JSONObject;/** * 聊天服务器类 * @author shiyanlou * */@ServerEndpoint("/websocket")public class ChatServer { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 日期格式化 @OnOpen public void open(Session session) { // 添加初始化操作 } /** * 接受客户端的消息,并把消息发送给所有连接的会话 * @param message 客户端发来的消息 * @param session 客户端的会话 */ @OnMessage public void getMessage(String message, Session session) { // 把客户端的消息解析为JSON对象 JSONObject jsonObject = JSONObject.fromObject(message); // 在消息中添加发送日期 jsonObject.put("date", DATE_FORMAT.format(new Date())); // 把消息发送给所有连接的会话 for (Session openSession : session.getOpenSessions()) { // 添加本条消息是否为当前会话本身发的标志 jsonObject.put("isSelf", openSession.equals(session)); // 发送JSON格式的消息 openSession.getAsyncRemote().sendText(jsonObject.toString()); } } @OnClose public void close() { // 添加关闭会话时的操作 } @OnError public void error(Throwable t) { // 添加处理错误的操作 }}

稍微解释下上面这段代码。

@ServerEndpoint(value =
“/websocket”)定义一个WebSocket服务端。value即访问地址。这个例子中:客户端通过
ws://{domain}/{context}来进行连接SetChatAnnotation
connections用于存储聊天室中的连接实例@OnPen,连接创建时调用的方法@OnClose,连接关闭时调用的方法@OnMessage,传输信息过程中调用的方法@OnError,发生错误时调用的方法broadcast(String
msg),通过connections,对所有其他用户推送信息的方法

4. 前后台交互

后台写完了,前台要用WebSocket连接后台,需要新建一个WebSocket对象,然后就可以和服务器端进行交互,从浏览器发送消息给服务器端,同时要验证输入框的内容是否为空,然后接受服务端发送的消息,把它们动态地添加到聊天内容框中,在

var um = UM.getEditor('myEditor');$('#nickname')[0].focus();// 新建WebSocket对象,最后的/websocket对应服务器端的@ServerEndpoint("/websocket")var socket = new WebSocket('ws://${pageContext.request.getServerName()}:${pageContext.request.getServerPort()}${pageContext.request.contextPath}/websocket'); // 处理服务器端发送的数据 socket.onmessage = function(event) { addMessage(event.data); }; // 点击Send按钮时的操作 $('#send').on('click', function() { var nickname = $('#nickname').val(); if (!um.hasContents()) { // 判断消息输入框是否为空 // 消息输入框获取焦点 um.focus(); // 添加抖动效果 $('.edui-container').addClass('am-animation-shake'); setTimeout("$('.edui-container').removeClass('am-animation-shake')", 1000); } else if (nickname == '') { // 判断昵称框是否为空 //昵称框获取焦点 $('#nickname')[0].focus(); // 添加抖动效果 $('#message-input-nickname').addClass('am-animation-shake'); setTimeout("$('#message-input-nickname').removeClass('am-animation-shake')", 1000); } else { // 发送消息 socket.send(JSON.stringify({ content : um.getContent(), nickname : nickname })); // 清空消息输入框 um.setContent(''); // 消息输入框获取焦点 um.focus(); } }); // 把消息添加到聊天内容中 function addMessage(message) { message = JSON.parse(message); var messageItem = 'li + (message.isSelf ? 'am-comment-flip' : 'am-comment') + '"' + 'a href="javascript:void(0)" img src="assets/images/'%20+%20(message.isSelf%20?%20'self.png'%20:%20'others.jpg')%20+%20'" alt="图片 8" width="48" height="48"//a' + 'divheaderdiv' + 'a href="javascript:void(0)"' + message.nickname + '/a time' + message.date + '/time/div/header' + 'div' + message.content + '/div/div/li'; $(messageItem).appendTo('#message-list'); // 把滚动条滚动到底部 $(".chat-content-container").scrollTop($(".chat-content-container")[0].scrollHeight); }

到这步,简单的网页聊天室就完成了。

六、小结

本次项目课使用WebSocket实现了简单的网页聊天室,其实WebSocket不仅可以应用于浏览器,也可以应用于桌面客户端。

来源:

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图