龙空技术网

WebSocket实现实时推送(附源码demo)

小楼一夜非天 5974

前言:

现时我们对“netmvcwebsocket”大致比较着重,朋友们都想要知道一些“netmvcwebsocket”的相关知识。那么小编在网上汇集了一些对于“netmvcwebsocket””的相关文章,希望咱们能喜欢,大家快快来学习一下吧!

实现推送功能方案

在以前的消息推送机制中,用的都是 Ajax 轮询(polling),在特定的时间间隔由浏览器自动发出请求,将服务器的消息主动的拉回来,这种方式是非常消耗资源的,因为它本质还是http请求,而且显得非常笨拙。而WebSocket 在浏览器和服务器完成一个握手的动作,在建立连接之后,服务器可以主动传送数据给客户端,客户端也可以随时向服务器发送数据。

核心技术介绍:

WebSocket协议概述

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。

WebSocket是真正实现了全双工通信的服务器向客户端推的互联网技术。

它是一种在单个TCP连接上进行全双工通讯协议。Websocket通信协议与2011年倍IETF定为标准RFC 6455,Websocket API被W3C定为标准。

WebSocket协议的优越性

以前不管使用HTTP轮询或使用TCP长连接等方式制作在线聊天系统,都有天然缺陷,随着Html5的兴起,其中有一个新的协议WebSocket protocol,可实现浏览器与服务器全双工通信(full-duplex),它可以做到:浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。这个新的协议的特点正好适合这种在线即时通信。

WebSocket协议实现方式:

它是一种长链接,只能通过一次请求来初始化链接,然后所有的请求和响应都是通过这个TCP链接进行通讯,这意味着它是一种基于事件驱动,异步的消息机制

服务端-服务器的支持

新版本的应用服务器新增了支持的API,如Tomcat 7.0.47+等

整体框架介绍

服务端:Maven+spring mvc+Spring WebSocket+jQuery+Gson

客户端:html5的WebSocket的api

图解:

Maven环境的搭建(略)

maven的项目之前一定先配置好maven的环境!

引入pom.xml配置:

<project xmlns="" xmlns:xsi="" xsi:schemaLocation=" ">

<modelVersion>4.0.0</modelVersion>

<groupId>cn.itcast.projects</groupId>

<artifactId>chatroomdemo</artifactId>

<version>0.0.1-SNAPSHOT</version>

<packaging>war</packaging>

<name>chatroomdemo</name>

<description>聊天室的demo</description>

<!-- 自定义属性管理 -->

<properties>

<!-- 编译等所有操作使用utf-8编码 -->

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- 统一版本维护管理 -->

<spring.version>4.2.8.RELEASE</spring.version>

<servlet.version>3.1.0</servlet.version>

<jsp.version>2.0</jsp.version>

<gson.version>2.7</gson.version>

<junit.version>4.12</junit.version>

</properties>

<!-- 依赖管理 -->

<dependencies>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-websocket</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-messaging</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>javax.servlet-api</artifactId>

<version>${servlet.version}</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>${junit.version}</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>com.google.code.gson</groupId>

<artifactId>gson</artifactId>

<version>${gson.version}</version>

</dependency>

</dependencies>

<!-- 构建信息管理 -->

<build>

<finalName>chatroom</finalName>

<plugins>

<!-- 编译的jdk版本 -->

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<configuration>

<source>1.7</source>

<target>1.7</target>

</configuration>

</plugin>

<plugin>

<groupId>org.apache.tomcat.maven</groupId>

<artifactId>tomcat7-maven-plugin</artifactId>

<version>2.2</version>

<configuration>

<port>8080</port>

<path>/chatroom</path>

<uriEncoding>UTF-8</uriEncoding>

<finalName>chatroom</finalName>

<server>tomcat7</server>

</configuration>

</plugin>

</plugins>

</build>

</project>

项目API讲解客户端的API

客户端如何去连接服务端?

客户端需要主动握手,

需要使用html5的一些代码

// 创建一个Socket实例(需要浏览器支持)ws:WebSocket协议地址开头

var socket = new WebSocket('ws://localhost:8080');

//下面有几个回调函数,自动调用(什么时候调用?)

// 打开Socket

socket.onopen = function(event) {

//握手成功后,会自动调用该函数

}

// 监听消息:用来获取服务端的消息

socket.onmessage = function(event) {

console.log('Client received a message',event);

};

// 监听Socket的关闭

socket.onclose = function(event) {

console.log('Client notified socket has closed',event);

};

