目录
一、介绍
本章节主要讲第三部分,涉及客户端的实现,主要包括客户端进行登录、私聊、群聊。
如果你还没有看过第一部分,那么请通过下面这篇文章进行学习:
https://blog.csdn.net/Staba/article/details/135057482
如果你还没有看过第二部分,那么请通过下面这篇文章进行学习:
https://blog.csdn.net/Staba/article/details/135061442
二、结构
三、依赖
此处为了节省麻烦,通过http接口调用去进行登录、发送私聊消息、发送群聊消息,所以引入了spring-boot-web-starter
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.101.Final</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>chat-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
四、Response消息处理Handler实现
1、BaseHandler,所有Handler的父类
public abstract class BaseHandler<T> extends SimpleChannelInboundHandler<T> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端进行连接, channel: " + ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端断开连接, channel: " + ctx.channel());
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("出现异常断开, channel: " + ctx.channel());
cause.printStackTrace();
// todo, 清除相关数据
}
}
2、LoginResponseHandler,用于接收登录返回消息
public class LoginResponseHandler extends BaseHandler<LoginResponse> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginResponse msg) throws Exception {
System.out.println("[LoginResponseHandler]读取到服务端消息, channel: " + ctx.channel());
System.out.println("消息是: " + msg);
}
}
3、SingleMessageResponseHandler,用于接收单聊消息
public class SingleMessageResponseHandler extends BaseHandler<SingleMessageResponse> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, SingleMessageResponse msg) throws Exception {
System.out.println("[SingleMessageResponseHandler]读取到服务端消息, channel: " + ctx.channel());
System.out.println("消息是: " + msg);
}
}
4、GroupMessageResponseHandler,用于接收群聊消息
public class GroupMessageResponseHandler extends BaseHandler<GroupMessageResponse> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, GroupMessageResponse msg) throws Exception {
System.out.println("[GroupMessageResponseHandler]读取到服务端消息, channel: " + ctx.channel());
System.out.println("消息是: " + msg);
}
}
五、ClientChannelInitializer实现
该类用于初始化Server与Client通信的Channel,需要将我们前面写的编解码器以及ResponseHandler添加进pipeline
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new MessageEncode());
pipeline.addLast(new MessageDecode());
pipeline.addLast(new LoginResponseHandler());
pipeline.addLast(new SingleMessageResponseHandler());
pipeline.addLast(new GroupMessageResponseHandler());
}
}
六、ChatClient实现
public class ChatClient {
public Channel connect(String ip, Integer port) {
EventLoopGroup workGroup = new NioEventLoopGroup();
Channel channel = null;
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.AUTO_READ, true)
.handler(new ClientChannelInitializer());
// 阻塞,等待连接建立
channel = bootstrap.connect(ip, port).sync().channel();
System.out.println("chatClient 启动...");
} catch (Exception e) {
e.printStackTrace();
}
return channel;
}
}
七、启动器下启动ChatClient
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
@Bean
public Channel init(){
return new ChatClient().connect("127.0.0.1", 8888);
}
}
八、编写登录、私聊、群聊接口
@RestController
public class ChatController {
@Resource
private Channel channel;
private String username;
@GetMapping("/login")
public void login(String username) {
this.username = username;
LoginRequest loginRequest = new LoginRequest(username);
channel.writeAndFlush(loginRequest);
}
@GetMapping("/send/single")
public void sendSingleMessage(String sendTo, String content) {
SingleMessageRequest singleMessageRequest = new SingleMessageRequest(this.username, sendTo, content);
channel.writeAndFlush(singleMessageRequest);
}
@GetMapping("/send/group")
public void sendGroupMessage(String group, String content) {
GroupMessageRequest groupMessageRequest = new GroupMessageRequest(this.username, group, content);
channel.writeAndFlush(groupMessageRequest);
}
}
九、测试登录、私聊、群聊
1、启动客户端
分别启动三个客户端,端口分别为8081、8082、8083
2、登录
分别调用一下http接口进行模拟登录
http://localhost:8081/login?username=001
http://localhost:8082/login?username=002
http://localhost:8083/login?username=003
在各自客户端控制台看到以下输出即可:
[LoginResponseHandler]读取到服务端消息, channel: [id: 0xb7a9fb1b, L:/127.0.0.1:58376 - R:/127.0.0.1:8888]
消息是: LoginResponse(result=true, message=登录成功!)
3、私聊
调用以下接口,模拟username为001的用户向002用户发送消息
http://localhost:8081/send/single?sendTo=002&content=nihao
可以在端口为8082服务的控制台上看到如下信息,其他服务看不到即可:
[SingleMessageResponseHandler]读取到服务端消息, channel: [id: 0xfc720170, L:/127.0.0.1:58340 - R:/127.0.0.1:8888]
消息是: SingleMessageResponse(sendFrom=001, content=nihao)
4、群聊
调用以下接口,模拟username为001的用户想group2的群组发送消息
http://localhost:8081/send/group?group=group2&content=nihao
可以在端口为8082和8083服务的控制台上看到如下信息:
[GroupMessageResponseHandler]读取到服务端消息, channel: [id: 0xfc720170, L:/127.0.0.1:58340 - R:/127.0.0.1:8888]
消息是: GroupMessageResponse(sendFrom=001, group=group2, content=nihao)
至此,一个简单的私聊,群聊实现完毕