先提出三个问题:
1.默认情况下,netty服务端启多少个线程?
2. netty是如何解决 jdk空轮询的bug?
3.netty是如何保证异步串行化?
答案
1.
优秀的博文地址1:此博主还有另一篇优秀博文【netty搭建socket客户端,断线重连,发送数据,接收服务端数据,spring注入service】
2.
Netty框架学习之(一):Netty框架简介
3.
Netty学习(五)—IdleStateHandler心跳机制
4.
Netty学习(六)—WebSocket通信
5
netty事件
netty的 bund 和 outbund
6.
channel讲解









基于上图,第一个代码演示 Netty
服务端代码:
package com.jidongcloud;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* ClassName:Server
* Package:com.jidongcloud
*
* @DATE:2020/11/14 0014 21:09
* @Author:robin
*/
public class Server {
//IP地址
private static final String ip = "127.0.0.1";
//核心线程数 (一般为cpu的2倍)
private static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2;
//定义一个线程的大小
private static final int BIZTHREADSIZE = 100;
//端口
private int port;
public Server(int port) {
this.port = port;
}
public static void main(String[] args) {
System.out.println("启动Server..........");
new Server(8379).start();
}
/**
* @return void
* @Author robin
* @Description 启动服务的方法:
* 如何去连接,以及连接好了如何去处理 IO事件
* @Date 21:35 2020/11/14 0014
* @Param
**/
//定义 boss线程:管理各种事件的一个组
EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE); //用于处理服务器端接收客户端连接
//定义worker线程
EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE); //进行网络通信(读写)
public void start() {
try {
//辅助工具类,用于服务器通道的一系列配置
ServerBootstrap bootstrap = new ServerBootstrap();
//绑定两个线程组
bootstrap.group(bossGroup, workerGroup)
//NioServerSocketChannel
.channel(NioServerSocketChannel.class) //指定NIO的模式
// .handler("");处理服务端的逻辑,比如我的channel被注册上了。
//传入 ChannelInitializer 类的抽象方法 initChannel java8新特性
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception { //childHandler是对连接 的处理 handler 父子关系
//比如设置 TCP的属性、发送缓冲区、接收缓冲区。
//channel核心的初始化流程
// ChannelPipeline不是单独存在,
// 它肯定会和Channel、ChannelHandler、ChannelHandlerContext关联在一起,
// 所以有关概念这里一起讲。
ChannelPipeline pipeline = ch.pipeline();//业务逻辑是由ChannelHandler类去做的。 pipeline是管理ChannelHandler的一个容器。
//这里加 ChannelHandler
//解码器来解决粘包与半包问题
// maxFrameLength:单个包最大的长度;这个值根据实际场景而定,我设置的是1024,固然我的心跳包不大,但是其他包可能比较大
//lengthFieldOffset:表示数据长度字段开始的偏移量,比如上面的协议,lengthFieldOffset应该取值为5,
// 因为数据长度之前有2个字节的包头,1个字节的功能
//lengthFieldLength:数据长度字段的所占的字节数,上面的协议中写的是2个字节,所以取值为2
//lengthAdjustment:这里取值为10=7(系统时间) + 1(校验码)+ 2 (包尾),如果这个值取值为0,试想一下,
// 解码器跟数据长度字段的取值(这里数据长度内容肯定是1),只向后取一个字节,肯定不对。(lengthAdjustment + 数据长度取值 = 数据长度字段之后剩下包的字节数)
//initialBytesToStrip:表示从整个包第一个字节开始,向后忽略的字节数,我设置为0,
// 本来可以忽略掉包头的两个字节(即设置为2),但是,实际项目中,需要校验包头取值是否为AA55,来判断包的合法性。
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
//字符串类型解码器和编码器
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
//加一个TCP方式的处理类
pipeline.addLast(new ServerHandler());
}
});
/**
* 对于ChannelOption.SO_BACKLOG的解释:
* 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端
* 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到
* 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept
* 从B队列中取出完成了三次握手的连接。
* A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
* 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的
* 连接数并无影响,backlog影响的只是还没有被accept取出的连接
*/
// .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
// .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
// .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
// .childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接
//绑定端口,因为比较耗时,所以同步阻塞。sync ,如果进程不在,会绑定端口失败。
//第一步: 监听
ChannelFuture future = bootstrap.bind(ip, port).sync();
//ChannelFuture关闭的时候需要提示一些信息,所以要注册一个监听器 ,sync,因为关闭也是一个阻塞事件。
future.channel().closeFuture().sync();
System.out.println("Server start: ");
} catch (Exception e) {
e.printStackTrace();
} finally {
//优雅的关闭线程
shutDown();
}
}
private void shutDown() {
//优雅的关闭线程
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("已完成优雅关闭Server。。。");
}
}
package com.jidongcloud;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* ClassName:ServerHandler
* Package:com.jidongcloud
*
* @DATE:2020/11/14 0014 21:11
* @Author:robin TODO: ChannelHandlerAdapter 这个类不行
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
//传播事件,或者说是激活的状态
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// super.channelActive(ctx);
System.out.println("channel active ...");
}
//一个消息可读了,channelRead就可以被回调。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("server receive message : "+msg);
//do something msg
// ByteBuf buf = (ByteBuf)msg;
// byte[] data = new byte[buf.readableBytes()];
// buf.readBytes(data);
// String request = new String(data, "utf-8");
// System.out.println("Server: " + request);
// //写给客户端
// String response = "我是反馈的信息";
Channel channel = ctx.channel();
// channel.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
channel.writeAndFlush ("accept message"+msg);
//.addListener(ChannelFutureListener.CLOSE);
//关闭channel上下文资源
ctx.close();
}
//捕捉异常的方法
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
String message = cause.getMessage();
System.out.println("异常信息----->"+message);
// ctx.close();
}
}
测试网络连接状态:

