目录
1、定义类成员变量 ChannelGroup cg,用于保存所有客户端的 Channel。
2、在 handlerAdded() 方法中,当有新客户端连接时,向所有已连接的客户端发送提示信息,并将新客户端的 Channel 添加到 cg 中。
3、在 channelActive() 方法中,当客户端上线时,向所有已连接的客户端发送提示信息。
4、在 channelRead0() 方法中,当接收到客户端发送的消息时,向所有已连接的客户端发送消息。
5、在 handlerRemoved() 方法中,当客户端连接断开时,从 cg 中移除该客户端的 Channel。
6、在 channelInactive() 方法中,当客户端下线时,向所有已连接的客户端发送提示信息。
2、连接到指定的服务器,并返回 ChannelFuture 对象
3、获取连接成功后的 Channel 对象,并通过 Scanner 从控制台获取用户输入的消息,将其发送给服务器
前言
Netty 是一个高性能、异步事件驱动的网络应用程序框架,主要用于开发高性能、高可靠性的网络服务器和客户端。Netty 支持多种传输协议和编解码技术,如 TCP、UDP、HTTP、WebSocket、SSL 等,并且提供了简单易用的 API 接口,使得开发者可以轻松地构建各种网络应用程序。
一、创建服务端
1、创建 ServerBootstrap 对象
ServerBootstrap bootstrap = new ServerBootstrap();
创建一个 ServerBootstrap 对象,该对象是 Netty 中用于启动服务器的入口类。
2、创建 boss 和 work 线程池
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup work = new NioEventLoopGroup(5);
在 Netty 中,通常会创建两个 EventLoopGroup,一个用于接收客户端连接(boss),另一个用于处理客户端请求(work)。其中,boss 线程池的大小通常为 1,work 线程池的大小根据服务器的性能和负载情况来确定。
3、组合 netty 组件
bootstrap.group(boss, work);
将 boss 和 work 线程池组合到 ServerBootstrap 对象中。
4、配置 handle 组件
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("encoder",new StringEncoder());
ch.pipeline().addLast("decoder",new StringDecoder());
ch.pipeline().addLast(new ServerChanelHandle());
}
});
在这里,我们使用了匿名内部类的方式创建了一个 ChannelInitializer 对象,并通过 addLast 方法添加了三个 handler 组件:StringEncoder、StringDecoder 和自定义的 ServerChanelHandle。其中,StringEncoder 和 StringDecoder 是 Netty 内置的编码和解码器,用于将字节流转换成字符串。而 ServerChanelHandle 则是我们自己实现的 handler 组件,用于处理客户端请求。
指定使用 NioServerSocketChannel 类作为服务器的 Channel 实现
bootstrap.channel(NioServerSocketChannel.class);
指定使用 NioServerSocketChannel 类作为服务器的 Channel 实现,该类是 Netty 中用于创建 TCP 服务器的通道类型。
5、绑定端口并启动服务器
ChannelFuture channel = bootstrap.bind(port).sync();
System.out.println(("服务端已启动,绑定端口:" + port)); channel.channel().closeFuture().sync();
调用 bind 方法绑定服务器的端口,并启动服务器。在这里,我们使用了 sync 方法来等待服务器启动完成。一旦服务器启动完成,我们就可以输出一条消息,告诉用户服务器已经启动,并通过 closeFuture 方法来等待服务器关闭。
6、关闭线程池
boss.shutdownGracefully(); work.shutdownGracefully();
在服务器关闭之后,我们需要手动关闭 boss 和 work 线程池,以释放资源
完整代码
public class ChatServer {
public void openServer(int port){
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup(1); //create boss group, threadpool size is 1
EventLoopGroup work = new NioEventLoopGroup(5); //create work group, threadpool size is 5
bootstrap.group(boss,work); //组合netty组件
//配置handle组件
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("encoder",new StringEncoder());
ch.pipeline().addLast("decoder",new StringDecoder());
ch.pipeline().addLast(new ServerChanelHandle());
}
});
bootstrap.channel(NioServerSocketChannel.class);
try{
ChannelFuture channel = bootstrap.bind(port).sync();
System.out.println(("服务端已启动,绑定端口:" + port));
channel.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
public static void main(String[] args){
ChatServer server = new ChatServer();
server.openServer(8090);
}
}
二、创建ServerChanelHandle类
1、定义类成员变量 ChannelGroup cg,用于保存所有客户端的 Channel。
public static ChannelGroup cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
2、在 handlerAdded() 方法中,当有新客户端连接时,向所有已连接的客户端发送提示信息,并将新客户端的 Channel 添加到 cg 中。
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel ch = ctx.channel();
for (Channel chanel : cg) {
chanel.writeAndFlush(ch.remoteAddress() + "进来啦!");
}
cg.add(ch);
}
3、在 channelActive() 方法中,当客户端上线时,向所有已连接的客户端发送提示信息。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
for (Channel ch : cg) {
ch.writeAndFlush(channel.remoteAddress() + "上线啦");
}
}
4、在 channelRead0() 方法中,当接收到客户端发送的消息时,向所有已连接的客户端发送消息。
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = ctx.channel();
for (Channel ch : cg) {
if (channel == ch) {
ch.writeAndFlush("我说" + msg);
} else {
ch.writeAndFlush(channel.remoteAddress() + "说:" + msg);
}
}
}
5、在 handlerRemoved() 方法中,当客户端连接断开时,从 cg 中移除该客户端的 Channel。
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
cg.remove(channel);
}
6、在 channelInactive() 方法中,当客户端下线时,向所有已连接的客户端发送提示信息。
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
String customerAddress = channel.remoteAddress().toString();
for (Channel ch : cg) {
ch.writeAndFlush("客户端" + customerAddress + "下线了!");
}
}
7、完整代码
public class ServerChanelHandle extends SimpleChannelInboundHandler {
//必须定义为类成员变量。每个客户端连接时,都会new ChatServerHandler。static保证数据共享
public static ChannelGroup cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel ch = ctx.channel();
for(Channel chanel: cg){
chanel.writeAndFlush(ch.remoteAddress()+"进来啦!");
}
cg.add(ch);
}
/**
* 上线处理
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
for(Channel ch: cg){
ch.writeAndFlush(channel.remoteAddress()+"上线啦");
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = ctx.channel();
for(Channel ch: cg){
if(channel ==ch){
ch.writeAndFlush("我说"+msg);
}else {
ch.writeAndFlush(channel.remoteAddress()+"说:"+msg);
}
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
cg.remove(channel);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
String customerAddress = channel.remoteAddress().toString();
for(Channel ch:cg){
ch.writeAndFlush("客户端" + customerAddress + "下线了!");
}
}
}
三、创建客户端
1、创建 Bootstrap 对象并设置相关参数
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup work = new NioEventLoopGroup(1);
bootstrap.group(work);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("encode",new StringEncoder());
ch.pipeline().addLast("decode",new StringDecoder());
ch.pipeline().addLast(new ClientChanelHandle());
}
});
bootstrap.channel(NioSocketChannel.class);
在这里,我们使用 Bootstrap 对象创建了一个 Netty 客户端,并设置了一个 NioEventLoopGroup 对象作为事件循环组。然后,我们通过 handler() 方法设置了一个 ChannelInitializer 对象,用于初始化 SocketChannel 中的通道处理器。其中,我们添加了 StringEncoder 和 StringDecoder 两个编解码器,以便在发送和接收数据时进行自动编解码。最后,我们添加了一个自定义的 ClientChanelHandle 处理器,用于处理接收到的消息。
2、连接到指定的服务器,并返回 ChannelFuture 对象
ChannelFuture channelFuture = bootstrap.connect(serverIP,port);
这里我们使用 connect() 方法连接到指定的服务器,传入服务器 IP 和端口号。这个方法是异步执行的,它会立即返回一个 ChannelFuture 对象,表示操作的异步结果。我们可以对该对象添加监听器来处理连接成功或失败的情况。
3、获取连接成功后的 Channel 对象,并通过 Scanner 从控制台获取用户输入的消息,将其发送给服务器
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String sendMsg = scanner.nextLine();
channel.writeAndFlush(sendMsg);
}
在这里,我们通过调用 channelFuture.channel() 方法来获取连接成功后的 Channel 对象。然后,我们使用 Scanner 从控制台获取用户输入的消息,并通过 writeAndFlush() 方法将其发送给服务器。这个方法也是异步执行的,它会将消息写入到底层的缓冲区中,并尝试将其发送给远程节点。如果发送成功,则会触发 writeComplete() 事件;否则,会触发 exceptionCaught() 事件。
4、关闭事件循环组,释放资源
work.shutdownGracefully();
最后,在程序退出时,我们需要手动关闭事件循环组,以便释放资源。这里我们调用 shutdownGracefully() 方法来安全地关闭事件循环组。该方法会等待所有线程优雅地停止,并释放相关资源。
5、完整代码
public class ChatClient implements Runnable{
private String serverIP;
private int port;
public ChatClient(String serverIp,int port ){
this.serverIP = serverIp;
this.port = port;
}
@Override
public void run() {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup work = new NioEventLoopGroup(1);
bootstrap.group(work);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("encode",new StringEncoder());
ch.pipeline().addLast("decode",new StringDecoder());
ch.pipeline().addLast(new ClientChanelHandle());
}
});
bootstrap.channel(NioSocketChannel.class);
ChannelFuture channelFuture = bootstrap.connect(serverIP,port);
Channel channel = channelFuture.channel();
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String sendMsg = scanner.nextLine();
channel.writeAndFlush(sendMsg);
}
work.shutdownGracefully();
}
public static void main(String[] args){
new Thread(new ChatClient("127.0.0.1",8090)).start();
}
四、创建ClientChanelHandle类
这是一个 Netty 客户端的处理器类,继承了 SimpleChannelInboundHandler 类。在 Netty 中,处理器类用于处理来自服务端的消息和客户端的请求,并将其转换为可读的格式。
channelRead0()
方法是该处理器类中的一个重要方法,它会在接收到来自服务端的消息时被调用,并将消息打印输出到控制台上。
public class ClientChanelHandle extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
}
}