Netty 实现 UDP 通讯

一、UDP 基础知识

UDP 是User Datagram Protocol 的简称, 翻译为用户数据报协议。
UDP 是一种无连接的传输协议,应用程序无需创建连接就可以发送数据报。
UDP 有三种通讯方式:单播、组播、广播。

1. 通讯方式

  • 单播 通过指定通讯主机 IP 和端口, 可以实现将消息发送到指定主机。
  • 组播 数据收发仅在指定分组中进行,其他未加入分组的主机不能收发对应的数据。
  • 广播 将消息发送到同一广播网络中每个主机。

2. UDP 地址

UDP 采用的也是类似于 IP 一样的地址,无需在操作系统中设置。只需要在应用程序中使用,且与 网卡 IP 地址不冲突。

广播地址

UDP 使用的广播地址为:255.255.255.255, 注意:本地广播信息不会被路由器转发。

组播地址

D 类地址用于组播,D 类地址范围为 224.0.0.0 ~ 239.255.255.255,这些地址又划分为以下 4 类:

地址 说明
224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用
224.0.1.0~224.0.1.255 是公用组播地址,可以用于 Internet;欲使用需申请
224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效

3. UDP 数据报

数据报长度

包含报头在内的数据报的最大长度为 64 K, 一些实际应用往往会限制数据报的大小,有时会降低到 8192 字节。UDP 信息包的标题很短,只有 8 个字节,相对于 TCP 的 20 个字节信息包而言,UDP 的额外开销很小。

数据报特征

UDP 报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。


二、程序实现

服务端

import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelFactory
import io.netty.channel.ChannelInitializer
import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.InternetProtocolFamily
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.util.NetUtil
import udp.UdpChannelInboundHandler

public class Server {

    static void main(String[] args) {

        Server server = new Server()

        InetSocketAddress  address = new InetSocketAddress("239.8.8.1", 51888)

        server.run(address)
    }


    void run(InetSocketAddress groupAddress) {

        EventLoopGroup group = new NioEventLoopGroup()

        try {

            Bootstrap b = new Bootstrap()

            b.group(group)
                    .channelFactory(new ChannelFactory<NioDatagramChannel>() {
                        @Override
                        NioDatagramChannel newChannel() {
                            return new NioDatagramChannel(InternetProtocolFamily.IPv4)
                        }
                    })
                    .handler(new ChannelInitializer<NioDatagramChannel>() {
                        @Override
                        void initChannel(NioDatagramChannel ch) throws Exception {
                            ch.pipeline().addLast(new UdpChannelInboundHandler(true))
                        }
                    })

            NioDatagramChannel ch = (NioDatagramChannel) b.bind(groupAddress.getPort()).sync().channel()


            NetworkInterface ni = NetUtil.LOOPBACK_IF

            println "$ni.name : $ni.displayName"

            ch.joinGroup(groupAddress, ni).sync()

            println "udp server($groupAddress.hostName:$groupAddress.port) is running..."

            ch.closeFuture().await()

        } catch (InterruptedException e) {
            e.printStackTrace()
        } catch (Exception e) {
            e.printStackTrace()
        } finally {
            group.shutdownGracefully()
        }
    }
}

运行结果:
udp server(239.8.8.8:51888) is started…
recv: msg 0
recv: msg 1
recv: msg 2
recv: msg 3
recv: msg 4

客户端

package udp

import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.UnpooledByteBufAllocator
import io.netty.channel.Channel
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.DatagramPacket
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler
import io.netty.util.CharsetUtil

class UdpClient {

    Channel channel

    /**
     * 点对点
     */
    InetSocketAddress remoteAddress = new InetSocketAddress("localhost", 51888)

    /** 广播地址
     InetSocketAddress remoteAddress = new InetSocketAddress("255.255.255.255", 9000)
     */

    /** 组播地址
     InetSocketAddress remoteAddress = new InetSocketAddress("239.8.8.1", 9000)
     */

    void startClient() {

        NioEventLoopGroup group = new NioEventLoopGroup()

        Bootstrap b = new Bootstrap()
        b.group(group)
                .channel(NioDatagramChannel.class)
                .handler(new ChannelInitializer() {
                    protected void initChannel(Channel ch) throws Exception {
                        ch.pipeline()
                                .addLast("recv", new UdpChannelInboundHandler())
                    }
                })
        channel = (NioDatagramChannel) b.bind(8888).sync().channel()
    }

    void sendMsg(String msg) {

        ByteBuf buf = new UnpooledByteBufAllocator(true).buffer()
        buf.writeCharSequence(msg, CharsetUtil.UTF_8)

        def packet = new DatagramPacket(buf, remoteAddress)

        channel.writeAndFlush(packet).sync()
    }

    static void main(String[] args) {

        UdpClient client = new UdpClient()

        client.startClient()

        for (int i = 0; i < 5; i++) {
            def msg = "msg $i"
            println "send msg: $msg"
            client.sendMsg(msg)
        }

        println "send finish.."
    }
}

运行结果:
send msg: msg 0
recv: ok
send msg: msg 1
recv: ok
send msg: msg 2
recv: ok
send msg: msg 3
recv: ok
send msg: msg 4
recv: ok
send finish…

编解码器

package udp

import io.netty.buffer.ByteBuf
import io.netty.buffer.UnpooledByteBufAllocator
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.channel.socket.DatagramPacket
import io.netty.util.CharsetUtil

class UdpChannelInboundHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    boolean rep

    UdpChannelInboundHandler(boolean rep = false) {
        this.rep = rep
    }

    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {

        def buf = msg.content()
        String strMsg = buf.toString(CharsetUtil.UTF_8)
        println "recv: $strMsg"


        if(rep) {
            ByteBuf buf1 = new UnpooledByteBufAllocator(true).buffer()
            buf1.writeCharSequence("ok", CharsetUtil.UTF_8)

            def packet = new DatagramPacket(buf1, msg.sender())

            ctx.writeAndFlush(packet).sync()
        }
    }
}

几个小问题

IllegalArgumentException:IPv6 socket cannot join IPv4 multicast group

服务启动时,可能会抛出以下异常:

java.lang.IllegalArgumentException: IPv6 socket cannot join IPv4 multicast group
  at sun.nio.ch.DatagramChannelImpl.innerJoin(DatagramChannelImpl.java:819)
  at sun.nio.ch.DatagramChannelImpl.join(DatagramChannelImpl.java:905)
  at io.netty.channel.socket.nio.NioDatagramChannel.joinGroup(NioDatagramChannel.java:414)
  at io.netty.channel.socket.nio.NioDatagramChannel.joinGroup(NioDatagramChannel.java:391)
  at io.netty.channel.socket.nio.NioDatagramChannel.joinGroup(NioDatagramChannel.java:384)
  at io.netty.channel.socket.DatagramChannel$joinGroup.call(Unknown Source)
  at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
  at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
  at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)

解决办法是定义 ChannelFactory,指定协议簇为 IPv4

b.group(group)
	  //.channel(NioDatagramChannel.class) 
	  //使用指定 ChannelFactory
	  .channelFactory(new ChannelFactory<NioDatagramChannel>() {
	      @Override
	      NioDatagramChannel newChannel() {
	      	  // 指定协议簇为 IPv4
	          return new NioDatagramChannel(InternetProtocolFamily.IPv4)
	      }
	  })
	  ...

小结

使用 Netty 的 UDP 服务与 TCP 服务程序结构几乎相同, 主要区别为:

  • 使用 NioDatagramChannel 通讯
  • 使用 DatagramPacket 封装数据

你可能感兴趣的:(Netty,netty,网络协议,网络通信,UDP)