小李带你写基于netty的webSocket项目,轻松过压测 源码已提交GitHub---------------------源码注释超详细

总算是破了我的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;
    }
}

 

你可能感兴趣的:(netty,websocket)