Java EE 7 去年刚刚发布了JSR356规范,使得WebSocket的Java API得到了统一,Tomcat从7.0.47开始支持JSR356,这样一来写WebSocket的时候,所用的代码都是可以一样的。今天终于体验了一把Tomcat发布的WebSocket,用着很爽,下面把这一历程分享给大家。

关键词:WebSocket, Tomcat

前提:使用Tomcat7.0.47,Firefox25.0.0.5046

首先Tomcat7.0.47自带WebSocket的示例程序,有两种版本,一种是使用注解(annotation API)的方式,另一种是继承javax.websocket.Endpoint类(programmatic API)。
先启动一下,看看效果。有四个example:Echo(回音)、Chat(聊天)、Multiplayer snake(多人蛇游戏)、Multiplayer drawboard(多人画板游戏)。
1、Echo
回音很简单,就是你输入什么,服务器给你返回什么。
界面效果​ 服务端代码
打开一个页面,首先点击Connect,保证连接到Websocket,
再在输入框里输入"I am angel1!",
点击Echo message,可以看到下面框里输入Sent和Received信息。
 

我们看一下它的代码是怎么实现的。
可以看出,在OnMessage处就是简单的通过传来的Session得到某一客户端,
再向其发出同样的消息。

@ServerEndpoint("/websocket/echoAnnotation")
public class EchoAnnotation {
 
    @OnMessage
    public void echoTextMessage(Session session, String msg, boolean last) {
        try {
            if (session.isOpen()) {
                session.getBasicRemote().sendText(msg, last);
            }
        } catch (IOException e) {
            try {
                session.close();
            } catch (IOException e1) {
                // Ignore
            }
        }
    }
 
    @OnMessage
    public void echoBinaryMessage(Session session, ByteBuffer bb,
            boolean last) {
        try {
            if (session.isOpen()) {
                session.getBasicRemote().sendBinary(bb, last);
            }
        } catch (IOException e) {
            try {
                session.close();
            } catch (IOException e1) {
                // Ignore
            }
        }

}
}

2、Chat
这里的聊天室跟群聊是同一个效果,不能一对一单聊。
界面效果​ 服务端代码
打开第一个页面,它会告诉你,你已经加入聊天了。
分析代码,就是一个新连接,会自动实例化一个ChatAnnotation,
这些ChatAnnotation对象共用同一些属性,
最重要的就是Set<ChatAnnotation> conncetions,
在OnOpen处把自身实例加入到conncetions中,并广播消息。
广播消息,是轮循conncetions并发送消息。

在界面输入对话框处输入文字,回车,消息就会发送到服务端。
就会传入到服务端某ChatAnnotation的OnMessage处,
然后把收到的消息与自身名称拼接后,再广播出去。
这下在线的客户端就都能够收到消息了。

第一个页面:

第二个页面:

@ServerEndpoint(value = "/websocket/chat")
public class ChatAnnotation {
 
    private static final String GUEST_PREFIX = "Guest";
    private static final AtomicInteger connectionIds = new AtomicInteger(0);
    private static final Set<ChatAnnotation> connections =
            new CopyOnWriteArraySet<ChatAnnotation>();
 
    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);
    }
 
 
    private static void broadcast(String msg) {
        for (ChatAnnotation client : connections) {
            try {
                client.session.getBasicRemote().sendText(msg);
            } catch (IOException 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);
            }
        }
    }

}

3、Multiplayer snake
这是一个多人在线小游戏,客户端通过操作上下左右键指挥自己的蛇,如果碰到别的蛇就死掉。还是一样,在服务端,对每个连接都维护一条蛇,有一个总的逻辑代码处理这些蛇,每条蛇再有各自的状态,向每个连接的客户发送消息。
@ServerEndpoint(value = "/websocket/snake")
public class SnakeAnnotation {
 
    public static final int PLAYFIELD_WIDTH = 640;
    public static final int PLAYFIELD_HEIGHT = 480;
    public static final int GRID_SIZE = 10;
 
    private static final AtomicInteger snakeIds = new AtomicInteger(0);
    private static final Random random = new Random();
 
 
    private final int id;
    private Snake snake;
public static String getRandomHexColor() {
        float hue = random.nextFloat();
        // sat between 0.1 and 0.3
        float saturation = (random.nextInt(2000) + 1000) / 10000f;
        float luminance = 0.9f;
        Color color = Color.getHSBColor(hue, saturation, luminance);
        return '#' + Integer.toHexString(
                (color.getRGB() & 0xffffff) | 0x1000000).substring(1);
    }
 
 
    public static Location getRandomLocation() {
        int x = roundByGridSize(random.nextInt(PLAYFIELD_WIDTH));
        int y = roundByGridSize(random.nextInt(PLAYFIELD_HEIGHT));
        return new Location(x, y);
    }
 
 
    private static int roundByGridSize(int value) {
        value = value + (GRID_SIZE / 2);
        value = value / GRID_SIZE;
        value = value * GRID_SIZE;
        return value;
    }
 
