Spring之WebSocket网页聊天以及服务器推送
Spring之WebSocket网页聊天以及服务器推送
转自:http://www.xdemo.org/spring-websocket-comet/
1. WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。
2. 轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。
3. 比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求
4. 在 WebSocket API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送
5. 在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
5.1. Header
互相沟通的Header是很小的-大概只有 2 Bytes
5.2. Server Push
浏览器支持情况
Chrome | 4+ |
Firefox | 4+ |
Internet Explorer | 10+ |
Opera | 10+ |
Safari | 5+ |
服务器支持
jetty | 7.0.1+ |
tomcat | 7.0.27+ |
Nginx | 1.3.13+ |
resin | 4+ |
API
var ws = new WebSocket(“ws: //echo.websocket.org”);
ws.onopen = function (){ws.send(“Test!”); };
//当有消息时,会自动调用此方法
ws.onmessage = function (evt){console.log(evt.data);ws.close();};
ws.onclose = function (evt){console.log(“WebSocketClosed!”);};
ws.onerror = function (evt){console.log(“WebSocketError!”);};
|
Demo简介
模拟了两个用户的对话,张三和李四,然后还有发送一个广播,即张三和李四都是可以接收到的,登录的时候分别选择张三和李四即可
Demo效果
Maven依赖
< dependency >
< groupId >com.fasterxml.jackson.core</ groupId >
< artifactId >jackson-annotations</ artifactId >
< version >2.3.0</ version >
</ dependency >
< dependency >
< groupId >com.fasterxml.jackson.core</ groupId >
< artifactId >jackson-core</ artifactId >
< version >2.3.1</ version >
</ dependency >
< dependency >
< groupId >com.fasterxml.jackson.core</ groupId >
< artifactId >jackson-databind</ artifactId >
< version >2.3.3</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-messaging</ artifactId >
< version >4.0.5.RELEASE</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-websocket</ artifactId >
< version >4.0.5.RELEASE</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-webmvc</ artifactId >
< version >4.0.5.RELEASE</ version >
</ dependency >
< dependency >
< groupId >com.google.code.gson</ groupId >
< artifactId >gson</ artifactId >
< version >2.3.1</ version >
</ dependency >
< dependency >
< groupId >javax.servlet</ groupId >
< artifactId >javax.servlet-api</ artifactId >
< version >3.1.0</ version >
< scope >provided</ scope >
</ dependency >
< dependency >
< groupId >junit</ groupId >
< artifactId >junit</ artifactId >
< version >3.8.1</ version >
< scope >test</ scope >
</ dependency >
|
Web.xml,spring-mvc.xml,User.java请查看附件
WebSocket相关的类
WebSocketConfig,配置WebSocket的处理器(MyWebSocketHandler)和拦截器(HandShake)
package org.xdemo.example.websocket.websocket;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebScoket配置处理器
* @author Goofy
* @Date 2015年6月11日 下午1:15:09
*/
@Component
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Resource
MyWebSocketHandler handler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/ws" ).addInterceptors( new HandShake());
registry.addHandler(handler, "/ws/sockjs" ).addInterceptors( new HandShake()).withSockJS();
}
}
|
MyWebSocketHandler
package org.xdemo.example.websocket.websocket;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.xdemo.example.websocket.entity.Message;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Socket处理器
*
* @author Goofy
* @Date 2015年6月11日 下午1:19:50
*/
@Component
public class MyWebSocketHandler implements WebSocketHandler {
public static final Map<Long, WebSocketSession> userSocketSessionMap;
static {
userSocketSessionMap = new HashMap<Long, WebSocketSession>();
}
/**
* 建立连接后
*/
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
Long uid = (Long) session.getAttributes().get( "uid" );
if (userSocketSessionMap.get(uid) == null ) {
userSocketSessionMap.put(uid, session);
}
}
/**
* 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理
*/
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
if (message.getPayloadLength()== 0 ) return ;
Message msg= new Gson().fromJson(message.getPayload().toString(),Message. class );
msg.setDate( new Date());
sendMessageToUser(msg.getTo(), new TextMessage( new GsonBuilder().setDateFormat( "yyyy-MM-dd HH:mm:ss" ).create().toJson(msg)));
}
/**
* 消息传输错误处理
*/
public void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 移除Socket会话
while (it.hasNext()) {
Entry<Long, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println( "Socket会话已经移除:用户ID" + entry.getKey());
break ;
}
}
}
/**
* 关闭连接后
*/
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
System.out.println( "Websocket:" + session.getId() + "已经关闭" );
Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 移除Socket会话
while (it.hasNext()) {
Entry<Long, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println( "Socket会话已经移除:用户ID" + entry.getKey());
break ;
}
}
}
public boolean supportsPartialMessages() {
return false ;
}
/**
* 给所有在线用户发送消息
*
* @param message
* @throws IOException
*/
public void broadcast( final TextMessage message) throws IOException {
Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap
.entrySet().iterator();
// 多线程群发
while (it.hasNext()) {
final Entry<Long, WebSocketSession> entry = it.next();
if (entry.getValue().isOpen()) {
// entry.getValue().sendMessage(message);
new Thread( new Runnable() {
public void run() {
try {
if (entry.getValue().isOpen()) {
entry.getValue().sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
/**
* 给某个用户发送消息
*
* @param userName
* @param message
* @throws IOException
*/
public void sendMessageToUser(Long uid, TextMessage message)
throws IOException {
WebSocketSession session = userSocketSessionMap.get(uid);
if (session != null && session.isOpen()) {
session.sendMessage(message);
}
}
}
|
HandShake(每次建立连接都会进行握手)
package org.xdemo.example.websocket.websocket;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* Socket建立连接(握手)和断开
*
* @author Goofy
* @Date 2015年6月11日 下午2:23:09
*/
public class HandShake implements HandshakeInterceptor {
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println( "Websocket:用户[ID:" + ((ServletServerHttpRequest) request).getServletRequest().getSession( false ).getAttribute( "uid" ) + "]已经建立连接" );
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession( false );
// 标记用户
Long uid = (Long) session.getAttribute( "uid" );
if (uid!= null ){
attributes.put( "uid" , uid);
} else {
return false ;
}
}
return true ;
}
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}
|
一个Controller
package org.xdemo.example.websocket.controller;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.socket.TextMessage;
import org.xdemo.example.websocket.entity.Message;
import org.xdemo.example.websocket.entity.User;
import org.xdemo.example.websocket.websocket.MyWebSocketHandler;
import com.google.gson.GsonBuilder;
@Controller
@RequestMapping ( "/msg" )
public class MsgController {
@Resource
MyWebSocketHandler handler;
Map<Long, User> users = new HashMap<Long, User>();
//模拟一些数据
@ModelAttribute
public void setReqAndRes() {
User u1 = new User();
u1.setId(1L);
u1.setName( "张三" );
users.put(u1.getId(), u1);
User u2 = new User();
u2.setId(2L);
u2.setName( "李四" );
users.put(u2.getId(), u2);
}
//用户登录
@RequestMapping (value= "login" ,method=RequestMethod.POST)
public ModelAndView doLogin(User user,HttpServletRequest request){
request.getSession().setAttribute( "uid" , user.getId());
request.getSession().setAttribute( "name" , users.get(user.getId()).getName());
return new ModelAndView( "redirect:talk" );
}
//跳转到交谈聊天页面
@RequestMapping (value= "talk" ,method=RequestMethod.GET)
public ModelAndView talk(){
return new ModelAndView( "talk" );
}
//跳转到发布广播页面
@RequestMapping (value= "broadcast" ,method=RequestMethod.GET)
public ModelAndView broadcast(){
return new ModelAndView( "broadcast" );
}
//发布系统广播(群发)
@ResponseBody
@RequestMapping (value= "broadcast" ,method=RequestMethod.POST)
public void broadcast(String text) throws IOException{
Message msg= new Message();
msg.setDate( new Date());
msg.setFrom(-1L);
msg.setFromName( "系统广播" );
msg.setTo(0L);
msg.setText(text);
handler.broadcast( new TextMessage( new GsonBuilder().setDateFormat( "yyyy-MM-dd HH:mm:ss" ).create().toJson(msg)));
}
}
|
一个消息的封装的类
package org.xdemo.example.websocket.entity;
import java.util.Date;
/**
* 消息类
* @author Goofy
* @Date 2015年6月12日 下午7:32:39
*/
public class Message {
//发送者
public Long from;
//发送者名称
public String fromName;
//接收者
public Long to;
//发送的文本
public String text;
//发送日期
public Date date;
public Long getFrom() {
return from;
}
public void setFrom(Long from) {
this .from = from;
}
public Long getTo() {
return to;
}
public void setTo(Long to) {
this .to = to;
}
public String getText() {
return text;
}
public void setText(String text) {
this .text = text;
}
public String getFromName() {
return fromName;
}
public void setFromName(String fromName) {
this .fromName = fromName;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this .date = date;
}
}
|
聊天页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getServerName() + ":"
+ request.getServerPort() + path + "/";
String basePath2 = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
< html xmlns = "http://www.w3.org/1999/xhtml" >
< head >
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" />
< title ></ title >
< script type = "text/javascript" src="<%=basePath2%>resources/jquery.js"></ script >
< style >
textarea {
height: 300px;
width: 100%;
resize: none;
outline: none;
}
input[type=button] {
float: right;
margin: 5px;
width: 50px;
height: 35px;
border: none;
color: white;
font-weight: bold;
outline: none;
}
.clear {
background: red;
}
.send {
background: green;
}
.clear:active {
background: yellow;
}
.send:active {
background: yellow;
}
.msg {
width: 100%;
height: 25px;
outline: none;
}
#content {
border: 1px solid gray;
width: 100%;
height: 400px;
overflow-y: scroll;
}
.from {
background-color: green;
width: 80%;
border-radius: 10px;
height: 30px;
line-height: 30px;
margin: 5px;
float: left;
color: white;
padding: 5px;
font-size: 22px;
}
.to {
background-color: gray;
width: 80%;
border-radius: 10px;
height: 30px;
line-height: 30px;
margin: 5px;
float: right;
color: white;
padding: 5px;
font-size: 22px;
}
.name {
color: gray;
font-size: 12px;
}
.tmsg_text {
color: white;
background-color: rgb(47, 47, 47);
font-size: 18px;
border-radius: 5px;
padding: 2px;
}
.fmsg_text {
color: white;
background-color: rgb(66, 138, 140);
font-size: 18px;
border-radius: 5px;
padding: 2px;
}
.sfmsg_text {
color: white;
background-color: rgb(148, 16, 16);
font-size: 18px;
border-radius: 5px;
padding: 2px;
}
.tmsg {
clear: both;
float: right;
width: 80%;
text-align: right;
}
.fmsg {
clear: both;
float: left;
width: 80%;
}
</ style >
< script >
var path = '<%=basePath%>';
var uid=${uid eq null?-1:uid};
if(uid==-1){
location.href="<%=basePath2%>";
}
var from=uid;
var fromName='${name}';
var to=uid==1?2:1;
var websocket;
if ('WebSocket' in window) {
websocket = new WebSocket("ws://" + path + "/ws?uid="+uid);
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://" + path + "/ws"+uid);
} else {
websocket = new SockJS("http://" + path + "/ws/sockjs"+uid);
}
websocket.onopen = function(event) {
console.log("WebSocket:已连接");
console.log(event);
};
websocket.onmessage = function(event) {
var data=JSON.parse(event.data);
console.log("WebSocket:收到一条消息",data);
var textCss=data.from==-1?"sfmsg_text":"fmsg_text";
$("#content").append("< div >< label >"+data.fromName+" "+data.date+"</ label >< div class = '"+textCss+"' >"+data.text+"</ div ></ div >");
scrollToBottom();
};
websocket.onerror = function(event) {
console.log("WebSocket:发生错误 ");
console.log(event);
};
websocket.onclose = function(event) {
console.log("WebSocket:已关闭");
console.log(event);
}
function sendMsg(){
var v=$("#msg").val();
if(v==""){
return;
}else{
var data={};
data["from"]=from;
data["fromName"]=fromName;
data["to"]=to;
data["text"]=v;
websocket.send(JSON.stringify(data));
$("#content").append("< div >< label >我 "+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</ label >< div >"+data.text+"</ div ></ div >");
scrollToBottom();
$("#msg").val("");
}
}
function scrollToBottom(){
var div = document.getElementById('content');
div.scrollTop = div.scrollHeight;
}
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
function send(event){
var code;
if(window.event){
code = window.event.keyCode; // IE
}else{
code = e.which; // Firefox
}
if(code==13){
sendMsg();
}
}
function clearAll(){
$("#content").empty();
}
</ script >
</ head >
< body >
欢迎:${sessionScope.name }
< div id = "content" ></ div >
< input type = "text" placeholder = "请输入要发送的信息" id = "msg" onkeydown = "send(event)" >
< input type = "button" value = "发送" onclick = "sendMsg()" >
< input type = "button" value = "清空" onclick = "clearAll()" >
</ body >
</ html >
|
发布广播的页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath= request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
< html xmlns = "http://www.w3.org/1999/xhtml" >
< head >
< meta http-equiv = "Content-Type" content = "text/html; charset=utf-8" />
< title ></ title >
< script type = "text/javascript" src="<%=basePath%>resources/jquery.js"></ script >
< script type = "text/javascript" >
var path='<%=basePath%>';
function broadcast(){
$.ajax({
url:path+'msg/broadcast',
type:"post",
data:{text:$("#msg").val()},
dataType:"json",
success:function(data){
alert("发送成功");
}
});
}
</ script >
</ head >
< body >
发送广播
< textarea style = "width:100%;height:300px;" id = "msg" ></ textarea >
< input type = "button" value = "发送" onclick = "broadcast()" >
</ body >
</ html >
|
Chrome的控制台网络信息
Type:websocket
Time:Pending
表示这是一个websocket请求,请求一直没有结束,可以通过此通道进行双向通信,即双工,实现了服务器推送的效果,也减少了网络流量。
Chrome控制台信息
Demo下载
百度网盘:http://pan.baidu.com/s/1dD0b15Z
转载于:https://www.cnblogs.com/1995hxt/p/5125615.html
Spring之WebSocket网页聊天以及服务器推送相关推荐
- 长连接/websocket/SSE等主流服务器推送技术比较
最近做的某个项目有个需求,需要实时提醒client端有线上订单消息.所以保持客户端和服务器端的信息同步是关键要素,对此我们了解了可实现的方式.本文将介绍web常用的几种方式,希望给需要服务器端推送消息 ...
- 基于Tomcat7、Java、WebSocket的服务器推送聊天室
2019独角兽企业重金招聘Python工程师标准>>> 基于Tomcat7.Java.WebSocket的服务器推送聊天室 转载于:https://my.oschina.net/u/ ...
- netty服务器定时发送消息,netty+websocket+quartz实现消息定时推送
netty+websocket+quartz实现消息定时推送&&IM聊天室 在讲功能实现之前,我们先来捋一下底层的原理,后面附上工程结构及代码 1.NIO NIO主要包含三大核心部分: ...
- ajax轮询模拟websocket,Ajax轮询和SSE服务器推送数据与websocket模式的区别性学习
我们试想一下我们做个实时聊天的窗口有几种方法? 在我们不刷新页面并且可以试试更新页面内容的方法 你这时候是不是想到了ajax没错确实可以 Ajax轮询 什么是轮询?顾名思义就是我轮着问你,规定一个时间 ...
- JS 服务器推送技术 WebSocket 入门指北
作者: 前端下午茶 公号 / SHERlocked93 最近在工作中遇到了需要服务器推送消息的场景,这里总结一下收集整理WebSocket相关资料的收获. 1. 概述 1.1 服务器推送 WebSo ...
- Web服务器推送信息SSE/WebSocket
介绍 没有简单,通用的方法来以可接受的性能在Web应用程序中实现服务器到客户端的异步通信. HTTP是客户端-服务器计算模型中的请求-响应协议.为了开始交换,客户端向服务器提交请求.为了完成交换,服务 ...
- 服务器推送技术之短轮询、长轮询、SSE和Websocket
服务器推送技术 服务器推送技术干嘛用?就是让用户在使用网络应用的时候,不需要一遍又一遍的去手动刷新就可以及时获得更新的信息.大家平时在上各种视频网站时,对视频节目进行欢乐的吐槽和评论,会看到各种弹幕, ...
- HTTP Websocket 服务器推送消息
文章目录 HTTP HTTP请求过程 1. 无状态 2. 基于TCP协议 心跳包 3. 长.短连接 4. 单向请求 传统服务器推送技术 短轮询 polling 同源限制 跨域资源共享 长轮询 long ...
- 服务器如何向c winform推送信息,C局域网聊天工具消息推送实现思路与源码.doc
C局域网聊天工具消息推送实现思路与源码 C#局域网聊天工具怎么实现? 网络通讯编程的基础便是协议,信息的发送常用的协议有面向连接的TCP协议,以及不面向连接的UDP协议TCP:Transmission ...
最新文章
- ubyntu 链接mysql_ubuntu mysql 的安装、配置、简单使用,navicat 连接
- android webview javascript不执行,WebView中的JavaScript为什么不执行?
- js地址栏获取参数的方法,解决中文乱码问题,能支持中文参数
- Android Studio “Project Structure”选项目录结构显示异常
- python docx 表格样式修改 Package not found at ‘*.docx‘; “no style with name ‘Table Grid‘“
- Windows10如何彻底卸载MySQL
- SecureCRT 安装与破解教程
- 2021-2027全球与中国Al2O3氧化铝陶瓷基板白板市场现状及未来发展趋势
- 冯.诺依曼计算机结构要点
- 微软360度——成功与成长
- linux系统进入图文界面,Linux启动界面切换:图形界面-字符界面
- 用文氏图来理解卷积神经网络如何决定提取哪些特征
- java反序加密_对java程序加密防止反编译
- word转html显示不完整,word页面怎么显不完全 word页面视图显示不全怎么办
- 你真的会用区块链赚钱吗?论区块链的商业思维
- 2019年工作总结——没有岁月可回首,珍惜当下不负流年
- 20210327Java网络编程
- 原创教程:下载和安装“图形化积木Python编程”海龟编辑器
- cmd命令,最新的装逼利器
- jeesite使用说明