作者:Grey

原文地址:Java IO学习笔记八:Netty入门

多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用。

Netty+最朴素的阻塞的方式来实现一版客户端和服务端通信的代码,然后再重构成Netty官方推荐的写法。

第一步,引入netty依赖包。

<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.65.Final</version>
</dependency>

准备发送端

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; import static java.nio.charset.StandardCharsets.UTF_8; /**
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since
*/
public class NettyClientSync {
public static void main(String[] args) throws Exception {
NioEventLoopGroup thread = new NioEventLoopGroup(1);
NioSocketChannel client = new NioSocketChannel();
thread.register(client);
ChannelPipeline p = client.pipeline();
p.addLast(new MyInHandler());
ChannelFuture connect = client.connect(new InetSocketAddress("192.168.205.138", 9090));
ChannelFuture sync = connect.sync();
ByteBuf buf = Unpooled.copiedBuffer("hello server".getBytes());
ChannelFuture send = client.writeAndFlush(buf);
send.sync();
sync.channel().closeFuture().sync();
System.out.println("client over....");
} static class MyInHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("client register...");
} @Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("client active...");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
CharSequence str = buf.getCharSequence(0, buf.readableBytes(), UTF_8);
System.out.println(str);
ctx.writeAndFlush(buf);
}
}
}

这个客户端主要就是给服务端(192.168.205.138:9090)发送数据, 启动一个服务端:

[root@io ~]# nc -l 192.168.205.138 9090

然后启动客户端,服务端可以接收到客户端发来的数据:

[root@io ~]# nc -l 192.168.205.138 9090
hello server

这就是netty实现的一个客户端,再来看服务端的写法:

import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import java.net.InetSocketAddress; /**
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since
*/
public class NettyServerSync {
public static void main(String[] args) throws Exception {
NioEventLoopGroup thread = new NioEventLoopGroup(1);
NioServerSocketChannel server = new NioServerSocketChannel();
thread.register(server);
ChannelPipeline p = server.pipeline();
p.addLast(new MyAcceptHandler(thread, new NettyClientSync.MyInHandler()));
ChannelFuture bind = server.bind(new InetSocketAddress("192.168.205.1",9090));
bind.sync().channel().closeFuture().sync();
System.out.println("server close....");
} static class MyAcceptHandler extends ChannelInboundHandlerAdapter { private final EventLoopGroup selector;
private final ChannelHandler handler; public MyAcceptHandler(EventLoopGroup thread, ChannelHandler myInHandler) {
this.selector = thread;
this.handler = myInHandler;
} @Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("server registered...");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
SocketChannel client = (SocketChannel) msg;
ChannelPipeline p = client.pipeline();
p.addLast(handler);
selector.register(client);
}
}
}

启动这个服务端,然后通过一个客户端来连接这个服务端,并且向这个服务端发送一些数据

[root@io ~]# nc 192.168.205.1 9090
hello
hello

服务端可以感知到客户端连接并接收到客户端发来的数据

client  register...
client active...
hello

但是,这样的服务端如果再接收一个客户端连接,客户端继续发送一些数据进来,服务端就会报一个错误:

An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.channel.ChannelPipelineException: git.snippets.io.netty.NettyClientSync$MyInHandler is not a @Sharable handler, so can't be added or removed multiple times.

原因在这个博客里面说的比较清楚:Netty ChannelHandler使用报错

我们可以发现每当有新的数据可读时都会往这个channel的pipeline里加入handler,这里加的是childHander。值得注意的是,我们初始化的时候这个childHandler都是同一个实例,也就说会导致不同的channel用了同一个handler,这个从netty的设计角度来说是要避免的。因为netty的一大好处就是每一个channel都有自己绑定的eventloop和channelHandler,这样可以保证代码串行执行,不必考虑并发同步的问题。所以才会有checkMultiplicity这个方法来检查这个问题。那该怎么办呢?netty的这段代码:child.pipeline().addLast(childHandler)就是用了同一个handler啊,怎么才能为每一个channel创建不同的handler呢?

很简单,只要写个类继承ChannelInitializer就行了,ChannelInitializer这个类比较特殊,你可以把它想象成是很多channelhandler的集合体,而且这个类就是@Shareable的,继承了这个类之后你可以为每一个channel单独创建handler,甚至是多个handler。

解决方案也很简单,只需要在服务端传入的handler上加上@Sharable注解即可

@ChannelHandler.Sharable
static class MyInHandler extends ChannelInboundHandlerAdapter{
...
}

但是对于每次服务端的Handler,如果都要加@Sharable,就会非常不好扩展,Netty里面提供了一个没有任何业务功能的并且标注为@Sharable的类:ChannelInitializer, 每个业务handler只需要重写其initChannel()方法即可,我们可以改造一下NettyClientSync和NettyServerSync的代码,并用Netty推荐的写法来修改。

客户端改成:

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; /**
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since
*/
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup(1);
Bootstrap bs = new Bootstrap();
ChannelFuture fu = bs
.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
ChannelPipeline pipeline = nioSocketChannel.pipeline();
pipeline.addLast(new NettyClientSync.MyInHandler());
}
}).connect(new InetSocketAddress("192.168.205.138", 9090));
Channel client = fu.channel();
ByteBuf buf = Unpooled.copiedBuffer("Hello Server".getBytes());
ChannelFuture future = client.writeAndFlush(buf);
future.sync();
}
}