    public SnakeAnnotation() {
        this.id = snakeIds.getAndIncrement();
    }
@OnOpen
    public void onOpen(Session session) {
        this.snake = new Snake(id, session);
        SnakeTimer.addSnake(snake);
        StringBuilder sb = new StringBuilder();
        for (Iterator<Snake> iterator = SnakeTimer.getSnakes().iterator();
                iterator.hasNext();) {
            Snake snake = iterator.next();
            sb.append(String.format("{id: %d, color: '%s'}",
                    Integer.valueOf(snake.getId()), snake.getHexColor()));
            if (iterator.hasNext()) {
                sb.append(',');
            }
        }
        SnakeTimer.broadcast(String.format("{'type': 'join','data':[%s]}",
                sb.toString()));
    }
 
 
    @OnMessage
    public void onTextMessage(String message) {
        if ("west".equals(message)) {
            snake.setDirection(Direction.WEST);
        } else if ("north".equals(message)) {
            snake.setDirection(Direction.NORTH);
        } else if ("east".equals(message)) {
            snake.setDirection(Direction.EAST);
        } else if ("south".equals(message)) {
            snake.setDirection(Direction.SOUTH);
        }

}

 
 
    
 
 
    
 
 
    @OnClose
    public void onClose() {
        SnakeTimer.removeSnake(snake);
        SnakeTimer.broadcast(String.format("{'type': 'leave', 'id': %d}",
                Integer.valueOf(id)));
    }
 
 
    @OnError
    public void onError(Throwable t) throws Throwable {
        // Most likely cause is a user closing their browser. Check to see if
        // the root cause is EOF and if it is ignore it.
        // Protect against infinite loops.
        int count = 0;
        Throwable root = t;
        while (root.getCause() != null && count < 20) {
            root = root.getCause();
            count ++;
        }
        if (root instanceof EOFException) {
            // Assume this is triggered by the user closing their browser and
            // ignore it.
        } else {
            throw t;
        }

}
}

4、自写客户端
Multiplayer drawboard就不分析了,在Firefox25.0.0.5046上一直loading。下面探讨一下,Java客户端的编写。
界面和ClientEndpoit​
入口代码
下面是调用了echoAnnotation的websocket的客户端与服务端交互过程。
同样是客户端发给服务端一个消息,服务端收到后发给客户端,
客户端收到后显示出来。
 
 
客户端代码也很简单,没有什么逻辑,只管把接收的打印出来就行了。
需要注意的是,需要引用的jar包只在Java EE 7中包含。
包括javax.websocket-api.jar、tyrus-client.jar、
tyrus-container-grizzly.jar、tyrus-core.jar、
tyrus-websocket-core.jar、tyrus-spi.jar、tyrus-server.jar、
nucleus-grizzly-all.jar
 
同样的也可以调用其它的websocket,比如chat...使用起来非常方便。
 
 
@ClientEndpoint
public class MyClient {
 
    @OnOpen
    public void onOpen(Session session) {
 
    }
 
    @OnMessage
    public void onMessage(String message) {
        System.out.println("Client onMessage: " + message);
    }
 
    @OnClose
    public void onClose() {
 
    }
 

}

public class Main {
 
    private static String uri = "ws://localhost/examples/websocket/echoAnnotation";
    private static Session session;
 
    private void start() {
        WebSocketContainer container = null;
        try {
            container = ContainerProvider.getWebSocketContainer();
        } catch (Exception ex) {
            System.out.println("error" + ex);
        }
 
        try {
            URI r = URI.create(uri);
            session = container.connectToServer(MyClient.class, r);
        } catch (DeploymentException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Main client = new Main();
        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();
        }
    }

}

这样以来,从功能少的说,搭建向手机、客户端推送消息的服务器是非常简单的;从功能多的说,以后搭建安全的、扩展性好的、性能超群的游戏服务器,比如QQ斗地主游戏、泡泡卡丁车游戏,在线聊天服务器,也是简单的。http://www.cnblogs.com/wgp13x/p/3800030.html  这是我之前的局域网多人对战飞行棋的实现,因为它是C#局域网版本的,故没有用到WebSocket技术。

