初識WebSocket

初識WebSocket

用Java和JavaScript基於WebSocket完成聊天室Democss

  什麼是WebSocket,WebSocket是一種基於TCP的網絡協議,就像HTTP同樣,它與HTTP最大的不一樣就是它是全雙工的,也就是服務器能夠主動發送數據給瀏覽器(是否是像Java中的Socket)。在HTTP中,瀏覽器發起請求以後服務器才能響應,給瀏覽器發送數據,服務器不能主動給瀏覽器發送數據。
  可是在不少時候,最簡單的就好比聊天室,在Http中只能採用輪訓的方式,也就是瀏覽器不停地訪問服務器查詢有沒有消息,這樣作效率很低,並且很是浪費流量,WebSocket就是解決「服務器沒法主動推送數據」這一難點而發明的。html

  目前瀏覽器基本都支持WebSocket,這種協議有着以下特色:前端

  • 創建在 TCP 協議之上,服務器端的實現比較容易。
  • 與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容易屏蔽,能經過各類 HTTP 代理服務器。
  • 數據格式比較輕量,性能開銷小,通訊高效。
  • 能夠發送文本,也能夠發送二進制數據。
  • 沒有同源限制,客戶端能夠與任意服務器通訊。
  • 協議標識符是ws(若是加密,則爲wss),服務器網址就是 URL。

websocket以ws開頭,一個標準的ws網址像這樣:java

ws://ip:port/path

  其中IP能夠被域名代替。下面我會給出一個websocket下的聊天室Demojquery

基於WebSocket的聊天室Demo

環境:JDK 1.8.0_211
開發工具:IDEA
項目管理工具:maven
前端頁面:bootstrapweb

代碼有詳細的註釋,應該比較好懂的。json

前端頁面:
bootstrap


chat.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>基於WebSocket的在線聊天室</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <style>
        .chatFont {
            /* 消息字體大小 */
            font-size: 16px;
        }

        .input {
            /* 輸入框絕對定位 */
            position: fixed;
            top: 75%;
            right: 3%;
            width: 70%;
            font-size: 16px;
        }
    </style>
</head>
<body>
<!--側邊欄-->
<div class="column col-xs-3" id="sidebar">
    <h3 class="text-center">在線人員</h3>
    <ul class="nav" id="onlineUser">
    <!-- 在線用戶顯示在這裏 -->
    </ul>
</div>

<div class="col-xs-9">
    <div class="panel panel-info">
        <div class="panel-heading"><h3>聊天室</h3></div>
        <div class="panel-body" id="show" style="height: 380px;">
        <!-- 消息顯示在這裏 -->
            <div class="chatFont">聊天記錄<br></div>
        </div>
    </div>
    <div class="input">
        <label for="msg">請輸入</label>
        <textarea class="form-control" rows="5" id="msg"></textarea>
    </div>
</div>

</body>
<script src="js/jquery-3.4.1.js"></script>
<script src="js/bootstrap.min.js"></script>
<script>
    //這個num是用來限制消息條數,否則消息會撐破面板(前端技術太渣只能用這種笨辦法)
    var num = 1;
    // 建立WebSocket對象
    var socket = new WebSocket("ws://localhost:8080/chat");
    //發送消息
    var sendMsg = function () {
        var input = $('#msg');
        if (input.val()) {
            socket.send(input.val());
        } else {
            alert('消息不能爲空')
        }
        //清空輸入框
        input.val("");
    };

    //輸入框鍵盤事件檢測
    var keyDown = function (e) {
        if (!e.ctrlKey && e.keyCode == 13) { // enter 鍵
            sendMsg();
        } else if (e.keyCode == 13 && e.ctrlKey) {
            //實現換行,這個沒寫
            alert('ctrl enter');
        }
    };
    //綁定輸入框鍵盤事件
    $('#msg').keydown(keyDown);

    //websocket監聽事件,收到消息時觸發
    socket.onmessage = function (ev) {
        //console.log(ev);
        showMsg(ev.data);
    };
    //顯示消息
    var showMsg = function (data) {
        //後端使用Json傳輸
        data = JSON.parse(data);
        //若是有消息則顯示在消息框
        if (data.msg) {
            var text = '<div class="chatFont">' + '[' + data.userName + ']:' + data.msg + '<br></div>';
            $('#show').append(text);
        }
        //若是是新用戶則添加在線成員,針對新用戶
        if (data.onlineUser) {
            console.log(data.onlineUser);
            var online = data.onlineUser;
            $.each(online, function (index, element) {
                $('#onlineUser').append('<li class="active" id="' + element + '"><a href="#">' + element + '</a></li>');
            })
        }
        //若是有新用戶進來則添加在線成員,針對已在線成員
        if (data.addUser) {
            $('#onlineUser').append('<li class="active" id="' + data.userName + '"><a href="#">' + data.userName + '</a></li>');
        }
        //若是有用戶離開則移除在線成員
        if (data.removeUser) {
            $('#' + data.userName + '').remove();
        }
        //消息大於15條時刪除最上邊的,防止撐破消息欄
        if ($('#show').children('div').length>15){
            $('#show').children('div:first').remove();
        }
    };
    //鏈接斷開時觸發,清空綁定
    socket.onclose = function (ev) {
        $('#msg').unbind();
        $('#show').text('鏈接已斷開');
    };
    //鏈接時觸發,建立用戶名
    socket.onopen = function (ev) {
        var name = prompt('請輸入暱稱:');
        if (name) {
            socket.send(name);
        } else {
            socket.send('遊客:' + Math.random() * 100000000000000000);
        }
    }