启动一个服务端,然后启动上述客户端代码,服务端可以收到信息

[root@io ~]# nc -l 192.168.205.138 9090
Hello Server

接下来改造服务端代码:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; /**
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @since
*/
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup(1);
ServerBootstrap bs = new ServerBootstrap();
ChannelFuture bind = bs
.group(group, group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioServerSocketChannel) throws Exception {
ChannelPipeline pipeline = nioServerSocketChannel.pipeline();
pipeline.addLast(new NettyClientSync.MyInHandler());
}
}).bind(new InetSocketAddress("192.168.205.1", 9090));
bind.sync().channel().closeFuture().sync();
}
}

启动服务端代码,然后通过客户端连接服务端并发送一些数据:

[root@io ~]# nc 192.168.205.1 9090
sdfasdfas
sdfasdfas

可以正常接收。

源码:Github

Java IO学习笔记八:Netty入门的更多相关文章

  1. Java IO学习笔记八

    BufferedReader和BufferedWriter 这两个类是高效率的提高文件的读取速度,它们为字符输入和输出提供了一个缓冲区,可以显著的调高写入和读取的速度,特别针对大量的磁盘文件读取的时候 ...

  2. Java IO学习笔记总结

    Java IO学习笔记总结 前言 前面的八篇文章详细的讲述了Java IO的操作方法,文章列表如下 基本的文件操作 字符流和字节流的操作 InputStreamReader和OutputStreamW ...

  3. Java IO学习笔记:概念与原理

    Java IO学习笔记:概念与原理   一.概念   Java中对文件的操作是以流的方式进行的.流是Java内存中的一组有序数据序列.Java将数据从源(文件.内存.键盘.网络)读入到内存 中,形成了 ...

  4. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

  5. Java IO学习笔记二

    Java IO学习笔记二 流的概念 在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成. 程序中的输入输 ...

  6. Java IO学习笔记一

    Java IO学习笔记一 File File是文件和目录路径名的抽象表示形式,总的来说就是java创建删除文件目录的一个类库,但是作用不仅仅于此,详细见官方文档 构造函数 File(File pare ...

  7. Java IO学习笔记一:为什么带Buffer的比不带Buffer的快

    作者:Grey 原文地址:Java IO学习笔记一:为什么带Buffer的比不带Buffer的快 Java中为什么BufferedReader,BufferedWriter要比FileReader 和 ...

  8. Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer

    作者:Grey 原文地址:Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer ByteBuffer.allocate()与ByteBuffer.allocateD ...

  9. Java IO学习笔记三:MMAP与RandomAccessFile

    作者:Grey 原文地址:Java IO学习笔记三:MMAP与RandomAccessFile 关于RandomAccessFile 相较于前面提到的BufferedReader/Writer和Fil ...

随机推荐

  1. 段间跳转之TSS段

    TR寄存器,TSS描述符,TSS段 TR寄存器与普通的段寄存器一样都有可见部分和不可见部分.TR的可见部分为16位为其段选择子,不可见部分是32位的TSS基地址和16位的大小. TSS描述符存在GDT ...

  2. JS实现单例模式的多种方案

    JS实现单例模式的多种方案 今天在复习设计模式中的-创建型模式,发现JS实现单例模式的方案有很多种,稍加总结了一下,列出了如下的6种方式与大家分享 大体上将内容分为了ES5(Function)与ES6 ...

  3. CF1444A Division 求质因数的方法

    2020.12.20 求质因数的方法 CF1444A Division #include<bits/stdc++.h> #define ll long long #define fp(i, ...

  4. MyBaits自动配置原理

    前言 首先我们建立一个SpringBoot工程,导入mybatis-spring-boot-starter依赖. <dependency> <groupId>org.mybat ...

  5. 『动善时』JMeter基础 — 20、JMeter配置元件【HTTP Cookie管理器】详细介绍

    目录 1.HTTP Cookie管理器介绍 2.HTTP Cookie管理器界面详解 3.JMeter中对Cookie的管理 (1)Cookie的存储 (2)Cookie的管理策略 4.补充:Cook ...

  6. [刷题] 235 Lowest Common Ancestor of a Binary Search Tree

    要求 给定一棵二分搜索树和两个节点,寻找这两个节点的最近公共祖先 示例 2和8的最近公共祖先是6 2和4的最近公共祖先是2 思路 p q<node node<p q p<=node& ...

  7. [Java] 部署到Linux

    阿里云 控制台->云服务器ECS->实例->创建实例 计费方式 地域 网络 安全组:默认安全组 公网IP地址:分配 实例 公网带宽:1M ECS服务器:公共镜像CentOS 存储 购 ...

  8. 016.Python闭包函数以及locals和globals

    一 闭包函数 内函数使用了外函数的局部变量,并且外函数把内函数返回出来的过程叫做闭包,这个内函数叫做闭包函数 1.1 闭包函数语法 def outer(): a = 5 def inner(): pr ...

  9. shell应用之监测电脑内存、负载和磁盘的使用情况

    1 #!/bin/bash 2 for i in $(cat ip.txt);do 3 #内存 4 total_memo=$(ssh $i "free -m" |grep Mem ...

  10. 解决无法访问github的问题

    当我们想学习下载某个大神分享的github项目时,由于github域名解析异常,时常会无法访问Github网站. 下面是我总结分享的有效解决方法:思路是自己手动修改hosts文件添加域名解析! 下面教 ...