WebSock群聊配置 以及UDP TCP WebSocket网路协议通信


1: webSocket
    一般情况Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现到浏览器中
    WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
    浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
    当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage(mes?d?) 事件来接收服务器返回的数据
    
    Websocket 使用 ws 或 wss 的统一资源标志符,类似于 HTTPS,其中 wss 表示在 TLS 之上的 Websocket
    Websocket 使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,Websocket 协议使用 80 端口;运行在 TLS 之上时,默认使用 443 端口。
    相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输
    
    客户端(发请求,建立链接):啦啦啦,有没有新信息(Request)
    服务端:没有。。(Response)
    客户端(发请求,建立链接):啦啦啦,有没有新信息,没有的话就等有了才返回给我吧(Request)
    
    ajax轮询 需要服务器有很快的处理速度和资源。(速度)
    long poll 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)
    
    
2:TCP 协议


    面向连接、传输可靠(保证数据正确性)、有序(保证数据顺序)、传输大量数据(流模式)、速度慢、对系统资源的要求多,程序结构较复杂,

 第一次握手

      客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号 seq=x,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状

态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始。表示客户端想要和服务端建立连接。

  第二次握手

      TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此

时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端

是否准备好。

  第三次握手

      TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。

TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。

思考:为什么要三次握手呢,有人说两次握手就好了

举例:已失效的连接请求报文段。

   client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效

的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个

请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的

很多资源就没白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。

第一次握手  

    TCP发送一个FIN(结束),用来关闭客户到服务端的连接。

    客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),

此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

第二次握手

    服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。

    服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器

通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个

状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

 

客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

第三次握手

      服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。

      服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,

此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

第四次握手

     客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。

     客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时

TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

 

服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

 

 

3:UDP 协议    
(用户数据报协议,User Data Protocol):(类似发短信)
面向非连接 、传输不可靠(可能丢包)、无序、传输少量数据(数据报模式)、速度快,对系统资源的要求少,程序结构较简单 ,
UDP支持一对一,一对多,多对一和多对多的交互通信,
UDP的首部开销小,只有8个字节。


Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。

4:WebSocket与HTTP的关系
    1. 都是一样基于TCP的,都是可靠性传输协议。
    2. 都是应用层协议
    3. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
    4. WebSocket是需要浏览器和服务器握手进行建立连接的。而http是浏览器发起向服务器的连接,服务器预先并不知道这个
    
     
//创建连接    
websocket = new WebSocket(webSocketMessageUrl);
//发送数据
建立连接后触发Socket.onopen 可以使用sent使用连接发送数据
//接收数据
并通过 onmessage 事件来接收服务器返回的数据

解决HTTP缺陷
    非持久性 同步有延 消耗资源 无状态协议。 被动性

5: WebSocket 配置群聊的基本流程

第一步:导入websocket的依赖

第二步:websocket的配置

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

第三步:建立ServerEndpoint的java类,能够接受客户端发送过来的信息和发送给客户端信息

#webSocket群聊配置
webSocketMessage:
  path: wss://guandao.gsfccs.com/webSocketMessage

