netty(六) websocket开发应用
package com.lance.net.server.common; import java.net.InetSocketAddress; import org.springframework.stereotype.Component; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.ImmediateEventExecutor; @Component
public class ChatServer {
private final ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
private final EventLoopGroup bossGroup = new NioEventLoopGroup();
private final EventLoopGroup workGroup = new NioEventLoopGroup();
private Channel channel; public ChannelFuture start(InetSocketAddress address) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChatServerInitializer(channelGroup))
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture future = bootstrap.bind(address).syncUninterruptibly();
channel = future.channel();
return future;
} public void destroy() {
if(channel != null) {
channel.close();
} channelGroup.close();
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} public static void main(String[] args) {
ChatServer server = new ChatServer();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9090);
ChannelFuture future = server.start(address); Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
server.destroy();
}
}); future.channel().closeFuture().syncUninterruptibly();
}
}
package com.lance.net.server.common; import java.util.concurrent.TimeUnit; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler; public class ChatServerInitializer extends ChannelInitializer<Channel>{
private final ChannelGroup group; public ChatServerInitializer(ChannelGroup group) {
this.group = group;
} @Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//处理日志
pipeline.addLast(new LoggingHandler(LogLevel.INFO)); //处理心跳
pipeline.addLast(new IdleStateHandler(0, 0, 1800, TimeUnit.SECONDS));
pipeline.addLast(new ChatHeartbeatHandler()); pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
pipeline.addLast(new HttpRequestHandler("/ws"));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new TextWebSocketFrameHandler(group));
}
}
package com.lance.net.server.common; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil; public class ChatHeartbeatHandler extends ChannelInboundHandlerAdapter{
private Logger logger = LogManager.getLogger();
private final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HB",CharsetUtil.UTF_8)); @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
logger.info("====>Heartbeat: greater than {}", 180);
ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}else {
super.userEventTriggered(ctx, evt);
}
}
}
package com.lance.net.server.common; import java.io.RandomAccessFile;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import io.netty.channel.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.lance.net.server.module.UserInfo; 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.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;
import org.springframework.util.CollectionUtils; public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private Logger loger = LogManager.getLogger();
private final String webUri;
private final String INDEX = "E:\\workspace\\test\\src\\main\\webapp\\index.html";
/**用来保存channel*/
public static Map<String, Channel> onlineChannels = new ConcurrentHashMap<>(); public HttpRequestHandler(String webUri) {
this.webUri = webUri;
} @Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
loger.info("===========> {}, {}", webUri, request.uri()); String uri = StringUtils.substringBefore(request.uri(), "?");
if(webUri.equalsIgnoreCase(uri)) {//获取webSocket参数
QueryStringDecoder query = new QueryStringDecoder(request.uri());
Map<String, List<String>> map = query.parameters();
List<String> tokens = map.get("token");
List<String> userIds = map.get("userId"); //根据参数保存当前登录对象, 并把该token加入到channel中
if(tokens != null && !tokens.isEmpty()) {
String token = tokens.get(0);
ChatConstants.addOnlines(token, new UserInfo(token));
ctx.channel().attr(ChatConstants.CHANNEL_TOKEN_KEY).getAndSet(token);
}
//将channel放入map中,key为用户id,value为channel
if(!CollectionUtils.isEmpty(userIds)){
onlineChannels.put(userIds.get(0),ctx.channel());
} request.setUri(uri);
ctx.fireChannelRead(request.retain());
}else {
if(HttpUtil.is100ContinueExpected(request)) {
send100ContinueExpected(ctx);
} RandomAccessFile file = new RandomAccessFile(INDEX, "r");
HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); boolean keepAlive = HttpUtil.isKeepAlive(request);
if(keepAlive) {
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
ctx.write(response); if(ctx.pipeline().get(SslHandler.class) == null) {
ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
}else {
ctx.write(new ChunkedNioFile(file.getChannel()));
} ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
if(!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
} file.close();
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
} private void send100ContinueExpected(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONFLICT);
ctx.writeAndFlush(response);
}
}
package com.lance.net.server.common; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.lance.net.server.module.ChatMessage;
import com.lance.net.server.module.UserInfo; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
private Logger loger = LogManager.getLogger();
private final ChannelGroup group; public TextWebSocketFrameHandler(ChannelGroup group) {
this.group = group;
} @Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
loger.info("Event====>{}", evt); if(evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
ctx.pipeline().remove(HttpRequestHandler.class); //加入当前, 上线人员推送前端,显示用户列表中去
Channel channel = ctx.channel();
ChatMessage message = new ChatMessage(null, "上线了");
group.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message,SerializerFeature.DisableCircularReferenceDetect)));
group.add(channel);
}else {
super.userEventTriggered(ctx, evt);
}
} @Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
Channel channel = ctx.channel();
String token = channel.attr(ChatConstants.CHANNEL_TOKEN_KEY).get();
UserInfo from = ChatConstants.onlines.get(token);
if(from == null) {
group.writeAndFlush("OK");
}else {
sendToAll(msg,from);
}
} /**
* 广播
* @param msg
* @param from
*/
private void sendToAll(TextWebSocketFrame msg,UserInfo from){
ChatMessage message = new ChatMessage(from, msg.text());
group.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message,SerializerFeature.DisableCircularReferenceDetect)));
} /**
* 为指定用户发消息
* @param msg
* @param from
* @param userId
*/
public void sendToUser(TextWebSocketFrame msg,UserInfo from,String userId){
ChatMessage message = new ChatMessage();
message.setFrom(from);
message.setMessage(msg.text());
Channel channel = HttpRequestHandler.onlineChannels.get(userId);
channel.writeAndFlush(JSON.toJSONString(message,SerializerFeature.DisableCircularReferenceDetect));
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
loger.info("Current channel channelInactive");
offlines(ctx);
} @Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
loger.info("Current channel handlerRemoved");
offlines(ctx);
} private void offlines(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
String token = channel.attr(ChatConstants.CHANNEL_TOKEN_KEY).get();
ChatConstants.removeOnlines(token); group.remove(channel);
ctx.close();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
loger.error("=====> {}", cause.getMessage());
offlines(ctx);
}
}
package com.lance.net.server.common; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils; import com.lance.net.server.module.UserInfo; import io.netty.util.AttributeKey; public class ChatConstants {
public static final AttributeKey<String> CHANNEL_TOKEN_KEY = AttributeKey.valueOf("netty.channel.token");
/**用来保存当前在线人员*/
public static Map<String, UserInfo> onlines = new ConcurrentHashMap<>(); public static void addOnlines(String sessionId, UserInfo val) {
onlines.putIfAbsent(sessionId, val);
} public static void removeOnlines(String sessionId) {
if(StringUtils.isNotBlank(sessionId) && onlines.containsKey(sessionId)){
onlines.remove(sessionId);
}
} private static char[]prefix = {'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y'};
private static int[]imgPrefix = {1,2,3,4,5,6,7,8,9,10,11}; public static String headImg() {
int index = RandomUtils.nextInt(0, imgPrefix.length);
return "/resources/img/head/"+imgPrefix[index]+".jpg";
} public static String code() {
int index = RandomUtils.nextInt(0, prefix.length);
char prf = prefix[index];
String len = (onlines.size()+1)+"";
if(len.length() < 4) {
len = StringUtils.leftPad(len, 4, '0');
}
return prf+len;
}
}
package com.lance.net.server.module; import java.util.Date;
import java.util.Map; import com.alibaba.fastjson.annotation.JSONField;
import com.lance.net.server.common.ChatConstants; public class ChatMessage {
//发送消息则
private UserInfo from; //发送内容
private String message; //接收者列表
private Map<String, UserInfo> to; //发送时间
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private Date createTime; public ChatMessage() { } public ChatMessage(UserInfo from,String message) {
this.from = from;
this.message = message;
this.to = ChatConstants.onlines;
this.createTime = new Date();
} public String getMessage() {
return message;
} public void setMessage(String message) {
this.message = message;
} public UserInfo getFrom() {
return from;
} public void setFrom(UserInfo from) {
this.from = from;
} public Map<String, UserInfo> getTo() {
return to;
} public void setTo(Map<String, UserInfo> to) {
this.to = to;
} public Date getCreateTime() {
return createTime;
} public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
package com.lance.net.server.module; import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import com.lance.net.server.common.ChatConstants; public class UserInfo implements Serializable{
private static final long serialVersionUID = 3562768188264006800L;
public static Map<String, UserInfo> map = new ConcurrentHashMap<>(); private Long id; private String phone; private String password; private String code; private String headImg; public UserInfo() { } public UserInfo(String phone) {
this.phone = phone;
this.headImg = ChatConstants.headImg();
this.code = ChatConstants.code();
this.id = System.currentTimeMillis();
} public String getHeadImg() {
return headImg;
} public void setHeadImg(String headImg) {
this.headImg = headImg;
} public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public String getPhone() {
return phone;
} public void setPhone(String phone) {
this.phone = phone;
} public String getCode() {
return code;
} public void setCode(String code) {
this.code = code;
}
}
netty(六) websocket开发应用的更多相关文章
- 【Netty】WebSocket
一.前言 前面学习了codec和ChannelHandler之间的关系,接着学习WebSocket. 二.WebSocket 2.1. WebSocket介绍 WebSocket协议允许客户端和服务器 ...
- Netty对WebSocket的支持(五)
Netty对WebSocket的支持(五) 一.WebSocket简介 在Http1.0和Http1.1协议中,我们要实现服务端主动的发送消息到网页或者APP上,是比较困难的,尤其是现在IM(即时通信 ...
- 使用Netty做WebSocket服务端
使用Netty搭建WebSocket服务器 1.WebSocketServer.java public class WebSocketServer { private final ChannelGro ...
- Netty之WebSocket和四种IO介绍
Netty简介 一.什么是netty? 高性能 事件驱动 异步非堵塞 基于NIO的客户端,服务器端编程框架 稳定性和伸缩性 二.Netty的使用场景 高性能领域 多线程并发领域 异步通信领域 ...
- netty实现websocket发送文本和二进制数据
原文:https://huan1993.iteye.com/blog/2433552 最近在学习netty相关的知识,看到netty可以实现 websoket,因此记录一下在netty中实现webso ...
- Netty 搭建 WebSocket 服务端
一.编码器.解码器 ... ... @Autowired private HttpRequestHandler httpRequestHandler; @Autowired private TextW ...
- netty系列之:使用netty搭建websocket服务器
目录 简介 netty中的websocket websocket的版本 FrameDecoder和FrameEncoder WebSocketServerHandshaker WebSocketFra ...
- netty系列之:使用netty搭建websocket客户端
目录 简介 浏览器客户端 netty对websocket客户端的支持 WebSocketClientHandshaker WebSocketClientCompressionHandler netty ...
- Netty 实现 WebSocket 聊天功能
上一次我们用Netty快速实现了一个 Java 聊天程序(见http://www.waylau.com/netty-chat/).现在,我们要做下修改,加入 WebSocket 的支持,使它可以在浏览 ...
随机推荐
- git创建分支并上传仓库
1. 新建分支 xxx 2. git pull (目录下 命令行将线上分支拉倒本地) 3. git checkout xxx (切换到到该分支 ) (可使用 git status 查看目前处于哪一个 ...
- ESP8266EX资料
https://github.com/esp8266/Arduino http://espressif.com/zh-hans/support/explore/faq 电路资料图如下: 介绍功能: 参 ...
- Linux LVM使用小记
对于Linux LVM一直不太理解,直到最近使用了简单功能后才稍微明白点. 对于硬盘空间物理上的使用,我们都是先对硬盘进行分区,然后格式化成文件系统支持的类型,最后给操作系统使用.但是这种使用方式很不 ...
- Discuz! X3 全新安装图文教程
Discuz! 是腾讯旗下 Comsenz 公司推出的以社区为基础的专业建站平台,帮助网站实现一站式服务.让论坛(BBS).个人空间(SNS).门户(Portal).群组(Group).应用开放平台( ...
- 【java】final修饰符介绍
final: 最终,作为一个修饰符特点:1.可以修饰类,函数,变量2.被final修的的类不能被继承.因此类用final修饰可以避免被继承,被子类重写功能.3.被final修饰的方法不可以被重写.4. ...
- CentOS 7下给nginx安装SSL证书
0. DNS要能解析你的网址(域名解析和主机解析,例如example.com和www.example.com都要能解析.注意泛解析记录*.example.com可以存在但在本文中暂时无法用于https ...
- 华硕飞马3S,日常使用续航测试
最近爱机荣耀6的电池1天2充,无奈换台新机,华为系列没大电池且价格贵,小米红米系列品控呵呵,其他品牌无小屏幕大容量电池: 然后换了台华硕飞马3S:5.2英寸 5000ma电池,日常工作娱乐使用1天半多 ...
- Jobs深入学习
代码回顾 Quartz 需要了解你可能希望该作业的实例拥有的各种属性,这是通过JobDetail 类完成的. JobDetail 实例是使用 JobBuilder 类构建的. JobDetail j ...
- lecune入门示例
注意:本示例中的lucene版本需在jdk7以上使用. 一.pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" ...
- Vue非父子组件之间的传值
1.新建一个js文件 然后引入vue 实例化vue 最后暴露这个实例:实例化Vue对象的时候名称要小写,大写控制台报错,我也不知道什么原因: 2.在要广播的地方引入刚才定义的实例: 3通过VueEm ...