Bootstrap

Websocket实现单聊

1.Websocket介绍

随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。
  我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)。其实后者本质上也是一种轮询,只不过有所改进。
  轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。
 。
  这种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。
  伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。
JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,下面的Demo代码也是需要部署在Tomcat7.0.47以上的版本才能运行。

聊天实现:
第一步:

2.1.maven导入包
<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-messaging</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-websocket</artifactId>
			<version>${spring.version}</version>
		</dependency>

第二步:

2.2.建包扫描
创建websocket包并扫描
<context:component-scan base-package="cn.itsource.websocket" />

第三步:

创建消息处理类

package cn.itsource.websocket;

import cn.itsource.Util.JsonUtil;
import cn.itsource.model.Chat;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

//消息处理类
public class MyHandler extends TextWebSocketHandler {

    // 在线用户列表
    public static final Map<String, WebSocketSession> userSocketSessionMap = new HashMap<String, WebSocketSession>();
    // 用户连接成功 就被调用
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {System.err.println("用户连接成功");
        String str = session.getUri().toString();
        String str1=str.substring(0, str.indexOf("="));
        String str2=str.substring(str1.length()+1, str.length());
        System.err.println("用户"+str2);//截取发送请求里面包含的id
        userSocketSessionMap.put(str2,session);//添加到map中  
    }

 @Override
    public void handleTextMessage(WebSocketSession session,TextMessage message) {
        try {    
                sendMessagesToone(message);   
        } catch (Exception e) {
            e.printStackTrace();
        }      
    }
 
    //用户退出后的处理,退出之后,要将用户信息从websocket的session中remove掉,这样用户就处于离线状态了,也不会占用系统资源
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)  {
        try {
            if(session.isOpen()){
                session.close();
            }
            userSocketSessionMap.remove(session.getId());
            System.out.println("退出系统");
        }catch (Exception e){
            System.out.println("用户非正常关闭");
        }
    }    
}

创建配置类:

package cn.itsource.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    //注册消息的处理类
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

        registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("*");
    }
    //处理类
    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

测试是否能链接:

  var url="ws://"+this.ip+"/myHandler?uid="+this.id;//携带登录者的id
   websocket=new WebSocket(url);
      websocket.onopen=function () {
          //alert("链接成功")
        console.log("链接成功")
    }

前台发送消息到后台:

因为我们要实现的是单独聊天,所以我们要区分是谁在跟你聊天,所以我们在发送消息时候将你选择的用户的id携带到后台以区分this.uid

//发送消息
chatxx:function () {
                var json={"id":this.uid,"name":this.username,"message":this.text,"tid":this.tid,"groupchat":this.Groupchat}
                var jsonstr=JSON.stringify(json)
				console.log("发送de消息"+jsonstr)
				this.data.push(json)
                //alert("单聊")
                this.websocket.send(jsonstr)
				this.text='';

            }

因为前台传到后台的值为json字符串所以我们需要反序列化一下,将字符串转换为类

首先创建一个和前台传来的消息一一对应的类:

package cn.itsource.model;

public class Chat {
    private Long id;
    private String name;
    private String message;
    private Long tid;
    private String groupchat;

    public String getGroupchat() {
        return groupchat;
    }

    public void setGroupchat(String groupchat) {
        this.groupchat = groupchat;
    }

    public Long getTid() {
        return tid;
    }

    public void setTid(Long tid) {
        this.tid = tid;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Chat{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", message='" + message + '\'' +
                ", tid=" + tid +
                ", groupchat='" + groupchat + '\'' +
                '}';
    }
}

然后写一个公共方法来实现转换:

package cn.itsource.Util;

import cn.itsource.model.Chat;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class JsonUtil {
    public static Chat jsontf(String jsonstr){
        ObjectMapper objectMapper = new ObjectMapper();
        Chat read=null;
        try {
            read= objectMapper.readValue(jsonstr, Chat.class);
            return read;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return read;
    }
}

在消息处理类中写一个接收消息的方法

          //单独聊天
    public void sendMessagesToone(TextMessage message){
            //转换为实体类
            Chat jsontf1 = JsonUtil.jsontf(message.getPayload());
            String id = jsontf1.getId().toString();//得到发送消息里面的用户id
            //将用户id放到map里面取出value
        WebSocketSession webSocketSession = userSocketSessionMap.get(id);
        try {
            System.err.println("消息的id"+id);
                //isOpen()在线就发送
            if(webSocketSession.isOpen()){
                    webSocketSession.sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

    }

然后在上面的handleTextMessage里面调用sendMessagesToone方法
前台在接收后台发放的消息:

 //接收消息
            this.websocket.onmessage=(e)=> {
                console.log(e.data)
                let rep = JSON.parse(e.data);//将后台信息转换为json
                this.cid=rep.id
                this.data.push(rep)//在集合里面追加内容

            }

            // 连接关闭
            this.websocket.onclose = function (event) {
                console.log('Info: connection closed.');
            }

在页面面上循环data里面的数据就可以了。

<ul v-show="schat" class="content" id="chatbox2" v-for="item in data"  >
					<!--<li class="other" v-if="'{{item.id}}'==='{{uid}}'" >-->
				<li class="other" v-if="item.id===id&&uid===item.tid">
					<img src="/static/images/head/15.jpg" title="张文超" >
					<span>{{item.message}}</span>
				</li>

				<li class="me" v-else-if="item.id!=id&&uid===item.id"><img src="/static/images/head/15.jpg" title="张文超" >
					<span>{{item.message}}</span>	
				</li>
			</ul>