// 关闭Socket....

//socket.close()

};

服务端的API

spring WebSocket:jee:WebSocket的封装。

用:只需要知道搭建步骤即可。

@Component("webSocketConfig")

//配置开启WebSocket服务用来接收ws请求

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

//注入处理器

@Autowired

private ChatWebSocketHandler webSocketHandler;

@Autowired

private ChatHandshakeInterceptor chatHandshakeInterceptor;

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

//添加一个处理器还有定义处理器的处理路径

registry.addHandler(webSocketHandler, "/ws").addInterceptors(chatHandshakeInterceptor);

/*

* 在这里我们用到.withSockJS(),SockJS是spring用来处理浏览器对websocket的兼容性,

* 目前浏览器支持websocket还不是很好,特别是IE11以下.

* SockJS能根据浏览器能否支持websocket来提供三种方式用于websocket请求,

* 三种方式分别是 WebSocket, HTTP Streaming以及 HTTP Long Polling

*/

registry.addHandler(webSocketHandler, "/ws/sockjs").addInterceptors(chatHandshakeInterceptor).withSockJS();

}

}

/**

* websocket的链接建立是基于http握手协议,我们可以添加一个拦截器处理握手

@Component

public class ChatHandshakeInterceptor implements HandshakeInterceptor{

/**

* 握手之前,若返回false,则不建立链接

*/

@Override

public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,

Map<String, Object> attributes) throws Exception {

if (request instanceof ServletServerHttpRequest) {

ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;

HttpSession session = servletRequest.getServletRequest().getSession(false);

//如果用户已经登录,允许聊天

if(session.getAttribute("loginUser")!=null){

//获取登录的用户

User loginUser=(User)session.getAttribute("loginUser") ;

//将用户放入socket处理器的会话(WebSocketSession)中

attributes.put("loginUser", loginUser);

System.out.println("Websocket:用户[ID:" + (loginUser.getId() + ",Name:"+loginUser.getNickname()+"]要建立连接"));

}else{

//用户没有登录,拒绝聊天

//握手失败!

System.out.println("--------------握手已失败...");

return false;

}

}

System.out.println("--------------握手开始...");

return true;

}

/**

* 握手之后

*/

@Override

public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,

Exception exception) {

System.out.println("--------------握手成功啦...");

}

}

@Component("chatWebSocketHandler")

public class ChatWebSocketHandler implements WebSocketHandler {

//在线用户的SOCKETsession(存储了所有的通信通道)

public static final Map<String, WebSocketSession> USER_SOCKETSESSION_MAP;

//存储所有的在线用户

static {

USER_SOCKETSESSION_MAP = new HashMap<String, WebSocketSession>();

}

/**

* webscoket建立好链接之后的处理函数--连接建立后的准备工作

*/

@Override

public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {

//将当前的连接的用户会话放入MAP,key是用户编号

User loginUser=(User) webSocketSession.getAttributes().get("loginUser");

USER_SOCKETSESSION_MAP.put(loginUser.getId(), webSocketSession);

//群发消息告知大家

Message msg = new Message();

msg.setText("风骚的【"+loginUser.getNickname()+"】踩着轻盈的步伐来啦。。。大家欢迎!");

msg.setDate(new Date());

//获取所有在线的WebSocketSession对象集合

Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();

//将最新的所有的在线人列表放入消息对象的list集合中,用于页面显示

for (Entry<String, WebSocketSession> entry : entrySet) {

msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser"));

}

//将消息转换为json

TextMessage message = new TextMessage(GsonUtils.toJson(msg));

//群发消息

sendMessageToAll(message);

}

@Override

/**

* 客户端发送服务器的消息时的处理函数,在这里收到消息之后可以分发消息

*/

//处理消息:当一个新的WebSocket到达的时候,会被调用(在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理)

public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> message) throws Exception {

//如果消息没有任何内容,则直接返回

if(message.getPayloadLength()==0)return;

//反序列化服务端收到的json消息

Message msg = GsonUtils.fromJson(message.getPayload().toString(), Message.class);

msg.setDate(new Date());

//处理html的字符,转义:

String text = msg.getText();

//转换为HTML转义字符表示

String htmlEscapeText = HtmlUtils.htmlEscape(text);

msg.setText(htmlEscapeText);

System.out.println("消息(可存数据库作为历史记录):"+message.getPayload().toString());

//判断是群发还是单发

if(msg.getTo()==null||msg.getTo().equals("-1")){

//群发

sendMessageToAll(new TextMessage(GsonUtils.toJson(msg)));

}else{

//单发

sendMessageToUser(msg.getTo(), new TextMessage(GsonUtils.toJson(msg)));

}

}

