Netty入门之UDP协议

在Java中由很多Socket套接字实现,如SocketImpl(对应TCP协议)、DatagramSocketImpl(对应UDP协议)

Netty一般使用的Channel都是NioSocketChannel,它是对应TCP协议的,如果要使用UDP协议,则需要使用另一种Channel,NioDatagramChannel

 

QuoteOfTheMomentClient

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.SocketUtils;
import lombok.extern.slf4j.Slf4j;


/**
 * 一个向 {@link QuoteOfTheMomentServer}询问当下报价(QOTM)的UDP广播客户端
 */
@Slf4j
public final class QuoteOfTheMomentClient {

    static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

    public static void main(String[] args) throws Exception {

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioDatagramChannel.class)//注意这里使用的是NioDatagramChannel,而不是NioSocketChannel,底层网络协议的切换在Channel层
                    .option(ChannelOption.SO_BROADCAST, true)// Socket参数,设置广播模式。
                    .handler(new QuoteOfTheMomentClientHandler());

            Channel ch = b.bind(0).sync().channel();

            /* 广播QOTM请求到端口,直接发送UDP数据报 */
            ch.writeAndFlush(new DatagramPacket(
                    Unpooled.copiedBuffer("QOTM?", CharsetUtil.UTF_8),
                    SocketUtils.socketAddress("255.255.255.255", PORT))).sync();

            /* 当收到响应时,QuoteOfTheMomentClientHandler将关闭DatagramChannel。如果在5秒钟内未关闭通道,则打印错误消息并退出。 */
            if (!ch.closeFuture().await(5000)) {
               log.error("QOTM请求超时.");
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}

1.Bootstrap设置的Channel类为NioDatagramChannel类,所以这里使用的是UDP协议

可以看出Netty的底层网络协议是使用Channel来区分的

2.设置了ChannelOption.SO_BROADCAST广播模式,这个后面再说

3.writeAndFlush的参数为DatagramPacket,即UDP数据报,这个不是JDK里面的类,而是Netty自有的类,这个类需要数据报内容、地址作为参数

而Netty里面很多方法参数类型都是Object,也是为了网络协议切换后,载荷的切换方便

 

QuoteOfTheMomentClientHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

/* 响应为UDP数据包,DatagramPacket */
@Slf4j
public class QuoteOfTheMomentClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    @Override
    public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        String response = msg.content().toString(CharsetUtil.UTF_8);
        if (response.startsWith("QOTM: ")) {
            log.info("Quote of the Moment: " + response);
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

1.继承了SimpleChannelInboundHandler<DatagramPacket>类,泛型参数为DatagramPacket,即载荷为UDP数据报

2.实现的读取方法为channelRead0,这个方法的载荷参数为DatagramPacket类型

 

QuoteOfTheMomentServer

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.extern.slf4j.Slf4j;

/**
 * 一个UDP服务端,用于应答QOTM请求
 */
@Slf4j
public final class QuoteOfTheMomentServer {

    private static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));

    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioDatagramChannel.class)//也需要使用UDP的Channel
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new QuoteOfTheMomentServerHandler());
            b.bind(PORT).sync().channel().closeFuture().await();
        } finally {
            group.shutdownGracefully();
        }
    }
}

1.因为这里采用的是广播模式,所以每个节点都是服务器/客户端,这里也是使用的Bootstrap,而不是ServerBootrap

2.这里进行接收,也需要使用NioDatagramChannel

3.开启广播模式

 

QuoteOfTheMomentServerHandler

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j
public class QuoteOfTheMomentServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    private static final Random random = new Random();

    // Quotes from Mohandas K. Gandhi:
    private static final String[] quotes = {
            "Where there is love there is life.",
            "First they ignore you, then they laugh at you, then they fight you, then you win.",
            "Be the change you want to see in the world.",
            "The weak can never forgive. Forgiveness is the attribute of the strong.",
    };

    /* 获取随机的句子 */
    private static String nextQuote() {
        int quoteId;
        synchronized (random) {
            quoteId = random.nextInt(quotes.length);
        }
        return quotes[quoteId];
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
        log.info(packet.content().toString(CharsetUtil.UTF_8));
        if ("QOTM?".equals(packet.content().toString(CharsetUtil.UTF_8))) {
            ctx.write(new DatagramPacket(Unpooled.copiedBuffer("QOTM: " + nextQuote(), CharsetUtil.UTF_8), packet.sender()));
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
    }
}

1.这里构造UDP数据报时,使用了发送者的地址,说明这个应答不是广播

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章