【0-3】从零开始的web即时聊天应用的开发

web即时聊天系统的实现

第【2】页

即时通讯方案:webSocket长连接

什么是webSocket?我会结合一个小demo来帮助理解。

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

图片来自https://www.runoob.com/html/html5-websocket.html

可以看到相对于ajax轮询来说,由于websocket进行一次连接http请求后直接可以创建持久性的连接,并进行双向数据传输。只有到使用webSocketAPI 的close()断开连接才结束,避免了每一次http请求发送包含的头部数据等非有效数据造成消耗带宽资源的情况,减少带宽的消耗,降低消息的延迟。最重要的是给服务器减压,因为连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。其他人获取message更新也不用向服务器请求更新数据,服务器自动发送数据到建立了webSocket连接的用户客户端上。(当然这是需要根据需求设计后端程序进行处理的)

方案思路:用户A与用户B建立了webSocket连接,互发消息和推送都是服务器主动进行转发,资源消耗明显减少。

以下是demo

【前端】HTML与JavaScript

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    WebSocket长连接
    <br />
    <input id="text" type="text" />
    <button οnclick="webSocketUnit.send()">发送消息</button>
    <hr />
    <input type="hidden" name="" class="userinfo" value="<?=$username?>">
    <button οnclick="window.webSocketUnit.init()" id="open">开启WebSocket连接</button>
    <button οnclick="webSocketUnit.closeWebSocket()" id="close">关闭WebSocket连接</button>
    <button οnclick=""><a href="../index/quit.php" style="text-decoration:none;display: block;">退出登录</a></button>
    <hr />
    <div id="message"></div>
</body>
<script>
! function() {
    var user = document.getElementsByClassName('userinfo')[0].value;

    // document.getElementById("close").disabled = true;
    var webSocketUnit = {
        websocket: null,
        heartCheck: {
            timeout: 12000, //12秒发一次心跳
            timeoutObj: null,
            serverTimeoutObj: null,
            reset: function() {
                clearInterval(this.timeoutObj);
                clearTimeout(this.serverTimeoutObj);
                return this;
            },
            start: function() {
                var self = this;
                this.timeoutObj = setInterval(function() {
                    //这里发送一个心跳,后端收到后,返回一个心跳消息,
                    //onmessage拿到返回的心跳就说明连接正常
                    webSocketUnit.websocket.send('{"username":"client","msg":"ping","type":0}');
                    
                }, this.timeout)
            }
        },
        openWebSocket: function() {
            if ('WebSocket' in window) {
                webSocketUnit.websocket = new WebSocket("ws://127.0.0.1:7001/webSocketTest/websocket");
            } else {
                alert('当前浏览器 Not support websocket')
            }
            return webSocketUnit.websocket;
        },
        closeWebSocket: function() {
            webSocketUnit.websocket.send('{"username":"client","msg":"退出群聊","type":2}');
            webSocketUnit.websocket.close();
            document.getElementById("open").disabled = false;
            document.getElementById("close").disabled = true;
        },
        printMessage: function(innerHTML) {
            document.getElementById('message').innerHTML += innerHTML + '<br/>';
        },
        send() {
            var message = document.getElementById('text').value;
            webSocketUnit.websocket.send('{"username":"' + user + '","msg":"' + message + '","type":2}');
            // console.log("{username:'"+user+"',msg:"+message+"}")
        },
        init: function() {

            var websocket = this.openWebSocket() //|| new WebSocket("ws://127.0.0.1:7001/webSocketTest/websocket");

            webSocketUnit.heartCheck.start();
            //连接发生错误的回调方法
            websocket.onerror = function() {
                webSocketUnit.printMessage("WebSocket连接发生错误");
            };

            //连接成功建立的回调方法
            websocket.onopen = function() {
                webSocketUnit.printMessage("WebSocket连接成功");
                websocket.send('{"username":"' + user + '","msg":"加入群聊","type":1}');
            }

            //接收到消息的回调方法
            websocket.onmessage = function(event) {
                webSocketUnit.printMessage(event.data);
            }

            //连接关闭的回调方法
            websocket.onclose = function() {
                webSocketUnit.heartCheck.reset();
                webSocketUnit.printMessage("连接关闭");
            }

            //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
            window.onbeforeunload = function() {
                webSocketUnit.closeWebSocket();
            }

            webSocketUnit.websocket = websocket;
            window.websocket = websocket;

            document.getElementById("open").disabled = true;
            document.getElementById("close").disabled = false;

        }
    }
    var websocket = webSocketUnit.websocket;
    window.webSocketUnit = webSocketUnit;
}()
</script>

</html>

【后端】

package com.coden.services;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

import net.sf.json.JSONObject;



/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 *                 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint("/websocket")
public class webSocket {
	// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
	private static int onlineCount = 0;

	// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
	private static CopyOnWriteArraySet<webSocket> webSocketSet = new CopyOnWriteArraySet<webSocket>();

	// 与某个客户端的连接会话,需要通过它来给客户端发送数据
	private Session session;

	/**
	 * 连接建立成功调用的方法
	 * 
	 * @param session
	 *            可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
	 */
	@OnOpen
	public void onOpen(Session session) {
		this.session = session;
		
		webSocketSet.add(this); // 加入set中
		addOnlineCount(); // 在线数加1
		System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
	}

	/**
	 * 连接关闭调用的方法
	 */
	@OnClose
	public void onClose() {
		webSocketSet.remove(this); // 从set中删除
		subOnlineCount(); // 在线数减1
		System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
		
	}

	/**
	 * 收到客户端消息后调用的方法
	 * 
	 * @param message
	 *            客户端发送过来的消息
	 * @param session
	 *            可选的参数
	 */
	@OnMessage
	public void onMessage(String message, Session session) {
		
		// 群发消息
		for (webSocket item : webSocketSet) {
			try {
				if(JSONObject.fromObject(message).getInt("type") == 0){
                    //返回响应心跳消息

//					System.out.println("心跳包:" + message);
				}else {
					item.sendMessage(JSONObject.fromObject(message).getString("username")+" : "+JSONObject.fromObject(message).getString("msg"));
				}
//				
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
		}
	}

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

	/**
	 * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
	 * 
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException {
		this.session.getBasicRemote().sendText(message);
		// this.session.getAsyncRemote().sendText(message);
	}

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

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

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

上面这个例子就展示了webSocket的简单应用,使用起来像是游戏界面内的公屏系统,任何用户都可加进来这个公屏上发表消息

当然如果需要建立起聊天室的功能还需要对项目进行设计,具体的会在实现篇里说明。

使用上效果很好,结合javaee的webSocket包,可以很好的设计webSocket后台程序。

即时通讯方案的SSE可以查阅资料,我就不在这里记录了。

[2019-10-26]

发布了12 篇原创文章 · 获赞 1 · 访问量 3728

猜你喜欢

转载自blog.csdn.net/ET1131429439/article/details/102751675
0-3