websocket实现多房间多用户聊天室

众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客端浏览器将信息呈现。但是对于实时性要求较高、海量并发的应用,比如金融证券的实时信息,web导航应用中地理位置获取,社交网络的实时消息推送等。

 

方案一:轮询,客户端用js代码每隔一定时间向服务器发送请求,这样会造成资源浪费(浪费带宽),在高并发的情况下还可能造成服务器奔溃。

方案二:基于Flash、AdobeFlash,通过socket实现数据信息交互,再利用Flash暴露的接口供js调用,但是Flash在移动互联网上的支持不好,IOS和Android都不支持Flash了。

方案三:WebSocket,2014年开始,各大应用服务器和浏览器厂商逐步统一,J2EE7也实现了WebSocket协议,无论客户端还是服务器都提供了对其的支持。


WebSocket介绍与原理

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和HTTP 最大不同是:

WebSocket 是一种双向通信协议,在基于http建立连接后,WebSocket 服务器和 browser都能主动向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信,实现长连接。

 

WebSocket 客户端连接报文

GET /webfin/websocket/ HTTP/1.1

Host: localhost

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==

Origin: http://localhost:8080

Sec-WebSocket-Version: 13

 

可以看到,客户端发起的 WebSocket 连接报文类似传统 HTTP 报文,”Upgrade:websocket”参数值表明这是 WebSocket 类型请求,“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。

WebSocket 服务端响应报文

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

 

“Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。 --------------------- 作者:zhengholien 来源:CSDN 原文:https://blog.csdn.net/zhengholien/article/details/76696509?utm_source=copy 版权声明:本文为博主原创文章,转载请附上博文链接!

项目实例

采用spring maven项目,开发工具eclise ,tomcat 版本8.5以上(不需要引用其它jar包)

主要jar包要求:(tomcat8.5 以上不需要引包)

代码实例

websocket核心配置类

package controller;

 
import java.util.Set;
 
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
 
/**
 * websockket 核心配置类,项目启动时会自动启动,类似与ContextListener.
 */
public class WebSocketConfig implements ServerApplicationConfig{
 
    
    /**
     * 注解方式
     * 扫描src下所有类@ServerEndPoint注解的类。
     */
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> arg0) {
        System.out.println("============="+arg0.size());
        //返回
        return arg0;
    }
 
    /**
     * 获取所有以接口方式配置的webSocket类。
     */
    public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> arg0) {
        return null;
    }
 
}

webSocket服务器

 

package controller;
 


import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
 
/**
 * writer: holien
 * Time: 2017-08-01 13:00
 * Intent: webSocket服务器
 */
@ServerEndpoint("/webSocket/chat/{roomName}/{username}")
public class WebSocketServer {
 
    // 使用map来收集session,key为roomName,value为同一个房间的用户集合
    // concurrentMap的key不存在时报错,不是返回null
    private static final Map<String, Set<Session>> rooms = new ConcurrentHashMap();
    private static final Map<String, String> userNameList = new ConcurrentHashMap();
    @OnOpen
    public void connect(@PathParam("roomName") String roomName,@PathParam("username") String username, Session session) throws Exception {
        // 将session按照房间名来存储,将各个房间的用户隔离
        if (!rooms.containsKey(roomName)) {
            // 创建房间不存在时,创建房间
            Set<Session> room = new HashSet<Session>();
            // 添加用户
            room.add(session);
            
            rooms.put(roomName, room);
        } else {
            // 房间已存在,直接添加用户到相应的房间
            rooms.get(roomName).add(session);
        }
        System.err.println("username"+username);
        System.out.println("a client has connected!");
    }
 
    @OnClose
    public void disConnect(@PathParam("roomName") String roomName,@PathParam("userName") String userName, Session session) {
        rooms.get(roomName).remove(session);
        System.out.println("a client has disconnected!");
    }
 
