WebSocket 实现聊天功能

java SSM项目中用WebSocket 实现聊天功能

前言

当我们开发的javaweb项目中需要实现聊天功能时,可以用HTML5的WebSocket协议来实现。在实现功能之前,先来看一下WebSocket的基本知识,加深对它的认识后面才能更好的理解。

(一) WebSocket

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

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。(相比于其他推送技术,WebSocket 协议能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。)

在这里插入图片描述
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,附加信息如图所示(看看就行):
在这里插入图片描述
(二) 连接过程

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当获取 Web Socket 的连接后,就可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

连接语法:var Socket = new WebSocket(url, [protocol] );
url是必须的,指定连接的url。 protocol是可选的,代表可接受的子协议。

(三) WebSocket的属性、事件、方法

1、WebSocket的属性

属性描述
Socket.readyState只读属性 readyState 表示连接状态,可以是以下值:
0 - 表示连接尚未建立。
1 - 表示连接已建立,可以进行通信。
2 - 表示连接正在进行关闭。
3 - 表示连接已经关闭或者连接不能打开。

2、WebSocket的事件(这里的Socket.事件名的Socket是js中new WebSocket(url)创建的对象)

事件事件处理程序(写在js中)描述
openSocket.onopen连接建立时触发
messageSocket.onmessage客户端接收服务端数据时触发
errorSocket.onerror通信发生错误时触发
closeSocket.onclose连接关闭时触发

3、WebSocket的方法

方法描述
Socket.send()使用连接发送数据
Socket.close()关闭连接

正文

好了,看到这里,对WebSocket也有了一定的了解。接下来,开始用WebSocket实现聊天。

开发环境:Eclipse、java version “10.0.2”
开发语言:java、javascript、html
框架:ssm框架 maven本地仓库
开发目标:实现用户之间的及时通讯(不包含数据库)

开发步骤
1、首先引入WebSocket的库文件

在maven的pom文件中引入依赖(如果没用maven,则需要自己去网上下载这个jar文件):

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-websocket</artifactId>
		<version>${spring.version}</version>
	</dependency>

2、设计思想

用户通过账号密码登录,登录之后通过WebSocket的onopen事件自动连接(相当于进入聊天室)。后台通过Map来记录用户连接情况。
进入聊天室之后,选择聊天对象,在input标签中输入文字,然后点击发送按钮,js响应按钮点击事件,把有关的各种JSON信息转换成字符串通过WebSocket的send()方法发送到后台服务器。
服务器接收到用户发送来的信息,解析之后转发给另一个用户,另一个用户通过WebSocket的onmessage事件接收消息。
至此,聊天功能实现。

3、后台代码(本人第一次编写WebSocket代码,所以直接写在Controller层了)

package cn.sx.handler;
/*
	*作者:Ji Pengjie
	*日期:2021年5月3日
    *时间:下午4:10:05
**/
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.json.Json;
import javax.json.JsonObject;
import javax.security.auth.message.callback.PrivateKeyCallback.Request;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.socket.server.standard.SpringConfigurator;

import com.alibaba.fastjson.JSONObject;

//websocket连接URL地址和可被调用配置 @ServerEndpoint这个注解用来标记一个类是 WebSocket 的处理器。
@ServerEndpoint(value="/websocketDemo/{fromId}",configurator = SpringConfigurator.class)
public class WebsocketDemo {
    //日志记录      
    private Logger logger = LoggerFactory.getLogger(WebsocketDemo.class);
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
   
    //记录每个用户下多个终端的连接
    public static Map<String, Set<WebsocketDemo>> userSocket = new HashMap<>();
 
    //需要session来对用户发送数据, 获取连接特征fromId
    private Session session;
    private String fromId;
    private String toId;
   
    /**
     * @Title: onOpen
     * @Description: websocekt连接建立时的操作
     * @param @param fromId 用户id
     * @param @param session websocket连接的session属性
     * @param @throws IOException
     */
    @OnOpen
    public void onOpen(@PathParam("fromId") String fromId, Session session) throws IOException{
        this.session = session;
        this.fromId = fromId;
        onlineCount++;
        //根据该用户当前是否已经在别的终端登录进行添加操作
        if (userSocket.containsKey(this.fromId)) {
            logger.debug("当前用户id:{}已有其他终端登录",this.fromId);
            userSocket.get(this.fromId).add(this); //增加该用户set中的连接实例
        }else {
            logger.debug("当前用户id:{}第一个终端登录",this.fromId);
            Set<WebsocketDemo> addUserSet = new HashSet<>();
            addUserSet.add(this);
            userSocket.put(this.fromId, addUserSet);
        }
        logger.debug("用户{}登录的终端个数是为{}",fromId,userSocket.get(this.fromId).size());
        logger.debug("当前在线用户数为:{},所有终端个数为:{}",userSocket.size(),onlineCount);
        
    }
   
