前言
经常写tcp,最近需求突然要搞udp和tcp监听同一端口号。
一、tcp和udp区别
TCP 是面向连接的,UDP 是面向无连接的
UDP程序结构较简单
TCP 是面向字节流的,UDP 是基于数据报的
TCP 保证数据正确性,UDP 可能丢包
TCP 保证数据顺序,UDP 不保证
二、netty创建服务
1.tcp服务
代码如下(示例):
public class TcpServer {
public static void main(String[] args) throws Exception {
//创建bossGroup,workGroup
//bossGroup只处理连接请求,真正的和客户的业务处理会交给workGroup完成
// 两个都是无限循环
//bossGroup,workerGroup含有的子线程(NioEventLoopGroup)默认有cpu核数*2
// 指定bossgroup一个线程
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//服务器端通道
.option(ChannelOption.SO_BACKLOG, 128)//设置线程队列得到的连接数
.childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动链接状态
.childHandler(new ChannelInitializer<SocketChannel>() {//匿名创建一个测试对象
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) {
//给我们的workerGroup的EventLoop对应的管道设置处理器
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println("........tcp服务监听启动.....");
// 绑定一个端口并且同步,生成一个channelFuture对象
//启动服务器并绑定端口
ChannelFuture cf = bootstrap.bind(6668).sync();
//对关闭通道进行监听
cf.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
/**
* 1.我们自定义一个handler,需要继承netty规定好的某个handlerAdapter
* 2.这时我们自定义一个ehandler才能称为一个handler
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据事件(这里我们可以读取客户端发送的消息
/**
* channelHandlerContext上下文对象含有:pipeline,通道channel,地址等信息
* Object msg:就是客户端发送的数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
System.out.println("服务器读取数据线程:"+Thread.currentThread().getName());
System.out.println("server ctx = "+ctx);
//将msg转为一个byteBuf,这个ByteBuf是Netty提供的,不同于NIO的ByteBuffer
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端发送消息是:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址:"+ctx.channel().remoteAddress());
/**
* 比如这里我们有个非常耗时的业务,我们需要异步执行,需要提交到该channel对应的NioEvention的taskQueue中
*/
// 解决方案1:用户自定义的程序任务
ctx.channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端喵2", CharsetUtil.UTF_8));
}catch (Exception e){
e.printStackTrace();
}
}
});
System.out.println("go on ......");
// 解决方案2:将任务提交到scheduleTaskQueue中
ctx.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端喵2", CharsetUtil.UTF_8));
}catch (Exception e){
e.printStackTrace();
}
}
},20, TimeUnit.MILLISECONDS);
}
// 数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
//writeAndFlush将数据写入到缓存,并刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端",CharsetUtil.UTF_8));
}
//处理异常,一般是需要关闭通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx , Throwable cause) throws Exception{
ctx.close();
}
}
2.udp服务
代码如下(示例):
public class UdpServer {
public static void main(String[] args) {
try {
Bootstrap b = new Bootstrap();
EventLoopGroup group = new NioEventLoopGroup();
b.group(group)
.channel(NioDatagramChannel.class)
.handler(new UdpServerInitializer17())//处理类
.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.SO_BROADCAST, true)
.option(ChannelOption.SO_RCVBUF, 2048*1024)
.option(ChannelOption.SO_SNDBUF, 1024*1024);
b.bind(6668).sync().channel().closeFuture().await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Udp处理类。
public class UdpServerInitializer17 extends SimpleChannelInboundHandler<DatagramPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
//做类型转换,将msg转换成Netty的ByteBuf对象。
//ByteBuf类似于JDK中的java.nio.ByteBuffer 对象,不过它提供了更加强大和灵活的功能。
ByteBuf buf = packet.copy().content();
//通过ByteBuf的readableBytes方法可以获取缓冲区可读的字节数,
//根据可读的字节数创建byte数组
byte[] req = new byte[buf.readableBytes()];
//通过ByteBuf的readBytes方法将缓冲区中的字节数组复制到新建的byte数组中
buf.readBytes(req);
//通过new String构造函数获取请求消息。
String body = HexStringUtils.bytesToHexString(req).toUpperCase();
System.out.println(body);//打印收到的信息
//向客户端发送消息
String json = "hello world!!";
// 由于数据报的数据是以字符数组传的形式存储的,所以传转数据
byte[] bytes = json.getBytes("UTF-8");
DatagramPacket data = new DatagramPacket(Unpooled.copiedBuffer(bytes), packet.sender());
ctx.writeAndFlush(data);//向客户端发送消息
}
总结
整体tcp和udp监听同一端口是没有问题的后续就是业务处理上的问题。