</script>
</html>

前端技術有點水,只能高出這麼簡陋的頁面了。後端

即便這樣也要搞個漂亮的首頁😆:
api


index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>首頁</title>

    <link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
    <div class="container text-center" >
        <h2 class="text-info" style="font-family:宋體;font-weight:bold;font-size:49px">基於WebSocket的在線聊天室</h2>
        <br>
        <div class="text-muted">與世界分享你的逼格</div>
        <br>
        <br>
    </div>
    <div class="container text-center">
        <button class="btn btn-primary" id="enter">進入</button>
    </div>
</div>
</body>
<script src="js/jquery-3.4.1.js"></script>
<script src="js/bootstrap.min.js"></script>
<script>
    $('#enter').click(function () {
        location.href = 'chat.html';
    });
</script>
</html>

接下來是後端,首先添加依賴:


pom.xml

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.59</version>
</dependency>

而後是服務端主體:


後端實現

面向對象,對消息進行封裝:

public class Message {
    private String userName;//用戶
    private String msg;//消息主體
    private boolean addUser;//用戶加入
    private boolean removeUser;//用戶離開

    //清空狀態
    public void clearMsg(){
        msg = null;
        addUser = false;
        removeUser = false;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public boolean isAddUser() {
        return addUser;
    }

    public void setAddUser(boolean addUser) {
        this.addUser = addUser;
    }

    public boolean isRemoveUser() {
        return removeUser;
    }

    public void setRemoveUser(boolean removeUser) {
        this.removeUser = removeUser;
    }

    @Override
    public String toString() {
        return "Message{" +
                "userName='" + userName + '\'' +
                ", msg='" + msg + '\'' +
                ", addUser=" + addUser +
                ", removeUser=" + removeUser +
                '}';
    }
}

服務主體:

import com.alibaba.fastjson.JSON;
import com.bilibili.pojo.Message;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//訪問路徑,相似http中的@WebServlet()註解
@ServerEndpoint("/chat")
public class Chat {
    //每個鏈接都會建立一個Chat對象,因此建立一個靜態Map來保存已鏈接用戶
    private static final Map<String, Chat> clientMap = new HashMap<>();

    private boolean firstFlag = true;//是否第一次訪問
    private String name;
    private Session session;//這裏的Session和servlet中的Session不是同一個種
    private Message message = new Message();

    /**
     * 客戶端鏈接時執行的方法
     * @param session 客戶端session
     * @throws IOException
     */
    @OnOpen
    public void start(Session session) throws IOException {
        this.session = session;
        System.out.println("鏈接");
    }

    /**
     * 客戶端斷開
     */
    @OnClose
    public void end() {
        //從鏈接對象中移除
        clientMap.remove(name, this);
        //向全部人發送一個有人離開的消息
        message.clearMsg();
        message.setUserName(name);
        message.setMsg("離開了聊天室!");
        message.setRemoveUser(true);
        // 發送消息
        sendMsg(JSON.toJSONString(message));
        System.out.println("斷開");
    }

    /**
     * 服務端收到消息
     * @param msg
     */
    @OnMessage
    public void receive(String msg) {
        if (firstFlag) {
            //把第一次的消息做爲用戶名
            name = msg;
            //構造發送給全部人的消息
            message.setMsg("加入了聊天室!");
            message.setUserName(name);
            message.setAddUser(true);
            //獲取當前在線用戶
            List<String> onlineUser = new ArrayList<>(clientMap.keySet());
            clientMap.put(name, this);
            try {
                //直接構造Json,給新鏈接的用戶發送刷新在線用戶的消息
                session.getBasicRemote().sendText("{\"onlineUser\":"+JSON.toJSONString(onlineUser)+"}");
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 給全部用戶發送有人進入的消息
            sendMsg(JSON.toJSONString(message));
            firstFlag = false;
        } else {
            //不是第一次則直接發送消息
            message.clearMsg();
            message.setMsg(msg);
            sendMsg(JSON.toJSONString(message));
        }

    }

    // 當客戶端通訊出現錯誤時,激發該方法
    @OnError
    public void onError(Throwable t) throws Throwable {
        System.out.println("WebSocket服務端錯誤 " + t);
    }

    //發送消息的方法
    public void sendMsg(String msg) {
        // 遍歷服務器關聯的全部客戶端
        Chat client = null;
        for (String nickname : clientMap.keySet()) {
            try {
                client = clientMap.get(nickname);
                synchronized (client) {
                    // 發送消息
                    client.session.getBasicRemote().sendText(msg);
                }
            } catch (IOException e) {
                System.out.println("聊天錯誤,向客戶端 " + client + " 發送消息出現錯誤。");
                clientMap.remove(name, client);
                try {
                    client.session.close();
                } catch (IOException e1) {
                }
                Message newMessage = new Message();
                newMessage.setMsg("["+client.name+"]已經被斷開了鏈接。");
                sendMsg(JSON.toJSONString(newMessage));
            }
        }
    }
}

這樣就簡單實現了一個聊天室。

效果圖以下:

websocket聊天室

這麼寫能夠發送html標籤來達到發送圖片的目的🤣

相關文章
相關標籤/搜索