网络I/o编程模型 19 netty使用udp协议实现c/s通信

Netty实现UDP通信详解

一  使用udp进行通信

1.1 描述

使用netty采用udp协议模拟客户端和服务端进行通信。

1.2 服务端代码

/**
 * @ClassName: UdpServer
 * @Description:
 * TODO
 * 1有一个boostrap服务者
 * 2有两个干活的,也就是处理IO的,一个是boss用于监听端口,一个work干活的
 * 3配置UDP管道,UDP管道主要使用的是NioDatagramChannel
 * 4然后使用pipeline进行配置,可以实现编码和解码等功能,然后寻找到我们的执行的处理的Handler类
 * 5配置我们的服务,例如选择是广播和单播,读和写缓存
 * 6绑定端口,获取通道
 * 7优雅的关闭服务器
 *
 * @Author: liujianfu
 * @Date: 2022/07/09 19:03:58
 * @Version: V1.0
 **/
public class UdpServer {

    //打印日志
    public static final Logger log = Logger.getLogger(UdpServer.class);

    //给管道抽象出接口,给Channel更多的能力和配置,例如Channel的状态,参数,IO操作
    //使用ChannelPipeline实现自定义IO
    public static Channel channel;

    public static void run() throws Exception {
        //启动服务
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        //优化使用的线程
        final EventExecutorGroup group = new DefaultEventExecutorGroup(16);

        try {
            Bootstrap b = new Bootstrap();//udp不能使用ServerBootstrap
            b.group(workerGroup).channel(NioDatagramChannel.class)//设置UDP通道
                    //设置udp的管道工厂
                    .handler(new ChannelInitializer<NioDatagramChannel>(){
                        //NioDatagramChannel标志着是UDP格式的
                        @Override
                        protected void initChannel(NioDatagramChannel ch)
                                throws Exception {
                            // TODO Auto-generated method stub
                            //创建一个执行Handler的容器
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            //执行具体的处理器
                            pipeline.addLast(group,"handler",new UdpServerHandler());//消息处理器
                        }

                    })//初始化处理器
                    //true / false 多播模式(UDP适用),可以向多个主机发送消息
                    .option(ChannelOption.SO_BROADCAST, true)// 支持广播
                    .option(ChannelOption.SO_RCVBUF, 2048 * 1024)// 设置UDP读缓冲区为2M
                    .option(ChannelOption.SO_SNDBUF, 1024 * 1024);// 设置UDP写缓冲区为1M

            // 绑定端口,开始接收进来的连接  ,绑定的端口9999
            ChannelFuture f = b.bind(Constants.PORT).sync();
            //获取channel通道
            channel=f.channel();
            System.out.println("UDP Server 启动!");
            // 等待服务器 socket 关闭 。
            // 这不会发生,可以优雅地关闭服务器。
            f.channel().closeFuture().sync();
        }finally {
            // 优雅退出 释放线程池资源
            group.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception {
        UdpServer.run();
    }
}

2.服务端handler

/**
 * @ClassName: UdpServerHandler
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2022/07/09 19:08:44
 * @Version: V1.0
 **/
public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
    //打印日志
    public static final Logger log = Logger.getLogger(UdpServerHandler.class);
    //在事件循环中执行
    private EventExecutor executor ;
    //客户端与服务器端连接成功的时候触发
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("UDP通道已经连接");
        System.out.println("UDP通道已经连接");
        super.channelActive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
            throws Exception
    {
        // 读取收到的数据
        ByteBuf buf = (ByteBuf) packet.copy().content();
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, CharsetUtil.UTF_8);
        System.out.println("【NOTE】>>>>>> 收到客户端的数据:"+body);

        // 回复一条信息给客户端
        /**
        ctx.writeAndFlush(new DatagramPacket(
                Unpooled.copiedBuffer("Hello,我是Server,我的时间戳是"+System.currentTimeMillis()
                        , CharsetUtil.UTF_8)
                , packet.sender())).sync(); **/  //(Netty权威指南和Netty实战中都有谈到,不要在ChannelHandler方法中调用sync()或await()方法,会有可能引起死锁。)
//https://blog.youkuaiyun.com/weixin_39734304/article/details/118975742
        ctx.writeAndFlush(new DatagramPacket(
                Unpooled.copiedBuffer("Hello,我是Server,我的时间戳是"+System.currentTimeMillis()
                        , CharsetUtil.UTF_8)
                , packet.sender())).sync();


    }
    //消息没有结束的时候触发
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    //捕获异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
        //logger.log(Level.INFO, "AuthServerInitHandler exceptionCaught");
        log.error("UdpServerHandler exceptionCaught"+cause.getMessage());
        System.out.println("UdpServerHandler exceptionCaught"+cause.getMessage());
        cause.printStackTrace();
        //ctx.close();
    }
}

1.3 客户端代码

/**
 * @ClassName: UdpClient
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2022/07/09 19:11:36
 * @Version: V1.0
 **/
public class UdpClient {
    public static void run(int port) {
        //创建于服务端就是连接操作,创建线程
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //开始客户端的服务,和管道的设置
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST,true)
                    .option(ChannelOption.SO_RCVBUF, 2048 * 1024)// 设置UDP读缓冲区为2M
                    .option(ChannelOption.SO_SNDBUF, 1024 * 1024)// 设置UDP写缓冲区为1M
                    .handler(new UdpClientHandler());
            //服务端绑定的管道的端口
            Channel ch = b.bind(8888).sync().channel();
            // 向网段类所有机器广播发UDP,这是想客户端发送内容
            ch.writeAndFlush(new DatagramPacket(
                    Unpooled.copiedBuffer("我是在客户端写的!!!", CharsetUtil.UTF_8),
                    //地址
                    new InetSocketAddress(
                            "localhost",port
                    ))).sync();
            //如果超过长时间则表示超时
            if(!ch.closeFuture().await(15000)){
                System.out.println("查询超时!!!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅的关闭释放内存
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception{
        int port = 666;
        int k=100;
        for(int m=0;m<10;m++){
             Thread.sleep(2000);
            new UdpClient().run(port);
        }

    }
}

2.客户端hadler

/**
 * @ClassName: UdpClientHandler
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2022/07/09 19:17:33
 * @Version: V1.0
 **/
public class UdpClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {
    //接受服务端发送的内容
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        String  body =  msg.content().toString(CharsetUtil.UTF_8);
        System.out.println(body+"这是服务端发送的内容");
        //这里接收到服务端发送的1内容
    }

    //捕获异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

1.4 调式运行

1.客户端

2.服务端

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值