客户端与服务端收发消息,要实现的具体功能是:在控制台输入一条消息之后按回车键,校验完客户端的登录状态之后,把消息发送到服务端;服务端收到消息之后打印,并向客户端发送一条消息,客户端收到消息之后打印。
客户端NettyClient
import com.xc.xcspringboot.x_netty.client.handler.*;
import com.xc.xcspringboot.x_netty.protocol.PacketCodec;
import com.xc.xcspringboot.x_netty.protocol.request.MessageRequestPacket;
import com.xc.xcspringboot.x_netty.util.LoginUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.Date;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class NettyClient {
private static final int MAX_RETRY = 5;
private static final String HOST = "127.0.0.1";
private static final int PORT = 8000;
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
// 引导器引导启动
bootstrap
.group(workerGroup) // 指定线程模型,驱动连接的数据读写
.channel(NioSocketChannel.class) // 指定IO模型为NioSocketChannel,表示IO模型为NIO
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 表示连接的超时时间,超过这个时间,如果仍未连接到服务端,则表示连接失败。
.option(ChannelOption.SO_KEEPALIVE, true) // 表示是否开启TCP底层心跳机制,true表示开启。
.option(ChannelOption.TCP_NODELAY, true) // 表示是否开始Nagle算法,true表示关闭,false表示开启。
.handler(new ChannelInitializer<Channel>() { // 给引导类指定一个Handler,主要定义连接的业务处理逻辑
@Override
protected void initChannel(Channel channel) {
// channel.pipeline().addLast(new StringEncoder());
// channel.pipeline().addLast(new FirstClientHandler());
channel.pipeline().addLast(new ClientHandler());
}
});
// 建立通道
/*
* 配置完线程模型、IO模型、业务处理逻辑之后,调用connect方法进行连接,
* 可以看到connect方法有两个参数,第一个参数可以填写IP或者域名,第二个参数填写端口号。
* 由于connect方法返回的是一个Future,也就是说这个方法是异步的,通过addListener方法可以监听连接是否成功,进而打印连接信息。
*/
// Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
connect(bootstrap, HOST, PORT, MAX_RETRY);
}
private static void connect(Bootstrap bootstrap, String host, int port, int retry) {
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 连接成功,启动控制台线程……");
Channel channel = ((ChannelFuture) future).channel();
startConsoleThread(channel);
} else if (retry == 0) {
System.err.println("重试次数已用完,放弃连接!");
} else {
// 第几次重连
int order = (MAX_RETRY - retry) + 1;
// 本次重连的间隔
int delay = 1 << order;
System.err.println(new Date() + ": 连接失败,第" + order + "次重连…… delay:" + delay);
/*
* 定时任务调用的是bootstrap.config().group().schedule(),
* 其中bootstrap.config()这个方法返回的是BootstrapConfig,它是对Bootstrap配置参数的抽象,
* 然后bootstrap.config().group()返回的就是我们在一开始配置的线程模型workerGroup,
* 调用workerGroup的schedule方法即可实现定时任务逻辑。
*/
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
}
});
}
/*private static void startConsoleThread(Channel channel) throws InterruptedException {
while (true) {
channel.writeAndFlush(new Date() + " Hello world");
Thread.sleep(2000);
}
}*/
private static void startConsoleThread(Channel channel) {
new Thread(() -> {
while (!Thread.interrupted()) {
if (LoginUtil.hasLogin(channel)) { // 在判断是否登录成功的时候取出这个标志位
System.out.println("输入消息发送至服务端: ");
Scanner SC = new Scanner(System.in);
String line = SC.nextLine();
MessageRequestPacket packet = new MessageRequestPacket();
packet.setMessage(line);
ByteBuf buffer = channel.alloc().buffer();
PacketCodec.INSTANCE.encode(buffer, packet);
channel.writeAndFlush(buffer);
}
}
}).start();
}
}
ClientHandler
import com.xc.xcspringboot.x_netty.protocol.Packet;
import com.xc.xcspringboot.x_netty.protocol.PacketCodec;
import com.xc.xcspringboot.x_netty.protocol.request.LoginRequestPacket;
import com.xc.xcspringboot.x_netty.protocol.response.LoginResponsePacket;
import com.xc.xcspringboot.x_netty.protocol.response.MessageResponsePacket;
import com.xc.xcspringboot.x_netty.util.LoginUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
public class ClientHandler extends ChannelInboundHandlerAdapter {
// 当 Channel 已经连接/绑定并且已经就绪时调用
public void channelActive(ChannelHandlerContext ctx) {
System.out.println(new Date() + ":客户端开始登录");
//创建登录对象
LoginRequestPacket loginRequestPacket = new LoginRequestPacket();
// loginRequestPacket.setUserId(UUID.randomUUID().toString());
loginRequestPacket.setUserName("flash");
loginRequestPacket.setPassword("pwd");
//编码
ByteBuf buffer = ctx.alloc().buffer();
PacketCodec.INSTANCE.encode(buffer, loginRequestPacket);
// 写数据
ctx.channel().writeAndFlush(buffer);
}
// 当从 Channel 读取数据时被调用
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf byteBuf = (ByteBuf) msg;
Packet packet = PacketCodec.INSTANCE.decode(byteBuf);
if (packet instanceof LoginResponsePacket) { // 登录逻辑
LoginResponsePacket loginResponsePacket = (LoginResponsePacket) packet;
if (loginResponsePacket.isSuccess()) {
LoginUtil.markAsLogin(ctx.channel()); // 在登录成功之后,给Channel绑定一个登录成功的标志位
System.out.println(new Date() + ":客户端登录成功");
} else {
System.out.println(new Date() + ":客户端登录失败,原因:" + loginResponsePacket.getReason());
}
} else if (packet instanceof MessageResponsePacket) { // 消息逻辑
MessageResponsePacket messageResponsePacket = (MessageResponsePacket) packet;
System.out.println(new Date() + ":收到服务端的消息:" + messageResponsePacket.getMessage());
}
}
}
服务端
import com.xc.xcspringboot.x_netty.server.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.Date;
public class NettyServer {
private static final int PORT = 8000;
public static void main(String[] args) {
NioEventLoopGroup boosGroup = new NioEventLoopGroup(); // bossGroup表示监听端口,接收新连接的线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup(); // workerGroup表示处理每一个连接的数据读写的线程组
ServerBootstrap serverBootstrap = new ServerBootstrap(); // 引导类ServerBootstrap,这个类将引导服务端的启动工作
serverBootstrap
.group(boosGroup, workerGroup) // .group(bossGroup,workerGroup)给引导类配置两大线程组
.channel(NioServerSocketChannel.class) // 指定服务端的IO模型为NIO NioServerSocketChannel是对NIO类型连接的抽象
.option(ChannelOption.SO_BACKLOG, 1024) // 表示系统用于临时存放已完成三次握手的请求的队列的最大长度
.childOption(ChannelOption.SO_KEEPALIVE, true) // 表示是否开启TCP底层心跳机制,true表示开启。
.childOption(ChannelOption.TCP_NODELAY, true) // 表示是否开启Nagle算法,true表示关闭,false表示开启
.handler(new ChannelInitializer<NioServerSocketChannel>() { // handler()方法用于指定在服务端启动过程中的一些逻辑
@Override
protected void initChannel(NioServerSocketChannel ch) {
System.out.println("服务端启动过程中...");
}
})
.childHandler(new ChannelInitializer<NioSocketChannel>() { // childHandler()方法,给这个引导类创建一个ChannelInitializer,主要是定义后续每个连接的数据读写
protected void initChannel(NioSocketChannel ch) { // 泛型参数NioSocketChannel,这个类就是Netty对NIO类型连接的抽象
/*ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});*/
// ch.pipeline().addLast(new FirstServerHandler());
ch.pipeline().addLast(new ServerHandler());
}
});
/*
* 要启动一个Netty服务端,必须要指定三类属性,分别是线程模型、IO模型、连接读写处理逻辑。
* 有了这三者,之后再调用bind(8000),就可以在本地绑定一个8000端口启动服务端。
*/
// .bind(8000);
// 给这个ChannelFuture添加一个监听器GenericFutureListener
serverBootstrap.bind(PORT).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 端口[" + PORT + "]绑定成功!");
} else {
System.err.println("端口[" + PORT + "]绑定失败!");
}
});
}
}
ServerHandler
import com.xc.xcspringboot.x_netty.protocol.Packet;
import com.xc.xcspringboot.x_netty.protocol.PacketCodec;
import com.xc.xcspringboot.x_netty.protocol.request.LoginRequestPacket;
import com.xc.xcspringboot.x_netty.protocol.request.MessageRequestPacket;
import com.xc.xcspringboot.x_netty.protocol.response.LoginResponsePacket;
import com.xc.xcspringboot.x_netty.protocol.response.MessageResponsePacket;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
public class ServerHandler extends ChannelInboundHandlerAdapter {
// 当从 Channel 读取数据时被调用
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf requestByteBuf = (ByteBuf) msg;
Packet packet = PacketCodec.INSTANCE.decode(requestByteBuf); // 解码
ByteBuf buffer = ctx.alloc().buffer();
if (packet instanceof LoginRequestPacket) { // 处理登录
LoginRequestPacket loginRequestPacket = (LoginRequestPacket) packet;
LoginResponsePacket loginResponsePacket = new LoginResponsePacket();
loginResponsePacket.setVersion(packet.getVersion());
//登录校验
if (valid(loginRequestPacket)) {
//校验成功
loginResponsePacket.setSuccess(true);
} else {
//校验失败
loginResponsePacket.setReason("账号密码校验失败");
loginResponsePacket.setSuccess(false);
}
//编码
PacketCodec.INSTANCE.encode(buffer, loginResponsePacket);
ctx.channel().writeAndFlush(buffer);
} else if (packet instanceof MessageRequestPacket) { //处理消息
MessageRequestPacket messageRequestPacket = ((MessageRequestPacket) packet);
System.out.println(new Date() + ":收到客户端消息: " + messageRequestPacket.getMessage());
MessageResponsePacket messageResponsePacket = new MessageResponsePacket();
messageResponsePacket.setMessage("服务端回复[" + messageRequestPacket.getMessage() + "] ");
PacketCodec.INSTANCE.encode(buffer, messageResponsePacket);
ctx.channel().writeAndFlush(buffer);
}
}
private boolean valid(LoginRequestPacket loginRequestPacket) {
return true;
}
}