总算是破了我的github处,jmeter压测过了,程序没崩用来显示的Chrome崩了。。。
Github地址:https://github.com/LikeAngPlease/webSocket
具体怎么用的大家看下readme吧,在这里不啰嗦了。讲些有用的
这个注释详细的,我感觉没啥可说的了,不懂的私信问我吧。。。
package org.ang.service;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.ImmediateEventExecutor;
import org.ang.handler.AngIdleStateHandler;
import org.ang.handler.BusinessWebSocketHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import java.util.concurrent.TimeUnit;
/**
* @author Liang
*/
public class NettyServer implements Runnable {
@Value("${readerIdleTime}")
int readerIdleTime;
private final int port;
private static final Logger log = LogManager.getLogger(NettyServer.class);
private final ChannelGroup group = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
public NettyServer(int port) {
this.port = port;
}
public void start() throws Exception {
//创建两个线程组 含有的子线程NioEventLoop的个数默认为CUP核数的二倍 bossGroup 只负责处理请求 workGroup会负责和客户端的业务处理
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
//创建服务端启动对象
ServerBootstrap sb = new ServerBootstrap();
//初始化服务器队列连接大小
sb.option(ChannelOption.SO_BACKLOG, 5214);
//// 绑定线程池
sb.group(workGroup, bossGroup)
// 指定使用的channel
.channel(NioServerSocketChannel.class)
// 绑定监听端口
.localAddress(this.port)
// 绑定客户端连接时候触发操作
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) {
//websocket协议本身是基于http协议的,所以这边也要使用http解编码器
ch.pipeline().addLast(new HttpServerCodec());
//以块的方式来写的处理器
ch.pipeline().addLast(new ChunkedWriteHandler());
// pipeline.addLast("http-encodec",new HttpResponseEncoder());
ch.pipeline().addLast(new HttpObjectAggregator(100000));
ch.pipeline().addLast(new AngIdleStateHandler(readerIdleTime, 0, 0, TimeUnit.SECONDS));
ch.pipeline().addLast(new BusinessWebSocketHandler());
// ch.pipeline().addLast(new BusinessWebSocketHandler1());
}
});
// 服务器异步创建绑定
ChannelFuture cf = sb.bind().sync();
log.info(NettyServer.class + "|・ω・`)|・ω・`)|・ω・`)|・ω・`) 启动正在监听: " + cf.channel().localAddress());
// 关闭服务器通道
cf.channel().closeFuture().sync();
} catch (Exception e) {
log.error(e.getMessage());
} finally {
// 释放线程池资源
workGroup.shutdownGracefully().sync();
bossGroup.shutdownGracefully().sync();
}
}
@Override
public void run() {
try {
start();
} catch (Exception e) {
log.error("启动失败");
}
}
}
唯一的难点就是
IdleStateHandler 的超时回调是交给他下一个Handler的。
也就是这个 业务处理的Handler BusinessWebSocketHandler 回调他的
userEventTriggered();具体源码大家看这个 然后CV攻城狮可以去copy源码了。 2020年7月16日23:42:16 该睡觉了。。
package org.ang.handler;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.IdleStateEvent;
import org.ang.pool.Channelpool;
import org.ang.service.MessageParsingService;
import org.ang.util.DefaultUtil;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@Component
public class BusinessWebSocketHandler extends SimpleChannelInboundHandler {
private WebSocketServerHandshaker handshaker;
private static String webSocketUrl;
public static void setWebSocketUrl(String webSocketUrl) {
webSocketUrl = webSocketUrl;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 客户端第一次接入,升级Upgrade websocket
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) msg);
}
// websocket数据交互
else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg.toString());
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// 判断是否是关闭链路的指令
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 UnsupportedOperationException(String.format("%s frame types not supportes", frame.getClass().getName()));
}
// 返回应答消息
String request = ((TextWebSocketFrame) frame).text();
MessageParsingService.messageParsing(request, this);
String timeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
ctx.channel().write(new TextWebSocketFrame(timeStr + ": OK"));
}
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
// 如果http解析失败,返回异常
if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
sendHttpResponse(ctx, req);
return;
}
// 构造握手响应返回
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://127.0.0.1:8848", null, false);
handshaker = wsFactory.newHandshaker(req);
Channel channel = ctx.channel();
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel);
} else {
handshaker.handshake(ctx.channel(), req);
//获取唯一id 和 Channel 保存下来
String id = req.uri().substring(1);
Channelpool.setChannel(id, channel);
//查看是否有延时消息
LinkedList linkedList = Channelpool.getDelayLinkedList(id);
if (linkedList == null || linkedList.size() == 0) {
} else {
ctx.channel().write(new TextWebSocketFrame(linkedList.toString()));
}
}
}
private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req) {
// 返回给客户端
// if(response.status().code() != 200){
// ByteBuf buf = Unpooled.copiedBuffer(req.toString().toString(), CharsetUtil.UTF_8);
// response.content().writeBytes(buf);
// buf.release();
// response.headers().set("Content-Length",response.content().readableBytes());
// }
String message = req.toString();
ChannelFuture f = ctx.channel().writeAndFlush(DefaultUtil.stringToDefaultFullHttpResponse(req.toString()));
// 如果是非Keep-Alive,关闭连接
if (!req.headers().get("Connection").equals("keep-alive")) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
/**
* 处理超时事件
*
* @param ctx
* @param evt
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
ctx.close();
break;
case WRITER_IDLE:
break;
case ALL_IDLE:
break;
default:
break;
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
/**
* 未完成
*
* @param message
* @param ids
* @return
*/
public boolean sendToResponse(String message, List ids) {
ids.forEach((id) -> {
Channel channel = Channelpool.getChannel(id);
if (channel == null) {
} else {
channel.writeAndFlush(DefaultUtil.stringToDefaultFullHttpResponse(message));
}
}
);
return true;
}
/**
* 发送消息给客户端
*
* @param message
* @param ids
* @return
*/
public boolean sendToTextWebSocketFrame(String message, List ids) {
ids.forEach((id) -> {
Channel channel = Channelpool.getChannel(id);
if (channel == null || !channel.isActive()) {
Channelpool.deleteChannel(id);
Channelpool.delayMessage(id, message);
} else {
channel.writeAndFlush(new TextWebSocketFrame(message));
}
}
);
return true;
}
}