2021SC@SDUSC
文章目录
前言
在之前的博客netty初步使用中,简单地编写了客户端与服务端的代码,实现了双向通信,这次,在上次的代码的基础上做了些改进,初步实现了聊天室的功能。
一、服务端
1、Server
Server类本身没有太多的变化,只是关于ServerInitializer的添加方式,由原本的内部类的方式,变成了new的方式,就是为了方便管理,将ServerInitializer类独立出来。
package com.homework.server_client.server;
import com.homework.server_client.server.handler.ServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
private final int PORT;
public Server(int port) {
this.PORT = port;
}
public void run() {
//创建两个线程池
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try {
//netty的引导程序
ServerBootstrap bootstrap = new ServerBootstrap();
//设置parentGroup和childGroup
bootstrap.group(bossGroup, workerGroup)
//指定Channel类型
.channel(NioServerSocketChannel.class)
//设置队列中最大连接数
.option(ChannelOption.SO_BACKLOG, 128)
//监控连接是否有效
.childOption(ChannelOption.SO_KEEPALIVE, true)
//客户端连接成功后,添加handler,注意ChannelInitilizer在注册成功后,会将自己删除
.childHandler(new ServerInitializer());
//为服务器绑定端口,同步等待
ChannelFuture future = bootstrap.bind(PORT).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new Server(8080).run();
}
}
2、ServerInitializer
依然是为了便于管理,将ServerHandler类独立编写,而不是采用内部类的方式编写。ServerInitializer类本身改动不多。
package com.homework.server_client.server.handler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
//添加编解码器
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
//自定义handler
pipeline.addLast(new ServerHandler());
}
}
3、ServerHandler
通过GROUP变量管理所有的通道,其类型为Set<Channel>,通过这种方式实现了消息的群发。由于HashSet类并不保证同步,通过ReentrantLock保证线程安全性。此外,只实现了消息的群发机制,而没有实现单独发送。
package com.homework.server_client.server.handler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ServerHandler extends SimpleChannelInboundHandler<String> {
/**
* 定义一个Channel组,管理所有Channel
*/
private static final Set<Channel> GROUP = new HashSet<>();
private final Lock LOCK = new ReentrantLock();
/**
* 表示连接建立,第一个被执行,将当前Channel加入到group
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
Lock lock = this.LOCK;
lock.lock();
try {
// 将channel加入的消息推送给其它channel
this.writeAndFlush(channel.remoteAddress() + "加入聊天室。");
GROUP.add(channel);
System.out.println(" 当前人数: " + GROUP.size());
} finally {
lock.unlock();
}
}
/**
* channel断开连接
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
Lock lock = this.LOCK;
lock.lock();
try {
// 将channel离开的消息推送给其它channel
GROUP.remove(channel);
this.writeAndFlush(channel.remoteAddress() + "离开聊天室。");
System.out.println(" 当前人数: " + GROUP.size());
} finally {
lock.unlock();
}
}
/**
* 重写channelRead0方法,读取消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel channel = ctx.channel();
Lock lock = this.LOCK;
lock.lock();
try {
for (Channel ch : GROUP) {
if (ch != channel) {
ch.writeAndFlush("来自" + channel.remoteAddress() + "的消息: " + msg);
} else {
ch.writeAndFlush("已经发送: " + msg);
}
}
} finally {
lock.unlock();
}
}
/**
* 捕捉到异常,关闭channel
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
private void writeAndFlush(String msg) {
for (Channel channel : GROUP) {
channel.writeAndFlush(msg);
}
}
}
二、客户端
1、Client
Client中原本的发送消息的功能转移到了ClientHandler类中,其他方面,改动与Server类似。
package com.homework.server_client.client;
import com.homework.server_client.client.handler.ClientInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
private static final int PORT = 8080;
private static final String HOST = "127.0.0.1";
public Client() {}
public void run() {
//创建线程池,注意,与服务端不同,客户端只需要一个线程池
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
//指定Channel类型
.channel(NioSocketChannel.class)
.handler(new ClientInitializer());
ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException exception) {
exception.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new Client().run();
}
}
2、ClientInitializer
ClientInitializer的改动与ServerInitializer类似。
package com.homework.server_client.client.handler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new ClientHandler());
}
}
3、ClientHandler
最后是ClientHandler类,增加了channelActive方法,就是创建一个线程不停地接受输入的消息,以换行符为界,并且发送给服务器。
package com.homework.server_client.client.handler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ClientHandler extends SimpleChannelInboundHandler<String> {
//将服务器发送的消息打印
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//IO事件,创建其它线程来处理
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String message = scanner.nextLine();
ctx.writeAndFlush(message);
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getMessage());
ctx.channel().closeFuture().sync();
}
}
总结
基于之前的代码,简单地使用netty实现了聊天室功能,但是,实现的功能相对较少,只有消息的群发功能,没有消息地单独发送功能,此外,任何人都能够加入聊天室,没有权限控制,且聊天室本身只有一个,所有人加入后都是同一个聊天室。总之,基本上只是学习netty时写的简单demo。