前言

编程学习的方法,我认为是以小见大,在理解一个东西之前一定要先会用,并用熟它,这样理解才会快。
就跟理解自行车一样,不会骑,然后先开始研究,最终可能会研究明白,但是毕竟还是事倍功半。
所以先构建一个可以使自己理解的项目,再一点一点学习原理是一种比较好的方式。

Server服务端

构建netty的话,就是一个流程三件套,最基础的三个框架组件摆出来,然后在上面写代码,分别是:

  1. Server 启动类
  2. Initializer 实始化组件类
  3. Handle 请求处理类

劳记这一个流程三件套,基本netty的开发,你已经入门了,就是这么回事。

启动类

套路第一步,写一个启动类,这个是入口,netty服务的话,一般都是先启动服务端,再启动客户端。
这个好理解,如果服务端都不提供服务,客户端还有必要连接吗。当然如果是要启动着玩,就另说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.liukai.netty.test02.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
* 服务端
*
* @author liu kai
* @since 2020-01-03 00:40
*/
public class ServerNettyServer {

public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerNettyChannelInitializer02());

ChannelFuture channelFuture = serverBootstrap.bind(8889).sync();
System.out.println("Netty 服务端启动完毕");
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
e.printStackTrace();
}
}
}

初始化

实始化必要组件,这也在其它的编常中也是非常常见的一种模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.liukai.netty.test02.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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;

/**
* 服务 Initializer
*
* @author liu kai
* @since 2020-01-03 23:52
*/
public class ServerNettyChannelInitializer02 extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//解码器
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
//编码器
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new ServerNettyServerHandle());
}
}

处理器

处理具体业务,其实看下来,就是这个模式,是不是很好理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.liukai.netty.test02.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.UUID;

/**
* 处理器
*
* @author liu kai
* @since 2020-01-04 22:08
*/
public class ServerNettyServerHandle extends SimpleChannelInboundHandler<String> {

/**
* 处理请求
* @param ctx 表示请求上下文信息。可用于获得channel,远程地址等
* @param msg 客户端消息
* @return void
* @author liu kai
* @since 2020-01-04 22:19
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("server: [remote ip]-> " + ctx.channel().remoteAddress()+", [msg]-> "+msg);
//向客户端发送消息
ctx.channel().writeAndFlush("server: "+ UUID.randomUUID());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}

客户端

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 客户端
*
* @author liu kai
* @since 2020-01-04 22:23
*/
public class ClientServer {

public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new ClientNettyChannelInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost",8889).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
eventLoopGroup.shutdownGracefully();
e.printStackTrace();
}
}
}

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.liukai.netty.test02.client;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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;

/**
* 客户端初始化
*
* @author liu kai
* @since 2020-01-04 22:23
*/
public class ClientNettyChannelInitializer extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new ClientNettyHandle());
}
}

处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.liukai.netty.test02.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.LocalDateTime;

/**
* 处理器
*
* @author liu kai
* @since 2020-01-04 22:27
*/
public class ClientNettyHandle extends SimpleChannelInboundHandler<String> {

/**
* 接收服务器返回消息
* @param ctx 上下文请求对象
* @param msg 表示服务端发来的消息
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("client: [remote ip]->" + ctx.channel().remoteAddress() + ", [msg]->" + msg);
//向服务端发送消息
Thread.sleep(1000);
ctx.writeAndFlush("client:"+ LocalDateTime.now());
}

/**
* 如果没有这个方法,Client并不会主动发消息给Server
* 那么Server的channelRead0无法触发,导致Client的channelRead0也无法触发
* 这个channelActive可以让Client连接后,发送一条消息
* 但是问题在于,有一这个方法后,服务端
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("客户端:发送数据");
}
}

效果

可以启动看一下这个代码的效果:

1.启动服务端Server

Netty 服务端启动完毕
server: [remote ip]-> /127.0.0.1:65320, [msg]-> 客户端:发送数据
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:42.653
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:43.657
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:44.661
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:45.664
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:46.668
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:47.674
server: [remote ip]-> /127.0.0.1:65320, [msg]-> client:2022-05-21T21:49:48.678

2.启动客户端

client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: bd34689c-237a-4499-be9f-be7b55d1f7e2
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: afe29b02-65f8-44c5-8b1b-c5286acd0a72
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 806f346f-9ae8-4dce-ad20-7a308d8b0c46
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 670fb4af-15cd-4471-9990-bc86ff07932c
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: b103ed1a-127b-42df-9c86-58f9e5dd2d4a
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 3948c322-1cb7-4ba0-bade-7f7161fc2712
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: e3daf473-6dd6-4e6c-8bca-27f87e43854f
client: [remote ip]->localhost/127.0.0.1:8889, [msg]->server: 64bf61e2-4126-4238-b53c-4bf90e1c21d8

总结

netyy 的刚开始学习时,只需要了解到它的这个套路,后面的开发其实大同小异,无非是对协议和序列化相关的东西进行处理。实现自己的业务需求。