    /**
     * @Title: onClose
     * @Description: 连接关闭的操作
     */
    @OnClose
    public void onClose(){
        //移除当前用户终端登录的websocket信息,如果该用户的所有终端都下线了,则删除该用户的记录
        if (userSocket.get(this.fromId).size() == 0) {
            userSocket.remove(this.fromId);
        }else{
            userSocket.get(this.fromId).remove(this);
        }
        logger.debug("用户{}退出连接!",this.fromId);
        logger.debug("用户{}登录的终端个数是为{}",this.fromId,userSocket.get(this.fromId).size());
        userSocket.remove(this.fromId);
        logger.debug("当前在线用户数为:{},所有终端个数为:{}",userSocket.size(),--onlineCount);
    }
   
    /**
     * @Title: onMessage
     * @Description: 收到消息后的操作
     * @param @param message 收到的消息
     * @param @param session 该连接的session属性
     */
    @OnMessage
    public void onMessage(String message, Session session) {    
        logger.debug("收到来自用户id为:{}的消息:{}",this.fromId,message);
        JSONObject jsonObject = JSONObject.parseObject(message);//将前台转来的字符串转为json对象
        this.toId = jsonObject.getString("toId");
        
        if(userSocket.containsKey(toId)) {
        	sendMessageToUser(toId, message);
        }else {
        	
        	logger.debug("用户{}不在线!", toId);
        }
        if(session ==null)  logger.debug("session null");
    }
   
    /**
     * @Title: onError
     * @Description: 连接发生错误时候的操作
     * @param @param session 该连接的session
     * @param @param error 发生的错误
     */
    @OnError
    public void onError(Session session, Throwable error){
        logger.debug("用户id为:{}的连接发送错误",this.fromId);
        error.printStackTrace();
    }
   
  /**
   * @Title: sendMessageToUser
   * @Description: 发送消息给用户下的所有终端
   * @param @param fromId 用户id
   * @param @param message 发送的消息
   * @param @return 发送成功返回true,反则返回false
   */
    public Boolean sendMessageToUser(String toId,String message){
        if (userSocket.containsKey(toId)) {
            logger.debug(" 给用户id为:{}的所有终端发送消息:{}",toId,message);
            for (WebsocketDemo WS : userSocket.get(toId)) {
                logger.debug("sessionId为:{}",WS.session.getId());
                try {
                    WS.session.getBasicRemote().sendText(message);//将消息发送给对方 getBasicRemote().sendText(message) 同步发送
                } catch (IOException e) {
                    e.printStackTrace();
                    logger.debug(" 给用户id为:{}发送消息失败",toId);
                    return false;
                }
            }
            return true;
        }
        logger.debug("发送错误:当前连接不包含id为:{}的用户",toId);
        return false;
    }
}

解释:
声明websocket地址类似Spring MVC中的@controller注解类似,websocket使用@ServerEndpoint来进行声明接口:@ServerEndpoint(value="/websocket/{paraName}") ; 其中 “ { } ”用来表示带参数的连接,如果需要获取{}中的参数,在接收参数时使用注解:@PathParam(“paraName”)

4、前端连接代码

