Spring+Netty+WebSocket实例
比较贴近生产,详见注释
一、pom.xml
具体太长,详见源码
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.2.Final</version>
</dependency>
二、目录结构
三、AfterSpringBegin
继承了AfterSpringBegin的子类在spring加载成功后,会自动启动
package com.netty.init; import java.util.Timer;
import java.util.TimerTask; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
/**
*
* spring加载后改方法的子类
* */
public abstract class AfterSpringBegin extends TimerTask implements ApplicationListener<ContextRefreshedEvent>{ public void onApplicationEvent(ContextRefreshedEvent event) {
// TODO Auto-generated method stub
if(event.getApplicationContext().getParent() ==null){ Timer timer = new Timer();
timer.schedule(this, 0);
}
} }
四、Constant
存放了websocket相关信道
package com.netty.constant; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 常量
* */
public class Constant {
//存放所有的ChannelHandlerContext
public static Map<String, ChannelHandlerContext> pushCtxMap = new ConcurrentHashMap<String, ChannelHandlerContext>() ; //存放某一类的channel
public static ChannelGroup aaChannelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); }
五、WebSocketServer
启动服务
package com.netty.server; import javax.annotation.PreDestroy; import org.springframework.beans.factory.annotation.Autowired; import com.netty.init.AfterSpringBegin; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* 启动服务
* */ public class WebSocketServer extends AfterSpringBegin{ //用于客户端连接请求
@Autowired
private EventLoopGroup bossGroup; //用于处理客户端I/O操作
@Autowired
private EventLoopGroup workerGroup; //服务器的辅助启动类
@Autowired
private ServerBootstrap serverBootstrap; //BS的I/O处理类
private ChannelHandler childChannelHandler; private ChannelFuture channelFuture; //服务端口
private int port; public WebSocketServer(){ System.out.println("初始化");
} public EventLoopGroup getBossGroup() {
return bossGroup;
} public void setBossGroup(EventLoopGroup bossGroup) {
this.bossGroup = bossGroup;
} public EventLoopGroup getWorkerGroup() {
return workerGroup;
} public void setWorkerGroup(EventLoopGroup workerGroup) {
this.workerGroup = workerGroup;
} public ServerBootstrap getServerBootstrap() {
return serverBootstrap;
} public void setServerBootstrap(ServerBootstrap serverBootstrap) {
this.serverBootstrap = serverBootstrap;
} public ChannelHandler getChildChannelHandler() {
return childChannelHandler;
} public void setChildChannelHandler(ChannelHandler childChannelHandler) {
this.childChannelHandler = childChannelHandler;
} public ChannelFuture getChannelFuture() {
return channelFuture;
} public void setChannelFuture(ChannelFuture channelFuture) {
this.channelFuture = channelFuture;
} public int getPort() {
return port;
} public void setPort(int port) {
this.port = port;
} @Override
public void run() {
// TODO Auto-generated method stub
try {
bulid(port);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} public void bulid(int port) throws Exception{ try { //(1)boss辅助客户端的tcp连接请求 worker负责与客户端之前的读写操作
//(2)配置客户端的channel类型
//(3)配置TCP参数,握手字符串长度设置
//(4)TCP_NODELAY是一种算法,为了充分利用带宽,尽可能发送大块数据,减少充斥的小块数据,true是关闭,可以保持高实时性,若开启,减少交互次数,但是时效性相对无法保证
//(5)开启心跳包活机制,就是客户端、服务端建立连接处于ESTABLISHED状态,超过2小时没有交流,机制会被启动
//(6)netty提供了2种接受缓存区分配器,FixedRecvByteBufAllocator是固定长度,但是拓展,AdaptiveRecvByteBufAllocator动态长度
//(7)绑定I/O事件的处理类,WebSocketChildChannelHandler中定义
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(592048))
.childHandler(childChannelHandler); System.out.println("成功");
channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
// TODO: handle exception
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully(); } } //执行之后关闭
@PreDestroy
public void close(){
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully(); }
}
六、WebSocketChildChannelHandler
五里面的private ChannelHandler childChannelHandler; 注入的就是这个类,注入配置在后面的xml中,用处在五代码里注解了
package com.netty.server; import javax.annotation.Resource; import org.springframework.stereotype.Component; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler; @Component
public class WebSocketChildChannelHandler extends ChannelInitializer<SocketChannel>{ @Resource(name = "webSocketServerHandler")
private ChannelHandler webSocketServerHandler; @Override
protected void initChannel(SocketChannel ch) throws Exception {
// TODO Auto-generated method stub
ch.pipeline().addLast("http-codec", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
ch.pipeline().addLast("handler",webSocketServerHandler);
} }
七、WebSocketServerHandler
websocket具体的业务处理,六中的private ChannelHandler webSocketServerHandler;,注入的就是这个类
package com.netty.server; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject;
import com.netty.constant.Constant;
import com.netty.manage.ManageMessage; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil; /**
* websocket 具体业务处理方法
*
* */ @Component
@Sharable
public class WebSocketServerHandler extends BaseWebSocketServerHandler{ private WebSocketServerHandshaker handshaker; /**
* 当客户端连接成功,返回个成功信息
* */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
push(ctx, "连接成功");
} /**
* 当客户端断开连接
* */
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
for(String key:Constant.pushCtxMap.keySet()){ if(ctx.equals(Constant.pushCtxMap.get(key))){
//从连接池内剔除
System.out.println(Constant.pushCtxMap.size());
System.out.println("剔除"+key);
Constant.pushCtxMap.remove(key);
System.out.println(Constant.pushCtxMap.size());
} }
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
ctx.flush();
} @Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
// TODO Auto-generated method stub //http://xxxx
if(msg instanceof FullHttpRequest){ handleHttpRequest(ctx,(FullHttpRequest)msg);
}else if(msg instanceof WebSocketFrame){
//ws://xxxx
handlerWebSocketFrame(ctx,(WebSocketFrame)msg);
} } public void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception{ //关闭请求
if(frame instanceof CloseWebSocketFrame){ handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain()); return;
}
//ping请求
if(frame instanceof PingWebSocketFrame){ ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); return;
}
//只支持文本格式,不支持二进制消息
if(!(frame instanceof TextWebSocketFrame)){ throw new Exception("仅支持文本格式");
} //客服端发送过来的消息 String request = ((TextWebSocketFrame) frame).text();
System.out.println("服务端收到:" + request); JSONObject jsonObject = null; try
{
jsonObject = JSONObject.parseObject(request);
System.out.println(jsonObject.toJSONString());
}
catch (Exception e)
{
}
if (jsonObject == null){ return;
} String id = (String) jsonObject.get("id");
String type = (String) jsonObject.get("type"); //根据id判断是否登陆或者是否有权限等 if(id!=null && !"".equals("id") && type!=null && !"".equals("type")){ //用户是否有权限
boolean idAccess = true;
//类型是否符合定义
boolean typeAccess = true; if(idAccess && typeAccess){
System.out.println("添加到连接池:"+request);
Constant.pushCtxMap.put(request,ctx);
Constant.aaChannelGroup.add(ctx.channel());
} //根据type 存放进对于的channel池,这里就简单实现,直接放进aaChannelGroup,方便群发 } }
//第一次请求是http请求,请求头包括ws的信息
public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){ if(!req.decoderResult().isSuccess()){ sendHttpResponse(ctx,req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
} WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws:/"+ctx.channel()+ "/websocket",null,false);
handshaker = wsFactory.newHandshaker(req); if(handshaker == null){
//不支持
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
}else{ handshaker.handshake(ctx.channel(), req);
} } public static void sendHttpResponse(ChannelHandlerContext ctx,FullHttpRequest req,DefaultFullHttpResponse res){ // 返回应答给客户端
if (res.status().code() != 200)
{
ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
} // 如果是非Keep-Alive,关闭连接
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.status().code() != 200)
{
f.addListener(ChannelFutureListener.CLOSE);
} } private static boolean isKeepAlive(FullHttpRequest req)
{
return false;
} //异常处理,netty默认是关闭channel
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
// TODO Auto-generated method stub
//输出日志
cause.printStackTrace();
ctx.close();
} }
八、BaseWebSocketServerHandler
把推送方法单独抽象出来,方便调用
package com.netty.server; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; /**
* 发消息方式 抽象出来
*
* */
public abstract class BaseWebSocketServerHandler extends SimpleChannelInboundHandler<Object>{ /**
* 推送单个
*
* */
public static final void push(final ChannelHandlerContext ctx,final String message){ TextWebSocketFrame tws = new TextWebSocketFrame(message);
ctx.channel().writeAndFlush(tws); }
/**
* 群发
*
* */
public static final void push(final ChannelGroup ctxGroup,final String message){ TextWebSocketFrame tws = new TextWebSocketFrame(message);
ctxGroup.writeAndFlush(tws); }
}
九、配置
application-netty.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd"> <bean id="bossGroup" class="io.netty.channel.nio.NioEventLoopGroup"></bean>
<bean id="workerGroup" class="io.netty.channel.nio.NioEventLoopGroup"></bean>
<bean id="serverBootstrap" class="io.netty.bootstrap.ServerBootstrap" scope="prototype"></bean>
<bean id="webSocketServer" class="com.netty.server.WebSocketServer"> <property name="port" value="${websocket.server.port}"></property>
<property name="childChannelHandler" ref="webSocketChildChannelHandler" />
</bean>
</beans>
application-beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> <context:annotation-config /> <context:component-scan base-package="com.netty">
<!-- 排除vst.back目录下Controller的service注入 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan> <bean id="configProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath*:conf/websocket.properties</value>
</list>
</property>
</bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="properties" ref="configProperties" />
</bean> </beans>
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> <description>Spring-web MVC配置</description> <bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean> </list>
</property>
</bean> <mvc:annotation-driven /> <context:component-scan base-package="com.netty.controller">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service" />
</context:component-scan> </beans>
websocket.properties
websocket.server.port=7397
十、客户端
用的jsp页面,具体连接逻辑什么的看需要写
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
</head>
</head>
<script type="text/javascript">
var socket;
//实际生产中,id可以从session里面拿用户id
var id = Math.random().toString(36).substr(2);
if(!window.WebSocket){
window.WebSocket = window.MozWebSocket;
} if(window.WebSocket){
socket = new WebSocket("ws://localhost:7397"); socket.onmessage = function(event){
appendln("receive:" + event.data);
}; socket.onopen = function(event){
appendln("WebSocket is opened");
login();
}; socket.onclose = function(event){
appendln("WebSocket is closed");
};
}else{
alert("WebSocket is not support");
} function appendln(text) {
var ta = document.getElementById('responseText');
ta.value += text + "\r\n";
} function login(){
console.log("aaaaaa");
var date={"id":id,"type":"aa"};
var login = JSON.stringify(date);
socket.send(login); } </script>
<body>
<form onSubmit="return false;">
<input type = "text" name="message" value="hello"/>
<br/><br/> <textarea id="responseText" style="width: 800px;height: 300px;"></textarea>
</form>
</body>
</html>
十一、源码
Spring+Netty+WebSocket实例的更多相关文章
- Spring boot+Websocket实例1
简单的demo https://github.com/callicoder/spring-boot-websocket-chat-demo
- Spring Chapter4 WebSocket 胡乱翻译 (二)
书接上文,Spring Chapter4 WebSocket 胡乱翻译 (一) 4.4.4. 消息流 一旦暴露了STOMP端点,Spring应用程序就成为连接客户端的STOMP代理. 本节介绍服务器端 ...
- spring集成webSocket实现服务端向前端推送消息
原文:https://blog.csdn.net/ya_nuo/article/details/79612158 spring集成webSocket实现服务端向前端推送消息 1.前端连接webso ...
- Spring - Netty (整合)
写在前面 大家好,我是作者尼恩.目前和几个小伙伴一起,组织了一个高并发的实战社群[疯狂创客圈].正在开始 高并发.亿级流程的 IM 聊天程序 学习和实战,此文是: 疯狂创客圈 Java ...
- 转载:Spring+EhCache缓存实例
转载来自:http://www.cnblogs.com/mxmbk/articles/5162813.html 一.ehcahe的介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干 ...
- Spring Rabbitmq HelloWorld实例
之前的博客和大家分享了Rabbitmq的基本框架,及其工作原理,网址为 < http://www.cnblogs.com/jun-ma/p/4840869.html >.今天呢,想和大家一 ...
- Spring+EhCache缓存实例
一.ehcahe的介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider.Ehcache是一种广泛使用的开源Java分布式 ...
- Spring 依赖注入,在Main方法中取得Spring控制的实例
Spring依赖注入机制,在Main方法中通过读取配置文件,获取Spring注入的bean实例.这种应用在实训的时候,老师曾经说过这种方法,而且学Spring入门的时候都会先学会使用如何在普通的jav ...
- Spring+EhCache缓存实例(详细讲解+源码下载)(转)
一.ehcahe的介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider.Ehcache是一种广泛使用的开源Java分布式 ...
随机推荐
- zookeeper提供了什么
简单的说,zookeeper=文件系统+通知机制. 每个子目录项如 NameService 都被称作为 znode,和文件系统一样,我们能够自由的增加.删除znode,在一个znode下增加.删除子z ...
- HttpUtility.UrlEncode,Server.UrlEncode 的区别
引用: 1.HttpUtility.UrlEncode,HttpUtility.UrlDecode是静态方法,而Server.UrlEncode,Server.UrlDecode是实例方法. 2.Se ...
- Vue前后端分离常用JS函数点(一)
1.筛选过滤:array.filter(function(val){}); 会把array中符合规则的数组元素按array里面的元素顺序保留下来. // 官方解释:按返回true或者false,把不 ...
- 用Navicat连接MySQL数据库出现1251错误:密码方式错误
原因:因为MySQL8.0是最新版密码保存方式,而图形化数据库管理工具还是原先的密码保存方式. 解决方式: 用CMD命令号方式进入MySQL use mysql: ALTER USER 'root'@ ...
- BZOJ2733: [HNOI2012]永无乡(线段树合并)
Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以 ...
- 八、Docker+RabbitMQ
原文:八.Docker+RabbitMQ 一.下载镜像 docker pull rabbitmq:management 二.运行 docker run -d --name rabbitmq -e TZ ...
- MFC ClistCtr锁定隐藏某一列
通过设置列的宽度为0, 可以隐藏列表框的某一列,但是用户通过拖动列表框的大小,隐藏的列,可能又被显示出来了. 我们可以自己写一个CListEx继承CListCtr,然后捕获拖动的消息,对该消息进行特殊 ...
- git还原文件
http://jingyan.baidu.com/album/e4511cf33479812b855eaf67.html
- LinearLayout-layout_gravity 属性没有效果分析
今天在一个布局文件中,遇到了一个问题,先看代码 <LinearLayout android:layout_width="match_parent" android:layou ...
- ajax缓存 header头文件
浏览器第一次访问服务器的时候,需要从服务器加载很多静态资源,并将这些资源文件缓存在浏览器中,当再次访问页面的时候,如果有相同资源文件就直接到缓存中去加载,这样就会降低服务器的负载和带宽,加快用户访问, ...