回车后出现下面信息说明成功启动服务端


客户端代码 :
package com.jidongcloud;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* ClassName:Client
* Package:com.jidongcloud
*
* @DATE:2020/11/14 0014 21:12
* @Author:robin TODO:
*/
public class Client implements Runnable{
//定义worker线程,进行网络通讯,比如 读写操作
public static EventLoopGroup workerGroup = new NioEventLoopGroup();
@Override
public void run() {
try {
// 辅助工具类,用于服务器通道的一系列配置
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
.addLast("frameEncoder",new LengthFieldPrepender(4))
.addLast("decoder",new StringDecoder(CharsetUtil.UTF_8))
.addLast("encoder",new StringEncoder(CharsetUtil.UTF_8))
.addLast("handler",new ClientHandler());
}
});
for (int i = 0; i <10 ; i++) {
ChannelFuture future = bootstrap.connect("127.0.0.1", 8379).sync();
future.channel().writeAndFlush("hello,Server"+Thread.currentThread().getName()+"------>"+i);
// 闭的时候需要提示一些信息,所以要注册一个监听器 ,sync,因为关闭也是一个阻塞事件。
future.channel().closeFuture().sync();
}
}catch (Exception e){
System.out.println("客户端异常------>"+e.getMessage());
}finally {
System.out.println("优雅关闭client。。。");
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i <3 ; i++) {
new Thread(new Client(),">>>>>this thread"+i).start();
}
}
}
package com.jidongcloud;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* ClassName:ClientHandler
* Package:com.jidongcloud
*
* @DATE:2020/11/14 0014 21:12
* @Author:robin TODO:
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
System.out.println("client receive msg: "+msg);
// ByteBuf buf = (ByteBuf) msg;
// byte[] data = new byte[buf.readableBytes()];
// buf.readBytes(data);
// System.out.println("Client:" + new String(data).trim());
} finally {
// ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
String message = cause.getMessage();
System.out.println("Client 异常信息----->"+message);
// ctx.close();
}
}
测试结果:

channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread1------>0
server receive message : hello,Server>>>>>this thread0------>0
server receive message : hello,Server>>>>>this thread2------>0
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread1------>1
server receive message : hello,Server>>>>>this thread2------>1
server receive message : hello,Server>>>>>this thread0------>1
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>2
server receive message : hello,Server>>>>>this thread0------>2
server receive message : hello,Server>>>>>this thread1------>2
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>3
server receive message : hello,Server>>>>>this thread1------>3
server receive message : hello,Server>>>>>this thread0------>3
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>4
server receive message : hello,Server>>>>>this thread1------>4
channel active ...
server receive message : hello,Server>>>>>this thread0------>4
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread0------>5
channel active ...
server receive message : hello,Server>>>>>this thread1------>5
server receive message : hello,Server>>>>>this thread2------>5
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread0------>6
server receive message : hello,Server>>>>>this thread2------>6
server receive message : hello,Server>>>>>this thread1------>6
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread1------>7
server receive message : hello,Server>>>>>this thread0------>7
server receive message : hello,Server>>>>>this thread2------>7
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>8
server receive message : hello,Server>>>>>this thread0------>8
server receive message : hello,Server>>>>>this thread1------>8
channel active ...
channel active ...
channel active ...
server receive message : hello,Server>>>>>this thread2------>9
server receive message : hello,Server>>>>>this thread1------>9
server receive message : hello,Server>>>>>this thread0------>9

完整的demo完成。
分析以上代码流程:

需求:做一个类似饿了么的业务推送平台。
消费者------>商家(饿了么)----->骑手

1. 整体架构
业务推送最终是把消费者下单信息推送到骑手手机上。

2.系统架构:

3.逻辑架构:微观的架构,具体用到组件

4.模块划分:









八.消息推送流程:

最后一个是客户端 client






项目结构:


首先是开发core代码:






参考 git
Netty服务器与客户端实现及核心机制解析

本文通过一个完整的Netty服务器和客户端示例,展示了如何配置和启动Netty服务,包括BossGroup和WorkerGroup的使用,以及如何处理连接和数据传输。同时,代码中使用了LengthFieldBasedFrameDecoder解决粘包半包问题,确保数据正确解码。此外,还涉及了ChannelPipeline和ChannelHandler在处理业务逻辑中的应用。
897

被折叠的 条评论
为什么被折叠?



