學習WebSocket一(WebSocket初識)


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技術。

 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM