JavaEE:Netty实现简单WebSocket服务端

一、导入Netty依赖包:

<!-- 导入Netty依赖包 --> 
<dependencies>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.51.Final</version>
    </dependency>
<dependencies>

二、服务端实现:

1.创建WebSocket心跳处理类:

public class WSHRHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {//客户端长时间没发心跳包时,会触发此方法
        if (!(evt instanceof IdleStateEvent)) {
            return;
        }
        IdleStateEvent state = (IdleStateEvent) evt;
        if (state.state() == IdleState.READER_IDLE) { //读空闲
            return;
        }
        if (state.state() == IdleState.WRITER_IDLE) { //写空闲
            return;
        }
        if (state.state() == IdleState.ALL_IDLE) { //客户端连接关闭
            ctx.channel().close();
            return;
        }
    }
}

2.创建用户连接列表管理类:

public class WSUserChannel {
    private static WSUserChannel I = new WSUserChannel();
    private Map<String, Channel> channelMap = new HashMap<>();
    public void addChannel(String userNo, Channel channel) {
        channelMap.put(userNo, channel);
    }
    public void remove(String userNo) {
        channelMap.remove(userNo);
    }
    public void removeByChannel(String channelId) {
        Set<String> userNoSet = channelMap.keySet();
        for (String userNo : userNoSet) {
            Channel channel = channelMap.get(userNo);
            if (channel.id().asLongText().equals(channelId)) {
                channelMap.remove(userNo);
                return;
            }
        }
    }
    public Channel getChannel(String userNo) {
        return channelMap.get(userNo);
    }
}

3.创建WebSocket消息处理类:

(1)创建消息实体类:

//消息实现类
public class Message {
    public MessageHead head; //头信息,消息类别等
    public MessageBody body; //消息内容
}
//消息实现类
public class MessageHead {
    public String userNo; //用户id
    public int type; //大类别,区分登录、登出、普通消息等
}
//消息实现类
public class MessageBody {
    public String senderUserNo;  //发送方用户Id
    public String receiverUsrNo; //接收方用户Id
    public int msgType; //小类别,聊天消息类别
    public String content; //消息内容
}

(2)创建消息处理类:

