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>