    @OnMessage
    public void receiveMsg(@PathParam("roomName") String roomName,@PathParam("username") String username,
                           String msg, Session session) throws Exception {
        // 此处应该有html过滤
        msg = username + ":" + msg;
        System.out.println(msg);
        // 接收到信息后进行广播
        broadcast(roomName, msg);
    }
 
    // 按照房间名进行广播
    public static void broadcast(String roomName, String msg) throws Exception {
        for (Session session : rooms.get(roomName)) {
                session.getBasicRemote().sendText(msg);
        }
    }
 
}

页面显示

<!DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网络聊天室</title>
</head>
<style type="text/css">
    .msg_board {
        width: 322px;
        height: 100px;
        border: solid 1px darkcyan;
        padding: 5px;
        overflow-y: scroll;
        // 文字长度大于div宽度时换行显示
        word-break: break-all;
    }
    /*set srcoll start*/
    ::-webkit-scrollbar
    {
        width: 10px;
        height: 10px;
        background-color: #D6F2FD;
    }
    ::-webkit-scrollbar-track
    {
        -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
        /*border-radius: 5px;*/
        background-color: #D6F2FD;
    }
    ::-webkit-scrollbar-thumb
    {
        height: 20px;
        /*border-radius: 10px;*/
        -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
        background-color: #89D7F7;
    }
    /*set srcoll end*/
</style>
<body>
<label>房间名</label>
<input id="input_roomName" size="10" maxlength="10">
<label>用户名</label>
<input id="username" size="10" maxlength="10">
<input type="button"  value="进入聊天室" onclick="initWebSocket()" />
<input type="button" value="退出聊天室" onclick="closeWs()" /><br>
<div class="msg_board"></div>
<input id="input_msg" size="43" maxlength="40">
<input type="button" value="发送" onclick="send_msg()" />
</body>
<script type="text/javascript">
    var webSocket;
 
    function send_msg() {
        if (webSocket != null) {
            var input_msg = document.getElementById("input_msg").value.trim();
            if (input_msg == "") {
                return;
            }
            webSocket.send(input_msg);
            // 清除input框里的信息
            document.getElementById("input_msg").value = "";
        } else {
            alert("您已掉线,请重新进入聊天室...");
        }
    };
 
    function closeWs() {
        webSocket.close();
    };
 
    function initWebSocket() {
        var roomName = document.getElementById("input_roomName").value;
        // 房间名不能为空
        if (roomName == null || roomName == "") {
            alert("请输入房间名");
            return;
        }
        var username = document.getElementById("username").value.trim();
        if (username == "" || username==null) {
            alert("用户名不能为空")
            return;
        }
        if ("WebSocket" in window) {
//            alert("您的浏览器支持 WebSocket!");
            if (webSocket == null) {
                var url = "ws://localhost:8080/webSocketDemo/webSocket/chat/" + roomName+"/"+username;
                // 打开一个 web socket
                webSocket = new WebSocket(url);
            } else {
                alert("您已进入聊天室...");
            }
 
            webSocket.onopen = function () {
                alert("已进入聊天室,畅聊吧...");
            };
 
            webSocket.onmessage = function (evt) {
                var msg_board = document.getElementsByClassName("msg_board")[0];
                var received_msg = evt.data;
                var old_msg = msg_board.innerHTML;
                msg_board.innerHTML = old_msg + received_msg + "<br>";
                // 让滚动块往下移动
                msg_board.scrollTop = msg_board.scrollTop + 40;
            };
 
            webSocket.onclose = function () {
                // 关闭 websocket,清空信息板
                alert("连接已关闭...");
                webSocket = null;
                document.getElementsByClassName("msg_board")[0].innerHTML = "";
            };
        }
        else {
            // 浏览器不支持 WebSocket
            alert("您的浏览器不支持 WebSocket!");
        }
    }
</script>
</html>

只需要这三个文件实现多房间多用户聊天,

 

posted @ 2018-10-10 10:43  冰极无双  阅读(8020)  评论(1编辑  收藏  举报