public class WSMsgHandler extends SimpleChannelInboundHandler<Object> {
    private WebSocketServerHandshaker handshaker;
    //所有用户连接
    private static ChannelGroup clientList = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    @Autowired
    private WSUserChannel userChannel;//用户通道列表,以userNo为Key,Channel为value
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {//新用户连接时触发
        clientList.add(ctx.channel()); //将用户连接保存起来
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {//接收消息
        System.out.println("服务器 ->>>>> channelRead0");
        if (msg instanceof FullHttpRequest) {
            processHttpMsg(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            processWebSocketMsg(ctx, (WebSocketFrame) msg);
        }
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//消息接收完成
        System.out.println("服务器 ->>>>> channelReadComplete");
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//某个用户连接异常
        System.out.println("服务器 ->>>>> exceptionCaught");
        cause.printStackTrace();
        userChannel.removeByChannel(ctx.channel().id().asLongText()); //移除这个用户的连接
        ctx.channel().close(); //关闭这个用户的连接
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {//某个用户连接断开
        userChannel.removeByChannel(ctx.channel().id().asLongText()); //移除这个用户的连接
    }
    private void processHttpMsg(ChannelHandlerContext ctx, FullHttpRequest req) {
        // 如果HTTP解码失败,则返回HTTP异常
        if (!req.decoderResult().isSuccess() || !"websocket".equals(req.headers().get("Upgrade"))) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        // 构造握手响应返回
        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory("ws:localhost:8080/websocket", null, false);
        handshaker = factory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }
    private 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 boolean isKeepAlive(FullHttpRequest req) {
        return false;
    }
    private void processWebSocketMsg(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            System.out.println("服务器 ->>>>> 收到关闭链路的指令");
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否为Ping消息
        if (frame instanceof PingWebSocketFrame) {
            System.out.println("服务器 ->>>>> Ping消息");
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 接收文本消息
        if (frame instanceof TextWebSocketFrame) {
            String body = ((TextWebSocketFrame) frame).text();
            System.out.println("服务器 ->>>>> 收到新消息:" + body);
            parseBody(body, ctx.channel());
        }
        // 发送消息
        System.out.println("服务器 ->>>>> 发送响应消息");
        ctx.channel().write(new TextWebSocketFrame("服务器应答消息"));
    }
    private void parseBody(String body, Channel channel) {
        Message msg = null; //调用gson解析body为Message
        switch (msg.head.type) {
            case 1: //登录时保存用户连接
                userChannel.addChannel(msg.head.userNo, channel);
                break;
            case -1://登出时移出用户连接
                userChannel.remove(msg.head.userNo);
                break;
            case 2://心跳包
                break;
            case 3://普通聊天消息
                //1.保存消息到DB中
                saveMsg(msg.body);
                //2.转发消息给接收的用户
                sendMsgToOtherUser(msg);
                break;
        }
    }
    private void saveMsg(MessageBody msg) {
        //此处保存消息到DB
    }
    private void sendMsgToOtherUser(Message msg) {//转发消息给接收的用户
        Channel channel = userChannel.getChannel(msg.body.receiverUsrNo);  //获取接收方的连接
        if(channel == null){ //不在线,不转发
            return;
        }
        channel.writeAndFlush(new TextWebSocketFrame("json串")); //将msg转为json发出去
    }
}

4.创建WebSocket通道初始化类设置:

public class WSChannelInitializer extends ChannelInitializer<SocketChannel> {
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //将请求一应答消息编码或解码为HTTP消息
        pipeline.addLast("http-codec", new HttpServerCodec());
        //将HTTP消息的多个部分组合成一条完整的HTTP消息
        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
        //向客户端发送HTML5文件,用于支持浏览器与服务器进行WebSocket通信
        pipeline.addLast("http-chunked", new ChunkedWriteHandler());
        //只允许ws方式连接
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        //设置空闲时间,第1参数为读空闲,第2参数为写空闲,第3参数为读写空闲
        pipeline.addLast(new IdleStateHandler(5, 5, 10));
        //添加心跳超时处理,WSHRHandler为自定义类
        pipeline.addLast(new WSHRHandler());
        //添加消息处理适配器
        pipeline.addLast(new WSMsgHandler());
    }
}

5.创建WebSocket服务端启动类:
(1)在工程的中添加websocket服务端口:

websocket:  #自定义名称,websocket服务器端口号
  port: 10001

(2)创建服务端启动类:

@Component
public class WSServer {
    private EventLoopGroup bossGroup; //主线程池,接收客户端连接
    private EventLoopGroup workerGroup; //从(工作)线程池,进行读写消息
    private ServerBootstrap serverBootstrap; //服务端启动工具
    private ChannelFuture channelFuture;
    @Value("${websocket.port}")
    private int port;
    //初始化
    public WSServer() {
        bossGroup = new NioEventLoopGroup(); //主线程池
        workerGroup = new NioEventLoopGroup(); //从(工作)线程池
        serverBootstrap = new ServerBootstrap(); //配置主从线程池和通道处理类
        serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) //配置主从线程池和通道处理类
                .option(ChannelOption.SO_BACKLOG, 1024) //设置队列中等待连接的个数
                .childOption(ChannelOption.SO_KEEPALIVE, true) //保持连接状态
                .childHandler(new WSChannelInitializer()); //配置消息处理类
    }
    //启动
    public void start() {
        //绑定指定端口,并启动WebSocket服务器
        channelFuture = serverBootstrap.bind(port);  //.sync()为同步方式启动
        System.out.println("WebSocket服务端启动成功");
    }
}

6.Spring容器启动时启动WebSocket服务端:

@Component //Spring容器启动时触发,启动WebSocket服务端
public class NettyApplicationListener implements org.springframework.context.ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private WSServer wsServer; //WebSocket服务端启动类
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextEvent) {
        if (contextEvent.getApplicationContext().getParent() == null) {
            try {
                wsServer.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

7.SpringBoot的Application类:

@SpringBootApplication
public class NettyServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(NettyServerApplication.class, args);
    }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值