UDP 是User Datagram Protocol 的简称, 翻译为用户数据报协议。
UDP 是一种无连接的传输协议,应用程序无需创建连接就可以发送数据报。
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 | 为本地管理组播地址,仅在特定的本地范围内有效 |
包含报头在内的数据报的最大长度为 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()
}
}
}
服务启动时,可能会抛出以下异常:
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 服务程序结构几乎相同, 主要区别为: