1. 程式人生 > >【WebSocket】---多人聊天系統

【WebSocket】---多人聊天系統

tom after channel 加載 cal parse ssi 獲得 路徑

多人聊天系統

功能說明:多人聊天系統,主要功能點:

1、當你登陸成功後,可以看到所有在線用戶(實際開發可以通過redis實現,我這邊僅僅用map集合)

2、實現群聊功能,我發送消息,大家都可以看到。

先看案例效果:

技術分享圖片

這裏面有關在線人數有個bug,就是在線用戶會被覆蓋,lisi登陸的話,zhangsan在線信息就丟來,xiaoxiao登陸,lisi就丟來,這主要原因是因為我放的是普通集合,所以在線用戶數據是無法共享

所以只能顯示最後顯示的用戶,如果放到redis就不會有這個問題。

一、案例說明

1、UserChatController

@Controller
public class UserChatController { @Autowired private WebSocketService ws; /** * 1、登陸時,模擬數據庫的用戶信息 */ //模擬數據庫用戶的數據 public static Map<String, String> userMap = new HashMap<String, String>(); static{ userMap.put("zhangsan", "123"); userMap.put(
"lisi", "456"); userMap.put("wangwu", "789"); userMap.put("zhaoliu", "000"); userMap.put("xiaoxiao", "666"); } /** *2、 模擬用戶在線進行頁面跳轉的時候,判斷是否在線 * (這個實際開發中肯定存在redis或者session中,這樣數據才能共享) * 這裏只是簡單的做個模擬,所以暫且用普通map吧 */ public static Map<String, User> onlineUser = new
HashMap<>(); static{ //key值一般是每個用戶的sessionID(這裏表示admin用戶一開始就在線) onlineUser.put("123",new User("admin","888")); } /** *3、 功能描述:用戶登錄接口 */ @RequestMapping(value="login", method=RequestMethod.POST) public String userLogin( @RequestParam(value="username", required=true)String username, @RequestParam(value="pwd",required=true) String pwd, HttpSession session) { //判斷是否正確 String password = userMap.get(username); if (pwd.equals(password)) { User user = new User(username, pwd); String sessionId = session.getId(); //用戶登陸成功就把該用戶放到在線用戶中... onlineUser.put(sessionId, user); //跳到群聊頁面 return "redirect:/group/chat.html"; } else { return "redirect:/group/error.html"; } } /** *4、 功能描述:用於定時給客戶端推送在線用戶 */ @Scheduled(fixedRate = 2000) public void onlineUser() { ws.sendOnlineUser(onlineUser); } /** *5、 功能描述 群聊天接口 * message 消息體 * headerAccessor 消息頭訪問器,通過這個獲取sessionId */ @MessageMapping("/group/chat") public void topicChat(InMessage message, SimpMessageHeaderAccessor headerAccessor){ //這個sessionId是在HttpHandShakeIntecepter攔截器中放入的 String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString(); //通過sessionID獲得在線用戶信息 User user = onlineUser.get(sessionId); message.setFrom(user.getUsername()); ws.sendTopicChat(message); } }

2、握手請求的攔截器

/**
 * WebSocket握手請求的攔截器. 檢查握手請求和響應, 對WebSocketHandler傳遞屬性
 * 可以通過這個類的方法獲取resuest,和response
 */
public class HttpHandShakeIntecepter implements HandshakeInterceptor{


    //在握手之前執行該方法, 繼續握手返回true, 中斷握手返回false. 通過attributes參數設置WebSocketSession的屬性
    //這個項目只在WebSocketSession這裏存入sessionID
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {

        System.out.println("【握手攔截器】beforeHandshake");

        if(request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
            HttpSession session =  servletRequest.getServletRequest().getSession();
            String sessionId = session.getId();
            System.out.println("【握手攔截器】beforeHandshake sessionId="+sessionId);
            //這裏將sessionId放入SessionAttributes中,
            attributes.put("sessionId", sessionId);
        }
        
        return true;
    }
 
    //在握手之後執行該方法. 無論是否握手成功都指明了響應狀態碼和相應頭(這個項目沒有用到該方法)
    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception exception) {
        System.out.println("【握手攔截器】afterHandshake");
        
        if(request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
            HttpSession session =  servletRequest.getServletRequest().getSession();
            String sessionId = session.getId();
            System.out.println("【握手攔截器】afterHandshake sessionId="+sessionId);
        }
    }
}

3、頻道攔截器

/** 
 * 功能描述:頻道攔截器 ,類似管道,可以獲取消息的一些meta數據
 */
public class SocketChannelIntecepter extends ChannelInterceptorAdapter{

