WebSocket实例—初级聊天室(来自Tomcat8的examples)

来源:互联网 发布:数字移相算法 编辑:程序博客网 时间:2024/06/03 13:49

程序跑起来

在Tomcat的Webapps中自带有Tomcat的很多小例子。本文选取其中的关于WebSocket的例子,来进行学习。首先先将程序跑起来。
开发环境eclipse+tomcat8
1、新建项目名称为examples(自取名字也可以,但是在程序中也要更改,这里避免麻烦,就不该了)
2、将websocket文件夹拷贝到WebContent目录下
3、将WEB-INF/classes文件夹中websocket和util包拷贝到src目录下
4、导入必要的包,我缺的是juli.jar包,右键-》build path-》configure build path-》add External JARs,juli包在tomcat的bin目录下。

配置Tomcat步骤省略了,运行项目,在浏览器中输入
http://localhost:8080/examples/websocket/chat.xhtml
即能实现浏览器之间的对话。
运行如下所示:(注意我这里面改变了项目名,程序中也做了相应的改变)

这里写图片描述

程序分析

这样就实现了初级的聊天程序,我们再来分析一下源代码。

我们发现该段代码是基于注解的实现。注解是元数据,对代码进行说明。但是不影响代码的逻辑功能。我们先看一下这段代码大概实现了一个什么样的逻辑。
1、定义一下基本的变量信息,表示在线客户端的信息。定义了一个Set来保存会话连接。
2、当连接打开时,保存连接,并广播连接加入信息。
3、当服务器端接收到客户端的信息时,将信息进行广播。
4、当连接关闭时,广播连接关闭信息
5、对错误的处理。
然后,我们发现最核心的功能就是,
建立连接-》将连接保存在set中-》广播信息-》连接断开-》将连接从set中移除

服务器端

@ServerEndpoint(value = "/websocket/chat")public class ChatAnnotation {    private static final Log log = LogFactory.getLog(ChatAnnotation.class);    private static final String GUEST_PREFIX = "Guest";    private static final AtomicInteger connectionIds = new AtomicInteger(0);    private static final Set<ChatAnnotation> connections =            new CopyOnWriteArraySet<>();    private final String nickname;    private Session session;    public ChatAnnotation() {        nickname = GUEST_PREFIX + connectionIds.getAndIncrement();    }    @OnOpen    public void start(Session session) {        this.session = session;        connections.add(this);        String message = String.format("* %s %s", nickname, "has joined.");        broadcast(message);    }    @OnClose    public void end() {        connections.remove(this);        String message = String.format("* %s %s",                nickname, "has disconnected.");        broadcast(message);    }    @OnMessage    public void incoming(String message) {        // Never trust the client        String filteredMessage = String.format("%s: %s",                nickname, HTMLFilter.filter(message.toString()));        broadcast(filteredMessage);    }    @OnError    public void onError(Throwable t) throws Throwable {        log.error("Chat Error: " + t.toString(), t);    }    private static void broadcast(String msg) {        for (ChatAnnotation client : connections) {            try {                synchronized (client) {                    client.session.getBasicRemote().sendText(msg);                }            } catch (IOException e) {                log.debug("Chat Error: Failed to send message to client", e);                connections.remove(client);                try {                    client.session.close();                } catch (IOException e1) {                    // Ignore                }                String message = String.format("* %s %s",                        client.nickname, "has been disconnected.");                broadcast(message);            }        }    }}

我们的疑惑可以有以下几点:
1、注解所发挥的作用
2、CopyOnWriteArraySet功能
3、发送信息时为什么是 client.session.getBasicRemote().sendText(msg);
第一个问题
程序上的注解都有以下几个,了解它们最好的方式就是看开发者文档。
@ServerEndpoint(value = “/websocket/chat”)
@ServerEndPoint注释端点表示将WebSocket服务器运行在ws://[Server端IP或域名]:[Server端口]/websockets/echo的访问端点,客户端浏览器已经可以对WebSocket客户端API发起HTTP长连接了。使用ServerEndpoint注释的类必须有一个公共的无参数构造函数。
@OnOpen
方法级别的注解,用于一个新的websocket会话连接打开时,注解方法的回掉
@OnClose
同上
@OnError
同上
@OnMessage
@onMessage注解的Java方法用于接收传入的WebSocket的信息,这个信息可以是文本格式的,也可以是二进制格式
第二个问题
参考链接:http://ifeve.com/tag/copyonwritearrayset/
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
第三个问题
那我们先看一下session的含义。
它是一个接口,表示两个web socket端点之间的对话。
getBasicRemote()的含义是:返回一个引用,什么样的引用呢?一个远程端点对象的引用,这个远程端点,代表会话的一端,能够同步的给另一端发送消息。

客户端

 <script type="application/javascript"><![CDATA[        "use strict";        var Chat = {};        Chat.socket = null;        //判断浏览器是否支持websocket,支持就新建websocket对象,不支持就输出错误信息。        Chat.connect = (function(host) {            if ('WebSocket' in window) {                Chat.socket = new WebSocket(host);            } else if ('MozWebSocket' in window) {                Chat.socket = new MozWebSocket(host);            } else {                Console.log('Error: WebSocket is not supported by this browser.');                return;            }             //当连接打开时,监听回车键,发送消息。            Chat.socket.onopen = function () {                Console.log('Info: WebSocket connection opened.');                document.getElementById('chat').onkeydown = function(event) {                    if (event.keyCode == 13) {                        Chat.sendMessage();                    }                };            };            Chat.socket.onclose = function () {                document.getElementById('chat').onkeydown = null;                Console.log('Info: WebSocket closed.');            };            Chat.socket.onmessage = function (message) {                Console.log(message.data);            };        });       //进行Chat初始化,与服务器建立连接        Chat.initialize = function() {            if (window.location.protocol == 'http:') {                Chat.connect('ws://' + window.location.host + '/Test/websocket/chat');            } else {                Chat.connect('wss://' + window.location.host + '/Test/websocket/chat');            }        };        //发送消息        Chat.sendMessage = (function() {            var message = document.getElementById('chat').value;            if (message != '') {                Chat.socket.send(message);                document.getElementById('chat').value = '';            }        });        var Console = {};        Console.log = (function(message) {            var console = document.getElementById('console');            var p = document.createElement('p');            p.style.wordWrap = 'break-word';            p.innerHTML = message;            console.appendChild(p);            while (console.childNodes.length > 25) {                console.removeChild(console.firstChild);            }            console.scrollTop = console.scrollHeight;        });        Chat.initialize();        document.addEventListener("DOMContentLoaded", function() {            // Remove elements with "noscript" class - <noscript> is not allowed in XHTML            var noscripts = document.getElementsByClassName("noscript");            for (var i = 0; i < noscripts.length; i++) {                noscripts[i].parentNode.removeChild(noscripts[i]);            }        }, false);    ]]></script>
1 0
原创粉丝点击