大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年。

很多小伙伴搞不清楚为啥要学习 Netty ,今天这篇文章开始之前,简单说一下自己的看法:

@

觉得不错的话,欢迎 star!ღ( ´・ᴗ・` )比心

下面,我会带着大家搭建自己的第一个 Netty 版的 Hello World 小程序。

首先,让我们来创建服务端。

服务端

我们可以通过 ServerBootstrap 来引导我们启动一个简单的 Netty 服务端,为此,你必须要为其指定下面三类属性:

  1. 线程组一般需要两个线程组,一个负责接处理客户端的连接,一个负责具体的 IO 处理
  2. IO 模型BIO/NIO
  3. 自定义 ChannelHandler处理客户端发过来的数据并返回数据给客户端

创建服务端

/**
* @author shuang.kou
* @createTime 2020年05月14日 20:28:00
*/
public final class HelloServer { private final int port; public HelloServer(int port) {
this.port = port;
} private void start() throws InterruptedException {
// 1.bossGroup 用于接收连接,workerGroup 用于具体的处理
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//2.创建服务端启动引导/辅助类:ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//3.给引导类配置两大线程组,确定了线程模型
b.group(bossGroup, workerGroup)
// (非必备)打印日志
.handler(new LoggingHandler(LogLevel.INFO))
// 4.指定 IO 模型
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
//5.可以自定义客户端消息的业务处理逻辑
p.addLast(new HelloServerHandler());
}
});
// 6.绑定端口,调用 sync 方法阻塞知道绑定完成
ChannelFuture f = b.bind(port).sync();
// 7.阻塞等待直到服务器Channel关闭(closeFuture()方法获取Channel 的CloseFuture对象,然后调用sync()方法)
f.channel().closeFuture().sync();
} finally {
//8.优雅关闭相关线程组资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new HelloServer(8080).start();
} }

简单解析一下服务端的创建过程具体是怎样的:

1.创建了两个 NioEventLoopGroup 对象实例:bossGroupworkerGroup

  • bossGroup : 用于处理客户端的 TCP 连接请求。
  • workerGroup : 负责每一条连接的具体读写数据的处理逻辑,真正负责 I/O 读写操作,交由对应的 Handler 处理。

举个例子:我们把公司的老板当做 bossGroup,员工当做 workerGroup,bossGroup 在外面接完活之后,扔给 workerGroup 去处理。一般情况下我们会指定 bossGroup 的 线程数为 1(并发连接量不大的时候) ,workGroup 的线程数量为 CPU 核心数 *2 。另外,根据源码来看,使用 NioEventLoopGroup 类的无参构造函数设置线程数量的默认值就是 CPU 核心数 *2

2.创建一个服务端启动引导/辅助类: ServerBootstrap,这个类将引导我们进行服务端的启动工作。

3.通过 .group() 方法给引导类 ServerBootstrap 配置两大线程组,确定了线程模型。

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

4.通过channel()方法给引导类 ServerBootstrap指定了 IO 模型为NIO

  • NioServerSocketChannel :指定服务端的 IO 模型为 NIO,与 BIO 编程模型中的ServerSocket对应
  • NioSocketChannel : 指定客户端的 IO 模型为 NIO, 与 BIO 编程模型中的Socket对应

5.通过 .childHandler()给引导类创建一个ChannelInitializer ,然后指定了服务端消息的业务处理逻辑也就是自定义的ChannelHandler 对象

6.调用 ServerBootstrap 类的 bind()方法绑定端口

//bind()是异步的,但是,你可以通过 `sync()`方法将其变为同步。
ChannelFuture f = b.bind(port).sync();

自定义服务端 ChannelHandler 处理消息

HelloServerHandler.java

/**
* @author shuang.kou
* @createTime 2020年05月14日 20:39:00
*/
@Sharable
public class HelloServerHandler extends ChannelInboundHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
ByteBuf in = (ByteBuf) msg;
System.out.println("message from client:" + in.toString(CharsetUtil.UTF_8));
// 发送消息给客户端
ctx.writeAndFlush(Unpooled.copiedBuffer("你也好!", CharsetUtil.UTF_8));
} finally {
ReferenceCountUtil.release(msg);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}

这个逻辑处理器继承自ChannelInboundHandlerAdapter 并重写了下面 2 个方法:

  1. channelRead() :服务端接收客户端发送数据调用的方法
  2. exceptionCaught() :处理客户端消息发生异常的时候被调用

客户端

创建客户端

public final class HelloClient {

    private final String host;
private final int port;
private final String message; public HelloClient(String host, int port, String message) {
this.host = host;
this.port = port;
this.message = message;
} private void start() throws InterruptedException {
//1.创建一个 NioEventLoopGroup 对象实例
EventLoopGroup group = new NioEventLoopGroup();
try {
//2.创建客户端启动引导/辅助类:Bootstrap
Bootstrap b = new Bootstrap();
//3.指定线程组
b.group(group)
//4.指定 IO 模型
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// 5.这里可以自定义消息的业务处理逻辑
p.addLast(new HelloClientHandler(message));
}
});
// 6.尝试建立连接
ChannelFuture f = b.connect(host, port).sync();
// 7.等待连接关闭(阻塞,直到Channel关闭)
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new HelloClient("127.0.0.1",8080, "你好,你真帅啊!哥哥!").start();
}
}

继续分析一下客户端的创建流程:

1.创建一个 NioEventLoopGroup 对象实例服务端创建了两个 NioEventLoopGroup 对象

2.创建客户端启动的引导类是 Bootstrap

3.通过 .group() 方法给引导类 Bootstrap 配置一个线程组

4.通过channel()方法给引导类 Bootstrap指定了 IO 模型为NIO

5.通过 .childHandler()给引导类创建一个ChannelInitializer ,然后指定了客户端消息的业务处理逻辑也就是自定义的ChannelHandler 对象

6.调用 Bootstrap 类的 connect()方法连接服务端,这个方法需要指定两个参数:

  • inetHost : ip 地址
  • inetPort : 端口号
    public ChannelFuture connect(String inetHost, int inetPort) {
return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
public ChannelFuture connect(SocketAddress remoteAddress) {
ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
this.validate();
return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
}

connect 方法返回的是一个 Future 类型的对象

public interface ChannelFuture extends Future<Void> {
......
}

也就是说这个方是异步的,我们通过 addListener 方法可以监听到连接是否成功,进而打印出连接信息。具体做法很简单,只需要对代码进行以下改动:

ChannelFuture f = b.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println("连接成功!");
} else {
System.err.println("连接失败!");
}
}).sync();

自定义客户端 ChannelHandler 处理消息

HelloClientHandler.java


/**
* @author shuang.kou
* @createTime 2020年05月14日 20:46:00
*/
@Sharable
public class HelloClientHandler extends ChannelInboundHandlerAdapter { private final String message; public HelloClientHandler(String message) {
this.message = message;
} @Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("client sen msg to server " + message);
ctx.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
try {
System.out.println("client receive msg from server: " + in.toString(CharsetUtil.UTF_8));
} finally {
ReferenceCountUtil.release(msg);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}

这个逻辑处理器继承自 ChannelInboundHandlerAdapter,并且覆盖了下面三个方法:

  1. channelActive() :客户端和服务端的连接建立之后就会被调用
  2. channelRead :客户端接收服务端发送数据调用的方法
  3. exceptionCaught :处理消息发生异常的时候被调用

运行程序

首先运行服务端 ,然后再运行客户端。

如果你看到,服务端控制台打印出:

message from client:你好,你真帅啊!哥哥!

客户端控制台打印出:

client sen msg to server 你好,你真帅啊!哥哥!
client receive msg from server: 你也好!

说明你的 Netty 版的 Hello World 已经完成了!

总结

这篇文章我们自己实现了一个 Netty 版的 Hello World,并且详细介绍了服务端和客户端的创建流程。客户端和服务端这块的创建流程,套路基本都差不多,差别可能就在相关配置方面。

文中涉及的代码,你可以在这里找到:https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/netty/echo

「Netty实战 02」手把手教你实现自己的第一个 Netty 应用!新手也能搞懂!的更多相关文章

  1. 【转】手把手教你把Vim改装成一个IDE编程环境(图文)

    手把手教你把Vim改装成一个IDE编程环境(图文) By: 吴垠 Date: 2007-09-07 Version: 0.5 Email: lazy.fox.wu#gmail.com Homepage ...

  2. netty系列之:来,手把手教你使用netty搭建一个DNS tcp服务器

    目录 简介 搭建netty服务器 DNS服务器的消息处理 DNS客户端消息请求 总结 简介 在前面的文章中,我们提到了使用netty构建tcp和udp的客户端向已经公布的DNS服务器进行域名请求服务. ...

  3. 🏆(不要错过!)【CI/CD技术专题】「Jenkins实战系列」(3)Jenkinsfile+DockerFile实现自动部署

    每日一句 没有人会因学问而成为智者.学问或许能由勤奋得来,而机智与智慧却有懒于天赋. 前提概要 Jenkins下用DockerFile自动部署Java项目,项目的部署放心推向容器化时代机制. 本节需要 ...

  4. 🏆【Java技术专区】「开发实战专题」Lombok插件开发实践必知必会操作!

    前言 在目前众多编程语言中,Java 语言的表现还是抢眼,不论是企业级服务端开发,还是 Andorid 客户端开发,都是作为开发语言的首选,甚至在大数据开发领域,Java 语言也能占有一席之地,如Ha ...

  5. 🏆【CI/CD技术专题】「Docker实战系列」(1)本地进行生成镜像以及标签Tag推送到DockerHub

    背景介绍 Docker镜像构建成功后,只要有docker环境就可以使用,但必须将镜像推送到Docker Hub上去.创建的镜像最好要符合Docker Hub的tag要求,因为在Docker Hub注册 ...

  6. 手把手教你用 Spring Boot搭建一个在线文件预览系统!支持ppt、doc等多种类型文件预览

    昨晚搭建环境都花了好一会时间,主要在浪费在了安装 openoffice 这个依赖环境上(Mac 需要手动安装). 然后,又一步一步功能演示,记录,调试项目,并且简单研究了一下核心代码之后才把这篇文章写 ...

  7. 手把手教你制作微信小程序,开源、免费、快速搞定

    最近做了个"罗孚传车"的小程序 一时兴起,做了一个小程序,将个人收集的同汽车相关的行业资讯和学习资料,分享到小程序中,既作为历史资料保存,又提供给更多的人学习和了解,还能装一下:) ...

  8. 从0开始,手把手教你开发并部署上线一个知识测验微信小程序

    上线项目演示 微信搜索[放马来答]或扫以下二维码体验: 项目源码 项目源码 其他版本 Vue答题App实战教程 Hello小程序 1.注册微信小程序 点击立即注册,选择微信小程序,按照要求填写信息 2 ...

  9. 手把手教你把Vim改装成一个IDE编程环境(图文)

    http://blog.csdn.net/wooin/article/details/1858917

随机推荐

  1. Qt自定义控件之仪表盘1--简单的贴图仪表盘

    0.前言 学程序首先要输出hell world,学电子要先来个流水灯.学Qt,那就必须先来个自定义控件,若有人问我从哪个下手,我推荐仪表盘,可简可繁,从低配到高配齐全,可入门也可进阶. 1.仪表盘解析 ...

  2. FGPA_Microblaze UART 中断

    由于底层所给函数发送与接收都采用中断,所用库函数比较复杂 ,有些更改涉及底层函数,因此结合网上论坛 .百度文库调试了串口中断接收程序.通过串口调试助手发送数据 ,以“发送新行”结束 . 硬件外设波特兰 ...

  3. c++ 第二天 命名空间、数组

    C++ 命名空间 命名空间,也就是名称空间/名字空间,注意需要的头文件是 iostream ,而不是 iostream.h ,后者是旧版本的 C++ 头文件,并不支持命名空间. 为什么要使用命名空间? ...

  4. PHP array_flip() 函数

    ------------恢复内容开始------------ 实例 反转数组中的键名和对应关联的键值: <?php$a1=array("a"=>"red&qu ...

  5. HDU Typewriter 6583 dp SAM 卡常

    LINK:Typewriter 好久没写SAM了 什么都给忘了. 写了大概2h.感觉被卡常还看了题解. 考虑dp 然后容易想到维护前面的一个j决策 尽可能小. 然后每次考虑向后加一个字符 不过不行就跳 ...

  6. 用大白话解释什么是Socket

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 我在去年就学习过Java中 ...

  7. 实验01——java模拟银行ATM系统

    用java写的一个模拟银行系统,比较初级. ATM.java package cn.tedu.yinhang; import java.util.Scanner; /** * @author 赵瑞鑫 ...

  8. 构建一个拥有sshd服务的docker镜像

    不直接描述结果,通过一个过程探究如何写一个 Dockerfile 一.环境 虚拟机CentOS7.4,Docker1.13.1 二.尝试步骤 1.下载基础镜像 docker pull alpine:3 ...

  9. 解惑4:java是值传递还是引用传递

    一.概述 曾经纠结了很久java的参数传递方式是什么样的,后面粗略的了解了一鳞半爪以后有了大概的印象:"传参数就是值传递,传对象就是引用传递",后面进一步查找了相关资料和文章以后, ...

  10. JS DOM操作案例

    显示隐藏表单文本内容 <input type="text" value="手机"> var text = document.querySelecto ...