学习WebSocket一(WebSocket初识)的更多相关文章

  1. Spring 学习——基于Spring WebSocket 和STOMP实现简单的聊天功能

    本篇主要讲解如何使用Spring websocket 和STOMP搭建一个简单的聊天功能项目,里面使用到的技术,如websocket和STOMP等会简单介绍,不会太深,如果对相关介绍不是很了解的,请自 ...

  2. HTML5学习总结-08 WebSocket 服务器推送

    一 WebSocket 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展 ...

  3. 学习html5的WebSocket连接

    1.什么是WebSocket WebSocket 是一种自然的全双工.双向.单套接字连接.使用WebSocket,你的HTTP 请求变成打开WebSocket 连接(WebSocket 或者WebSo ...

  4. Python Web学习笔记之WebSocket 通信过程与实现

    一.什么是 WebSocket ? WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输.但它跟 HTTP 没什么关系,它是基于 TCP 的一种独立实现. 以前客户端想知道服务 ...

  5. 【HTML5 WebSocket】WebSocket对象特性和方法

    <HTML5 WebSocket权威指南>学习笔记&3 WebSocket方法的对象特性 1. WebSocket方法 a. send方法 send方法用于在WebSocket连接 ...

  6. 【WebSocket】WebSocket介绍

    1.背景 在WebSocket出现之前客户端向服务器发出请求是通过http协议实现的,而http协议有个特点是通行请求只能由客户端发起,然后服务端响应查询结果,HTTP 协议没法让服务器主动向客户端推 ...

  7. Linux学习之CentOS(三)--初识linux的文件系统以及用户组等概念

    Linux学习之CentOS(三)--初识linux的文件系统以及用户组等概念 进入到了Linux学习之CentOS第三篇了,这篇文章主要记录下对linux文件系统的初步认识,以及用户组.用户权限.文 ...

  8. Linux学习之CentOS(二)--初识linux的一些常用命令

    Linux学习之CentOS(二)--初识linux的一些常用命令 在VM上安装完了CentOS6.4以后,看着linux系统成功跑起来,心里小激动了一把......但是前方学习的道路还很遥远... ...

  9. day 81 Vue学习一之vue初识

      Vue学习一之vue初识   本节目录 一 Vue初识 二 ES6的基本语法 三 Vue的基本用法 四 xxx 五 xxx 六 xxx 七 xxx 八 xxx 一 vue初识 vue称为渐进式js ...

  10. nginx支持websocket及websocket部分原理介绍

    nginx支持websocket及websocket部分原理介绍最近ipc通过websocket与server进行通行,经过无法通过nginx进行反向代理,只有直连nodejs端口.而且部署到阿里云用 ...

随机推荐

  1. React.js 小书 Lesson24 - PropTypes 和组件参数验证

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson24 转载请注明出处,保留原文链接和作者信息. 我们来了到了一个非常尴尬的章节,很多初学的朋友 ...

  2. find ip from hostname or find hostname from ip

    1. find ip from hostname ping <hostname> 2.fin hostname from ip nslookup <ip>

  3. jmeter(1)——环境部署及安装

    公司人事还有老大都找我谈了一下2019的目标和技能成长规划,所以整体想了一下,技能方面,自己今年准备从性能测试开始着手,也去咨询了一下大神,切入点最好是工具.性能测试是一门非常庞大的课程,最初级,最入 ...

  4. [PY3]——创建多值映射字典?/defaultdict默认字典/setdefault()

    Defaultdict 默认字典 collections 模块中的 defaultdict(默认字典),可以用来构造“一个键映射多个值”这样的字典 如果你想保持元素的插入顺序就应该使用list, 如果 ...

  5. SSIS教程:创建简单的ETL包

    SSIS: Microsoft SQL Server Integration Services.是一个可用于生成高性能数据集成解决方案的平台,其中包括数据仓库的提取(Extract).转换(Trans ...

  6. JAVA异常与异常处理详解

    一.异常简介 什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错.在java中,阻止当前方法或作用域的情况,称之为异常. java中异常的体系是怎么样的呢? 1.Java中的所有不正常类都 ...

  7. Java - 用builder代替构造器

    静态工厂和够构造器有一个共同的局限性:遇到大量的参数时无法很好的扩展. 先说说构造器. 其实field不多时重叠构造器(telescoping constructor)是个不错的方法,易于编写也易于调 ...

  8. MAC OS===>IntelliJ IDEA__ant__zookeeper源码编译

    1:配置ant环境 https://archive.apache.org/dist/ant/binaries/  官网下载文件  1.10.0以上需要JDK1.8以上 否则会报 Exception i ...

  9. Win10自带Ubuntu子系统下Mysql安装踩坑记录

    linux系统为win10自带Ubuntu子系统 错误的安装过程 我按照一般的方法安装mysql,安装步骤如下 1.升级源 $ sudo apt-get update 2.安装mysql $ sudo ...

  10. 初步理解impress.js

    1.认识impress.js impress.js 采用 CSS3 与 JavaScript 语言完成的一个可供开发者使用的表现层框架(演示工具). 现在普通开发者可以利用 impress.js 自己 ...