場景
使用Netty實現簡單羣聊。服務端實現監控客戶端上下線及通知、羣聊消息轉發。
實現
客戶端與服務端使用String類型的消息進行發送與接收,因此客戶端與服務端需要首先添加Netty封裝的用於網絡傳輸的編碼解密處理器,否則將無法成功打印消息。
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//字符串解碼器,用於將通過ByteBuf傳輸的數據轉換成String
pipeline.addLast("decoder", new StringDecoder());
//字符串編碼器,用於將String編碼到ByteBuf中進行網絡傳輸
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast(new ServerHandler());
}
});
服務端
package others.netty.groupChat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
* Netty羣聊服務端實現
* 根據不同事件驅動通知客戶端 上下線狀態,消息轉發
*
* @author makeDoBetter
* @version 1.0
* @date 2021/4/25 10:36
* @since JDK 1.8
*/
public class Sever {
private int port;
public Sever(int port) {
this.port = port;
}
public void run(){
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap sever = new ServerBootstrap();
try {
sever.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE ,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//字符串解碼器,用於將通過ByteBuf傳輸的數據轉換成String
pipeline.addLast("decoder", new StringDecoder());
//字符串編碼器,用於將String編碼到ByteBuf中進行網絡傳輸
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast(new ServerHandler());
}
});
ChannelFuture channelFuture = sever.bind(port).sync();
//添加一個監聽器
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()){
System.out.println("服務端啓動完成");
} else {
System.out.println("服務端啓動失敗");
}
}
});
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new Sever(1234).run();
}
}
自定義服務處理器,用於處理客戶端連接與斷開、消息轉發等。
package others.netty.groupChat;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
* 實現客戶端上下線狀態、消息通知
*
* @author makeDoBetter
* @version 1.0
* @date 2021/4/25 10:47
* @since JDK 1.8
*/
public class ServerHandler extends SimpleChannelInboundHandler<String> {
/**
* 定義一個全局的單線程的靜態變量,用於存儲整個連接的客戶端集合
*/
private static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
//不需要遍歷,group可以直接處理
group.writeAndFlush("[客戶端]" + channel.remoteAddress() + "上線");
group.add(channel);
System.out.println(group.toString());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
//不需要手動remove()當前channel,group會自動剔除離線channel
group.writeAndFlush("[客戶端]" + channel.remoteAddress() + "下線");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel self = ctx.channel();
//實現消息的轉發,忽略自身
group.forEach(channel -> {
if (self != channel) {
System.out.println(msg + "發送到客戶端" + channel.remoteAddress());
channel.writeAndFlush("[客戶端]" + self.remoteAddress() + "說:" + msg + "\n");
}
});
}
}
客戶端
package others.netty.groupChat;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.util.Scanner;
/**
* 客戶端實現服務端連接,消息發送、接收打印
*
* @author makeDoBetter
* @version 1.0
* @date 2021/4/25 11:23
* @since JDK 1.8
*/
public class Client {
private String host;
private int port;
public Client(String host, int port) {
this.host = host;
this.port = port;
}
public void run(){
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//字符串解碼器,用於將通過ByteBuf傳輸的數據轉換成String
pipeline.addLast("decoder", new StringDecoder());
//字符串編碼器,用於將String編碼到ByteBuf中進行網絡傳輸
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast(new ClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()){
System.out.println("====連接成功=====");
} else {
System.out.println("客戶端連接失敗");
}
}
});
Channel channel = channelFuture.channel();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String line = scanner.nextLine();
channel.writeAndFlush(line);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new Client("127.0.0.1", 1234).run();
}
}
客戶端處理程序,打印轉發到當前客戶端的消息。
package others.netty.groupChat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* 客戶端讀取數據事件
*
* @author makeDoBetter
* @version 1.0
* @date 2021/4/25 11:37
* @since JDK 1.8
*/
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg.trim());
}
}