网上很多关于netty的都是TCP的使用,这些写一下UDP的使用,其实也很简单。
这里不介绍TCP协议和UDP协议有什么区别了,网上很多。
这里要说明的一点是netty的TCP和UDP使用有点不一样:
1、netty TCP是每个客户端连接过来都有一条连接,而netty UDP没有连接,只监听端口。
2、netty TCP可以在Channel获取远程客户端的ip和端口号,而netty UDP 无法从Channel获取远程客户端的ip和端口号,而是通过发过来的DatagramPacket中的sender获取发送消息客户端的ip和端口号。
3、netty TCP消息通常需要粘包和拆包,netty UDP不需要粘包拆包,每个包都是完整的。这个算是tcp和udp的区别吧。
4、netty TCP可以定义自己消息的接收格式,netty UDP一般接收的是DatagramPacket包,里面封装了消息对象
看看代码吧,我的代码注释都比较详细。
package com.im.socket.netty.udp;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
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 java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import org.jboss.netty.util.internal.ConcurrentHashMap;
import com.im.socket.netty.udp.UdpServerInitializer;
/**
* 启动UDP服务
*
* @author kokJuis
* @version 1.0
* @date 2016-9-30
*/
public class UdpChatServer {
// UDP服务监听的数据通道
public static Channel channel;
public static ChannelHandlerContext ctx;
// 搞个map保存与客户端地址的映射关系
public static ConcurrentMap userSocketMap = new ConcurrentHashMap();
// 创建一个阻塞队列,用于消息缓冲
public static BlockingQueue msgQueue = new LinkedBlockingQueue();
private int port;// 监听端口号
public UdpChatServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();//udp不能使用ServerBootstrap
b.group(workerGroup).channel(NioDatagramChannel.class)//设置UDP通道
.handler(new UdpServerInitializer())//初始化处理器
.option(ChannelOption.SO_BROADCAST, true)// 支持广播
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_RCVBUF, 1024 * 1024)// 设置UDP读缓冲区为1M
.option(ChannelOption.SO_SNDBUF, 1024 * 1024);// 设置UDP写缓冲区为1M
System.out.println("[UDP 启动了]");
// 绑定端口,开始接收进来的连接
ChannelFuture f = b.bind(port).sync();
channel = f.channel();
// 等待服务器 socket 关闭 。
// 这不会发生,可以优雅地关闭服务器。
f.channel().closeFuture().await();
} finally {
workerGroup.shutdownGracefully();
System.out.println("[UDP 关闭了]");
}
}
}
package com.im.socket.netty.udp;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.codec.DatagramPacketDecoder;
import io.netty.handler.codec.DatagramPacketEncoder;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
/**
* netty处理器配置
*
* @author kokJuis
* @version 1.0
* @date 2016-9-30
*/
public class UdpServerInitializer extends
ChannelInitializer {
Timer timer;
public UdpServerInitializer() {
timer = new HashedWheelTimer();
}
@Override
public void initChannel(NioDatagramChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/**
* 从TCP与UDP的区别讲起
* 网络数据经过路由器,如果数据很小,没有超过路由器的封包大小,就会直接直接经过路由器到达下一个路由器,一层一层最终到达目的地
* 如果数据很大,这里指一个发送,超过了路由器的封包大小,那么路由器就会把这个数据包进行拆分,比如拆分成A B
* C三个包,这三个包都没有超过路由器的封包大小,到达下一个路由器的时候,TCP与UDP的区别就来了:
* TCP收到A的时候,会resp通知源路由器,A到达,B C包依然如此,如果由于网络的各种原因,目的路由收到了A
* C,B没有收到,TCP会要求源路由把B包重新发一次
* ,直到ABC包目的路由都接受到了,那么目的路由把ABC包重新组成起始包,继续往下一个路由发送
* ,这就是TCP安全连接的由来,只要发送,我就能保证目的一定能收到(网络断开能检测到)
* UDP则不是这样,如果ABC包拆分之后,目的路由只收到AC
* ,经过检测,B没有被收到,那么此包就会被当作不完整,直接被丢弃。由于UDP没有resp的通知过程
* ,所以,UDP的传输效率要高一些,当然安全性也低一些
* 由上面的这些可以得出结论:UDP是绝对不会被粘包,因为路由器收到的只会是完整数据才会继续下发,什么粘包处理完全没有必要
* 一般网络编程时候,也会定义数据包头,包体 TCP接收数据的时候,可以先接收包头进行安全验证,通过继续接受包体,不通过直接断开连接
* UDP接受则没有办法这样做
* ,你再大的一个数据,一个RECV,也是直接接受,不能说我先接受多长,这样是不可能的(不过一般大文件数据,都不会用UDP这种不安全传输)
*/
// 添加UDP解码器
// pipeline.addLast("datagramPacketDecoder", new DatagramPacketDecoder(
// new ProtobufDecoder(Message.getDefaultInstance())));
// 添加UDP编码器
// pipeline.addLast("datagramPacketEncoder",
// new DatagramPacketEncoder(new ProtobufEncoder()));
pipeline.addLast("handler", new UdpChatServerHandler());//消息处理器
pipeline.addLast("ackHandler", new UdpAckServerHandler());//ack处理器
pipeline.addLast("timeout", new IdleStateHandler(180, 0, 0,
TimeUnit.SECONDS));// //此两项为添加心跳机制,60秒查看一次在线的客户端channel是否空闲
pipeline.addLast(new UdpHeartBeatServerHandler());// 心跳处理handler
}
}
package com.im.socket.netty.udp;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
/**
* 消息处理类
*
* @author kokJuis
* @version 1.0
* @date 2016-9-30
*/
public class UdpChatServerHandler extends SimpleChannelInboundHandler {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//注意,UDP的通道至始至终只有一个,关了就不能接收了。
System.out.println("UDP通道已经连接");
UdpChatServer.ctx = ctx;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket
packet) throws Exception {
System.out.println("消息来源" + packet.sender().getHostString() +":"+ packet.sender().getPort());
// 消息处理。。。。。
//消息发送。。。。
DatagramPacket dp = new DatagramPacket(Unpooled.copiedBuffer("消息".getBytes()), packet.sender());
UdpChatServer.channel.writeAndFlush(dp);
}
}