    /**
     * 在完成發送之後進行調用,不管是否有異常發生,一般用於資源清理
     */
    @Override
    public void afterSendCompletion(Message<?> message, MessageChannel channel,
            boolean sent, Exception ex) {
        System.out.println("SocketChannelIntecepter->afterSendCompletion");
        super.afterSendCompletion(message, channel, sent, ex);
    }

    
    /**
     * 在消息被實際發送到頻道之前調用
     */
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        System.out.println("SocketChannelIntecepter->preSend");
        
        return super.preSend(message, channel);
    }

    /**
     * 發送消息調用後立即調用
     */
    @Override
    public void postSend(Message<?> message, MessageChannel channel,
            boolean sent) {
        System.out.println("SocketChannelIntecepter->postSend");
        
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);//消息頭訪問器
        
        if (headerAccessor.getCommand() == null ) return ;// 避免非stomp消息類型,例如心跳檢測
        
        String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
        System.out.println("SocketChannelIntecepter -> sessionId = "+sessionId);
        
        switch (headerAccessor.getCommand()) {
        case CONNECT:
            connect(sessionId);
            break;
        case DISCONNECT:
            disconnect(sessionId);
            break;
        case SUBSCRIBE:    
            break;
        
        case UNSUBSCRIBE:
            break;
        default:
            break;
        }
    }

    /**
     * 連接成功
     */
    private void connect(String sessionId){
        System.out.println("connect sessionId="+sessionId);
    }

    /**
     * 斷開連接
     */
    private void disconnect(String sessionId){
        System.out.println("disconnect sessionId="+sessionId);
        //用戶下線操作
        UserChatController.onlineUser.remove(sessionId);
    }
    
}

4、修改webSocket配置類

既然寫了兩個攔截器,那麽肯定需要在配置信息裏去配置它們。

@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {


    /**
     *配置基站
     */
    public void registerStompEndpoints(StompEndpointRegistry registry) {

        registry.addEndpoint("/endpoint-websocket").addInterceptors(new HttpHandShakeIntecepter()).setAllowedOrigins("*").withSockJS();
    }

    /**
     * 配置消息代理(中介)
     * enableSimpleBroker 服務端推送給客戶端的路徑前綴
     * setApplicationDestinationPrefixes  客戶端發送數據給服務器端的一個前綴
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        registry.enableSimpleBroker("/topic","/chat");
        registry.setApplicationDestinationPrefixes("/app");

    }

  
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors( new SocketChannelIntecepter());
    }

    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        registration.interceptors( new SocketChannelIntecepter());
    }

}

5、app.js

登陸頁面和群聊頁面就不細聊,貼上代碼就好。

index.html

技術分享圖片
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/group/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
</head>
<body>

<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline" method=‘post‘  action="/login">
                <div class="form-group">
                    <input type="text" name="username" class="form-control" placeholder="用戶名">
                    <input type="password" name="pwd" class="form-control" placeholder="密碼">
                    <input type="submit" class="default" value="登錄" />
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>
index.html

chat.html

技術分享圖片
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
    <title>devsq聊天室</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/group/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/group/app.js"></script>
</head>
<body>

<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">建立連接通道:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <input type="text" id="content" class="form-control" placeholder="請輸入...">
                </div>
                <button id="send" class="btn btn-default" type="submit">發送</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>實時在線用戶列表</th>
                </tr>
                </thead>
                <tbody id=‘online‘>
                </tbody>
            </table>
        </div>
        
         <div class="col-md-6">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>聊天記錄</th>
                </tr>
                </thead>
                <tbody id=‘record‘>
                </tbody>
            </table>
        </div>
        
        
    </div>
</div>
</body>
</html>
chat.html

app.js

var stompClient = null;

//一加載就會調用該方法 function connect() { var socket = new SockJS(‘/endpoint-websocket‘); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log(‘Connected: ‘ + frame); //訂閱群聊消息 stompClient.subscribe(‘/topic/chat‘, function (result) { showContent(JSON.parse(result.body)); }); //訂閱在線用戶消息 stompClient.subscribe(‘/topic/onlineuser‘, function (result) { showOnlieUser(JSON.parse(result.body)); }); }); } //斷開連接 function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } //發送聊天記錄 function sendContent() { stompClient.send("/app/group/chat", {}, JSON.stringify({‘content‘: $("#content").val()})); } //顯示聊天記錄 function showContent(body) { $("#record").append("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>"); } //顯示實時在線用戶 function showOnlieUser(body) { $("#online").html("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>"); } $(function () { connect();//自動上線 $("form").on(‘submit‘, function (e) { e.preventDefault(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendContent(); }); });

我只是偶爾安靜下來,對過去的種種思忖一番。那些曾經的舊時光裏即便有過天真愚鈍,也不值得譴責。畢竟,往後的日子,還很長。不斷鼓勵自己,

天一亮,又是嶄新的起點,又是未知的征程(上校1)

【WebSocket】---多人聊天系統