1.内容简介
这个只是完成了最简单的登录认证,数据都是基于内存的,都没有完成数据库的认证,这里主要是为了了解netty登录和验证的基本的流程。
2.具体实现
2.1准备客户端代码
这里大概的流程就是:
1.客户端初始化然后连接上服务端,等待channelActive事件触发后(就是跟服务端建立好了连接),就在控制台输出请输入用户名的提示然后我们就输入用户名,和密码。
2.输入完了后就发送我们带有用户名和密码的消息,经过出站处理器,首先就是我们的自定义协议的编解码器(这个编解码处理器的实现我前面的文章有写到),把我们的信息编码,然后再经过下一个出战处理器,这个处理器的作用就是打印一下我们输出信息相关的日志信息。然后再把信息发送给服务器。
3.等待接受服务器给我们的响应,还是首先经过了入栈处理器打印消息相关的日志(LOGGING_HANDLER),然后再经过我们自定义的编解码处理器,解码得到真正的消息,然后就是触发了channelRead事件,这就代表我们接收并拿到了服务器发送给我们的消息,我们把消息读出来,看到底是登录成功还是失败
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import message.LoginRequestMessage;
import protocol.MessageCodecSharable;
import protocol.ProcotolFrameDecoder;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast("clientHandler",new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(()->{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String username = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
LoginRequestMessage message = new LoginRequestMessage(username, password);
ctx.writeAndFlush(message);
System.out.println("等待");
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.debug("{}",msg);
}
});
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}
2.2准备服务端代码
1.接收到消息还是先经过日志处理器,打印这次接收到客户端消息的,然后在经过我们自定义的编解码处理器,把消息解码出来,传递给我们自定义的处理器(业务处理器)
2.自定义处理器拿到用户的登录相关的消息后,触发read事件,开始处理操作,拿到用户的用户名和密码后判断是否正确,正确就返回正确相关的消息,失败就提示失败相关的消息。发送消息
3.消息又经过我们自定义编解码处理器,给代码进行编码,然后再经过日志处理器打印日志,最后再发送给客户端。这样就
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import message.LoginRequestMessage;
import message.LoginResponseMessage;
import protocol.MessageCodecSharable;
import protocol.ProcotolFrameDecoder;
import server.service.UserServiceFactory;
@Slf4j
public class ChatServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast(new SimpleChannelInboundHandler<LoginRequestMessage>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage message) throws Exception {
String username = message.getUsername();
String password = message.getPassword();
boolean login = UserServiceFactory.getUserService().login(username, password);
LoginResponseMessage loginResponseMessage = null;
if (login){
loginResponseMessage=new LoginResponseMessage(true,"登录成功");
}else {
loginResponseMessage=new LoginResponseMessage(false,"用户名或密码错误");
}
ctx.writeAndFlush(loginResponseMessage);
}
});
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
3.总结
经过上面几个步骤,消息就形成了一个闭环,这个就是一个最简单的登录和验证的过程。
最后可能会对 ch.pipeline().addLast(new ProcotolFrameDecoder());这个处理器的作用不太了解,这个就是确保我们消息的完整的处理器,再网络中,一条消息可能不是一条条完整的消息,因为有些消息很长,必须分段,或者有些消息很短单独一次发出去很浪费网络资源,就会把几条短的消息合并在一起一起发送。所以我们需要一个处理器来确保我们能拿到一条完整的消息,这个大概的原理就是我们消息发送过来的时候我们提前规定好了一条消息的位置和长度,这个处理器知道这个基本的信息后,在接受到一天条长的信息,他读到指定的长度后,就知道这条消息读完了,后面是新的消息,就又根据这个规则去读,当我们读到一条消息的末尾后还没到长度,就知道这个条消息还没完,就会等待下一条消息,知道一条完整的消息读完。
这个处理器的具体实现
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class ProcotolFrameDecoder extends LengthFieldBasedFrameDecoder {
public ProcotolFrameDecoder() {
this(1024, 12, 4, 0, 0);
}
public ProcotolFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
}
这个东西很多netty已经帮我们实现了,我们就只需要给一些参数就行了,这些参数的意思很好,理解,搜一下就知道这些参数是上面意识,这里就不作太多的阐述。