@ServerEndpoint("/webSocketMessage")
@Component
public class WebSocketMessage {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketMessage> webSocketSet = new CopyOnWriteArraySet<WebSocketMessage>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private static final List<Map<String, Object>> users;
    static {
        users = new ArrayList<>();
    }

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        Map<String, Object> maps = new HashMap<>();
        maps.put("session", session);
        maps.put("userId","");
        maps.put("username","");
        users.add(maps);//加入set中
        addOnlineCount();//在线数加1
        System.out.println("有新连接加入!当前在线人数为:" + getOnlineCount());
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        for (Map<String, Object> map : users){
            Session session = (Session) map.get("session");
            if(session == this.session){
                ScoketMessage sm = new ScoketMessage();
                sm.setType("close");
                sm.setFromUser(map.get("username").toString());
                sm.setMessage("用户"+map.get("username").toString()+"离开会议!");
                sendMessageAll(JsonUtils.objectToJson(sm));
                users.remove(map);
            }
        }
        subOnlineCount();//在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }


    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        ScoketMessage sm = JSON.parseObject(message,ScoketMessage.class);
        if(sm.getType().equals("ping")){
            System.out.println("WebSocketMessage连接正常");
        } else if(sm.getType().equals("join")){
            for (Map<String, Object> map : users){
                if(map.get("session") == session){
                    map.put("userId", sm.getUserId());
                    map.put("username", sm.getFromUser());
                    sendMessageAll(JsonUtils.objectToJson(sm));
                    System.out.println("用户:【" + sm.getFromUser() + "】连接成功");
                }
            }
        } else {
            sendMessageAll(JsonUtils.objectToJson(sm));
            System.out.println("来自WebSocket的消息:" + JsonUtils.objectToJson(sm));
        }
    }


    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }


    /**
     * 群发自定义消息
     */
    public void sendMessageAll(String message){
        for (Map<String, Object> map : users){
            Session session = (Session) map.get("session");
            if(session.isOpen()){
                try {
                    session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketMessage.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketMessage.onlineCount--;
    }
}

第四部 js 获取WebSocket 请求的代码


    var websocket = null;
    var lockReconnect = false;  //避免ws重复连接
    createWebSocket();

    function createWebSocket(){
        try{
            //判断当前浏览器是否支持WebSocket
            if('WebSocket' in window){
                websocket = new WebSocket(webSocketMessageUrl);
            } else if('MozWebSocket' in window){
                websocket = new MozWebSocket(webSocketMessageUrl);
            } else {
                alert('您的浏览器不支持WebSocket');
            }
            initEventHandle();
        }catch(e){
            reconnect();
            console.log("创建WebSocket异常:"+e);
        }
    }

    function initEventHandle(){
        //连接成功建立的回调方法
        websocket.onopen = function(event){
            var sendTime = getCurrentDate(4);
            var msg = {
                type: "join",
                userId: userId,
                fromUser: account,
                sendTime: sendTime,
                message:"欢迎"+account+"加入会议!"
            };
            websocket.send(JSON.stringify(msg));
            console.log("【WebSocket消息】:"+JSON.stringify(msg));
        };


        //连接发生错误的回调方法
        websocket.onerror = function(){
            console.log("【WebSocket消息】:连接错误");
            reconnect();
        };

        //接收到消息的回调方法
        websocket.onmessage = function(event){
            var data = JSON.parse(event.data);
            if(data.type=='join'){
                var html = "<div class='history-toolbar'>"+
                    "<button class='u-btn u-btn-none f-w-140'>"+data.message+"</button>"+
                    "</div>";
                $("#webSocketMsg").append(html);
            } else if(data.type=='message'){
                if(data.fromUser == account){
                    var html = "<div class='msg msg-right'>"+
                        "<div class='avatar'></div>"+
                        "<div class='msgcontent'>"+
                        "<div class='nick'>"+data.sendTime+" "+data.fromUser+"</div>"+
                        "<div class='value'>"+data.message+"</div>"+
                        "</div>"+
                        "</div>";
                    $("#webSocketMsg").append(html);
                } else {
                    var html = "<div class='msg msg-left'>"+
                        "<div class='avatar'></div>"+
                        "<div class='msgcontent'>"+
                        "<div class='nick'>"+data.fromUser+" "+data.sendTime+"</div>"+
                        "<div class='value'>"+data.message+"</div>"+
                        "</div>"+
                        "</div>";
                    $("#webSocketMsg").append(html);
                }
            } else if(data.type=='close'){
                var html = "<div class='history-toolbar'>"+
                    "<button class='u-btn u-btn-none f-w-140'>"+data.message+"</button>"+
                    "</div>";
                $("#webSocketMsg").append(html);
            }
            //收到消息后自动滚动到消息列表底部
            var ele = document.getElementById('chat-msg-list');
            ele.scrollTop = ele.scrollHeight;
        };

        //连接关闭的回调方法
        websocket.onclose = function(){
            console.log("【WebSocket消息】:连接断开");
            reconnect();
        };

        //每隔30秒发送心跳包
        setInterval(heartCheck,30000);

    }


    function reconnect() {
        if(lockReconnect) return;
        lockReconnect = true;
        setTimeout(function () {     //没连接上会一直重连,设置延迟避免请求过多
            createWebSocket();
            lockReconnect = false;
        }, 30000);
    }


    //心跳
    function heartCheck(){
        var sendTime = getCurrentDate(2);
        var msg = {
            type: "ping",
            sendTime: sendTime,
            message:"ping"
        };
        websocket.send(JSON.stringify(msg));
    }

    //发送消息
    function sendMsg(){
        var message = document.getElementById('msgValue').value;
        if(message == ''){
            layer.msg("请输入需要发送的内容");
            return false;
        }
        var sendTime = getCurrentDate(4);
        var value = replace_em(message);
        var msg = {
            type: "message",
            userId: userId,
            fromUser:account,
            sendTime: sendTime,
            message:value
        };
        websocket.send(JSON.stringify(msg));
        document.getElementById('msgValue').value='';
    }


    function replace_em(str){
        str = str.replace(/\</g,'&lt;');
        str = str.replace(/\>/g,'&gt;');
        str = str.replace(/\n/g,'<br/>');
        str = str.replace(/\[em_([0-9]*)\]/g,'<img src="/plugin/qqFace/arclist/$1.gif" border="0" />');
        return str;
    }


    //连接关闭的回调方法
    websocket.onclose = function(){

    };


    //获取时间
    function getCurrentDate(format) {
        var now = new Date();
        var year = now.getFullYear(); //得到年份
        var month = now.getMonth();//得到月份
        var date = now.getDate();//得到日期
        var day = now.getDay();//得到周几
        var hour = now.getHours();//得到小时
        var minu = now.getMinutes();//得到分钟
        var sec = now.getSeconds();//得到秒
        month = month + 1;
        if (month < 10) month = "0" + month;
        if (date < 10) date = "0" + date;
        if (hour < 10) hour = "0" + hour;
        if (minu < 10) minu = "0" + minu;
        if (sec < 10) sec = "0" + sec;
        var time = "";
        //精确到天
        if(format==1){
            time = year + "-" + month + "-" + date;
        }
        //精确到分
        else if(format==2){
            time = year + "-" + month + "-" + date+ " " + hour + ":" + minu + ":" + sec;
        }
        //去掉年
        else if(format==3){
            time = month + "-" + date+ " " + hour + ":" + minu + ":" + sec;
        }
        //去掉年月日
        else if(format==4){
            time = hour + ":" + minu + ":" + sec;
        }
        return time;
    }

webSocket连接过程

1: js中创建webSocket 对象获取我们配置文件中配置的wss 域名路径进行连接

websocket = new WebSocket(webSocketMessageUrl);

2:连接成功后通过 onopen()来检测加入房间的用户

3:通过socket.send方法来发送我们的数据信息

websocket.send(JSON.stringify(msg));

后台代码主要是配置 websocket

1: 在配置文件中配置webSocket wss访问路径

2:配置ServerEndpoint java类用来接收数据

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值