1.写作目的
这篇文章只实现了最基本的netty聊天功能,真实的业务不是这样实现的,完成这个功能主要为了学习netty相关的使用。
2.具体实现
2.1服务端实现
1.随着进一步的学习,我对服务端的功能和认识有有了进一步的理解,最开始学习netty的时候以为netty是什么神秘莫测的东西,能完成许多神奇的事情,但随着现在的学习,我认为netty作为服务端往往扮演着一个中转站的作用,他接受消息,发送消息,但是很多的时候他对消息做的是一些基础处理,很多实现的细节要我们通过编写不同的处理器和不同的服务端来处理具体的请求和和功能。
2.这里的netty主要通过登录处理器和聊天消息处理器实现了对登录的验证,以及登录会话的管理,还有就是对聊天消息的接受和转发。
3代码具体的实现。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import protocol.MessageCodecSharable;
import protocol.ProcotolFrameDecoder;
import server.hander.ChatRequestMessageHandler;
import server.hander.LoginRequestMessageHandler;
@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();
LoginRequestMessageHandler loginRequestMessageHandler = new LoginRequestMessageHandler();
ChatRequestMessageHandler chatRequestMessageHandler = new ChatRequestMessageHandler();
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(loginRequestMessageHandler);
ch.pipeline().addLast(chatRequestMessageHandler);
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
这里需要用到两个自定义的处理器和一个会话管理
1.LoginRequestMessageHandler(登录处理器)
@ChannelHandler.Sharable
public class LoginRequestMessageHandler extends 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) {
SessionFactory.getSession().bind(ctx.channel(),username);
loginResponseMessage = new LoginResponseMessage(true, "登录成功");
} else {
loginResponseMessage = new LoginResponseMessage(false, "用户名或密码错误");
}
ctx.writeAndFlush(loginResponseMessage);
}
}
2.ChatRequestMessageHandler(聊天消息处理器)
@ChannelHandler.Sharable
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage chatRequestMessage) throws Exception {
Channel channel = SessionFactory.getSession().getChannel(chatRequestMessage.getTo());
//发送方在线
if (channel!=null){
channel.writeAndFlush(new ChatResponseMessage(chatRequestMessage.getFrom(),chatRequestMessage.getContent()));
}else {
//发送方不在线
channel.writeAndFlush(new ChatResponseMessage(false,"对方不在线"));
}
}
}
3.SessionFactory(会话管理)
public abstract class SessionFactory {
private static Session session = new SessionMemoryImpl();
public static Session getSession() {
return session;
}
}
以上就是具体的服务端的实现
2.2客户端实现
1.客户端启动后进行登录,用账号和密码登录和会把登录的信息发送给服务端进行处理,消息经过前面的处理后到达登录处理器,首先验证账号密码的正确,验证成功后就把当前的会话就是一个channel和用户名进行绑定,放进会话管理工具里面,失败的话就会发送登录失败的相关信息,客户端接受到服务端发送的登录失败后就断开连接,成功后就可以发送消息。
2.当前用户登录成功后就用同样的方法登录另外一位用户,等待另一位用户登录成功后就可以开始聊天了,现在发送的聊天的消息很固定,当前张三用户发送"send lisi 你好",会把当前发送的消息用split(" ")分割为3个字符串的数组,数组的第一个字符意识就是进行send操作,第二个 lisi就是发送的对象,第三个你好就是要发送的消息的内容,这条消息和发送这条消息的用户,拼接成(zhansgan,lisi,你好)发送给服务端,这三个意思就是(发送者,接收者,内容),发送到服务端后就会进行解析,拿到接收者后,服务端就会用会话管理器根据接受者的用户名,拿到接收者对应的channel然后发送(zhansgan,你好)就是(发送者,内容),这条消息李四哪里就会接收到(zhansgan,你好),李四就能知道是张三给他发送了你好,然后根据同样的操作,李四就能给张三回消息,如过这个时候张三给李四发送消息的时候,李四未登录或者下线了,就会提示张三对方不在线。(发送离线功能请期待后续更新)
好了这就是客户端基本的逻辑实现。下面是代码具体实现
3。客户端代码具体实现
@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();
CountDownLatch LOGIN_WAIT=new CountDownLatch(1);
try {
final boolean[] flag = {false};
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 {
LOGIN_WAIT.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//登录失败
if (!flag[0]){
System.out.println("登录失败");
ctx.channel().close();
return;
}
//登录成功
System.out.println("登录成功");
while (true){
System.out.println("========");
System.out.println("send [to] [content]");
String s = scanner.nextLine();
String[] s1 = s.split(" ");
switch (s1[0]){
case "send":
ctx.writeAndFlush(new ChatRequestMessage(username,s1[1],s1[2]));
break;
}
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof LoginResponseMessage){
LoginResponseMessage message= (LoginResponseMessage) msg;
if (message.isSuccess()){
flag[0] =true;
}
LOGIN_WAIT.countDown();
}
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();
}
}
}
3.总结
好了这就是具体的实现了,有什么不对或者可以跟好的地方可以评论指出。