@Override

/**

* 消息传输过程中出现的异常处理函数

* 处理传输错误:处理由底层WebSocket消息传输过程中发生的异常

*/

public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception {

// 记录日志,准备关闭连接

System.out.println("Websocket异常断开:" + webSocketSession.getId() + "已经关闭");

//一旦发生异常,强制用户下线,关闭session

if (webSocketSession.isOpen()) {

webSocketSession.close();

}

//群发消息告知大家

Message msg = new Message();

msg.setDate(new Date());

//获取异常的用户的会话中的用户编号

User loginUser=(User)webSocketSession.getAttributes().get("loginUser");

//获取所有的用户的会话

Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();

//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)

for (Entry<String, WebSocketSession> entry : entrySet) {

if(entry.getKey().equals(loginUser.getId())){

msg.setText("万众瞩目的【"+loginUser.getNickname()+"】已经退出。。。!");

//清除在线会话

USER_SOCKETSESSION_MAP.remove(entry.getKey());

//记录日志:

System.out.println("Socket会话已经移除:用户ID" + entry.getKey());

break;

}

}

//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)

for (Entry<String, WebSocketSession> entry : entrySet) {

msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser"));

}

TextMessage message = new TextMessage(GsonUtils.toJson(msg));

sendMessageToAll(message);

}

@Override

/**

* websocket链接关闭的回调

* 连接关闭后:一般是回收资源等

*/

public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {

// 记录日志,准备关闭连接

System.out.println("Websocket正常断开:" + webSocketSession.getId() + "已经关闭");

//群发消息告知大家

Message msg = new Message();

msg.setDate(new Date());

//获取异常的用户的会话中的用户编号

User loginUser=(User)webSocketSession.getAttributes().get("loginUser");

Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();

//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)

for (Entry<String, WebSocketSession> entry : entrySet) {

if(entry.getKey().equals(loginUser.getId())){

//群发消息告知大家

msg.setText("万众瞩目的【"+loginUser.getNickname()+"】已经有事先走了,大家继续聊...");

//清除在线会话

USER_SOCKETSESSION_MAP.remove(entry.getKey());

//记录日志:

System.out.println("Socket会话已经移除:用户ID" + entry.getKey());

break;

}

}

//并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。)

for (Entry<String, WebSocketSession> entry : entrySet) {

msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser"));

}

TextMessage message = new TextMessage(GsonUtils.toJson(msg));

sendMessageToAll(message);

}

@Override

/**

* 是否支持处理拆分消息,返回true返回拆分消息

*/

//是否支持部分消息:如果设置为true,那么一个大的或未知尺寸的消息将会被分割,并会收到多次消息(会通过多次调用方法handleMessage(WebSocketSession, WebSocketMessage). )

//如果分为多条消息,那么可以通过一个api:org.springframework.web.socket.WebSocketMessage.isLast() 是否是某条消息的最后一部分。

//默认一般为false,消息不分割

public boolean supportsPartialMessages() {

return false;

}

/**

*

* 说明:给某个人发信息

* @param id

* @param message

* @throws IOException

*

*/

private void sendMessageToUser(String id, TextMessage message) throws IOException{

//获取到要接收消息的用户的session

WebSocketSession webSocketSession = USER_SOCKETSESSION_MAP.get(id);

if (webSocketSession != null && webSocketSession.isOpen()) {

//发送消息

webSocketSession.sendMessage(message);

}

}

/**

*

* 说明:群发信息:给所有在线用户发送消息

* @autho

*

*/

private void sendMessageToAll(final TextMessage message){

//对用户发送的消息内容进行转义

//获取到所有在线用户的SocketSession对象

Set<Entry<String, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();

for (Entry<String, WebSocketSession> entry : entrySet) {

//某用户的WebSocketSession

final WebSocketSession webSocketSession = entry.getValue();

//判断连接是否仍然打开的

if(webSocketSession.isOpen()){

//开启多线程发送消息(效率高)

new Thread(new Runnable() {

public void run() {

try {

if (webSocketSession.isOpen()) {

webSocketSession.sendMessage(message);

}

} catch (IOException e) {

e.printStackTrace();

}

}

}).start();

}

}

}

}

源码:转发,关注,私信websocket即可

标签: #netmvcwebsocket