<div id="body1" >
		 <div class="page-header" id="tou">
            webSocket多终端聊天测试
        </div>
        <div class="well" id="msg"></div>
        <div class="col-lg">
            <div class="input-group">
            	<textarea class="form-control" placeholder="发送信息..." id="message"></textarea>
               <!--  <input type="text" class="form-control" placeholder="发送信息..." id="message"> -->
            </div>
            <div class="form-control1">
            	 <span class="float_r">
                    <button class="span_b" type="button" id="send">发送</button>
                    <button class="span_b" type="button" id="close">关闭</button>
                </span>
            </div>
        </div>
	</div>
	<script type="text/javascript">
	var tid;//全局变量
	var status = 0;//接收消息提醒标志,初始化为0,当收到消息并查看时设置为1
	//jquery代码当页面DOM树加载完毕之后触发
    $(function() {
            var websocket;//创建对象,用户连接WebSocket
        
            if('WebSocket' in window) {
                                    console.log("此浏览器支持websocket");
                websocket = new WebSocket("ws://127.0.0.1:3311/recruitment/websocketDemo/${session.username}");
            } else if('MozWebSocket' in window) {
                alert("此浏览器只支持MozWebSocket");
            } else {
                alert("此浏览器只支持SockJS");
            }
            //连接之后,WebSocket的事件
            websocket.onopen = function(evnt) {
                $("#msg_a").css("pointer-events","none");  //阻止点击产生效果 
                /* var str = location.href;返回当前显示的文档的完整 URL。
                if(str.split('?')[1]){
                	tid = str.split('?')[1].split('=')[1];//分割字符串,返回字符串数组
               		$("#tou").html("链接通道成功!开始与用户" + tid + "聊天!")
                } */
            };
            //接收消息的WebSocket事件
            websocket.onmessage = function(evnt) {
                var message = JSON.parse(evnt.data);//将数据解析成JSON形式
                var fromId = message.fromId;//对方用户的账号
                var toId = message.toId;//自己的账号
                var time = new Date(message.date);//对方发送信息时间
                var timeStr = time.toLocaleTimeString();
                if(status == 0){//这个可以不用实现也行
                    alert("收到来自用户 " + fromId + "的消息")
                    var msgg = document.getElementById("img_a");
                    msgg.src = "./imgs/msg2.png";
                    var msg1 = document.getElementById("msg_a");
                    msg1.href = "#?id=" + fromId;
                    $("#msg_a").css("pointer-events","auto");  //阻止a标签点击产生效果 
                }
                var text = message.text;//获取对方发送来的文本消息
                $("#msg").html($("#msg").html() + "<br/>" + timeStr + "   "+ fromId + "   " + "<br/>" +  text);//将信息显示在div页面
                scroll();
            };
            //发生错误时,WebSocket的处理事件
            websocket.onerror = function(evnt) {};
            //关闭连接时,WebSocket的处理事件
            websocket.onclose = function(evnt) {
                $("#tou").html("与服务器断开了链接!")
            };
            $(".imga").click(function (){ 
               status = 1;
        	});
        	//点击发送按钮,响应。
            $('#send').bind('click', function() {
                var str = location.href;返回当前显示的文档的完整 URL,里面包含着要发送给谁(对方)的id
                if(str.split('?')[1]){
                	tid = str.split('?')[1].split('=')[1];//分割字符串,返回字符串数组
                	//把自己的username写进去,然后调用send自定义js函数
                	send('${session.username}');
                	$("#tou").html("链接通道成功!开始与用户" + tid + "聊天!")
                }
                
            });
            
            function send(id) {
                if(websocket != null) {
                //json字符串
                    var msg = {
                            fromId: id,//自己的id
                            text: document.getElementById("message").value,//自己要发送的消息
                            toId: tid,//对方的id
                            date: Date.now()
                          };

                   /*  var message = document.getElementById('message').value; */
                   //调用WebSocket的send()方法,发送数据给服务器
                    websocket.send(JSON.stringify(msg));//把json数组转换成字符串传到后台
                    var div = document.getElementById('msg');
                    //把自己发送的信息显示在div上
                    div.innerHTML = div.innerHTML +  "<br/>" +new Date(msg.date).toLocaleTimeString() + "  " + msg.fromId + "  " + "<br/>" + msg.text;
                    document.getElementById('message').value = "";
                    scroll();
                } else {
                    alert('未与服务器链接.');
                }
            }
 
           $("#close").click(function (){ 
               status = 0;
               var msg = document.getElementById("img_a");
               msg.src = "./imgs/msg1.png";
               location.href = location.href.split('#')[0];//设置当前页面的url href地址
               $("#msg_a").css("pointer-events","none");  //阻止点击产生效果 
           });
           //点击退出按钮,退出当前账号,并关闭连接
           $("#exit").click(function (){ 
               close();
           });
           function close() {
               websocket.close();
        	}
    });
</script>

解释:
websocket = new WebSocket(“ws://127.0.0.1:3311/recruitment/websocketDemo/${session.username}”);
ws://本机地址:端口号/项目名/Websocket的处理器/{参数}
在这里插入图片描述

最后两个参数 要和WebSocket处理器注解@ServerEndpoint中的value值一样,参数直接传值就行,多个参数可以用/分割开

5、实现界面

首先登录
在这里插入图片描述
登录之后日志
在这里插入图片描述
在这里插入图片描述
开始发送消息
在这里插入图片描述

另一边收到消息通知
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考:
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications#closing_the_connection
https://www.jianshu.com/p/3398d0230e5f
https://www.runoob.com/html/html5-websocket.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值