2021SC@SDUSC
一、前言
在学习的第一个礼拜,先完成第二个目标,即使用netty编写服务端和客户端的程序,实现服务端和客户端的双向通信。当然,会在之后的学习过程中逐步完善。
二、服务端代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;
import io.netty.util.CharsetUtil;
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 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 SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress() + ": " +msg);
//将读取到的消息回写给客户端
ctx.writeAndFlush("服务器收到消息: " + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接已建立,来自" + ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("出现异常: " + cause.getMessage());
//出现异常,关闭连接
ctx.channel().close();
}
});
}
});
//为服务器绑定端口,同步等待
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();
}
}
三、客户端代码
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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 io.netty.util.CharsetUtil;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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 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 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();
}
});
}
});
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();
}
}
四、总结
使用netty框架完成服务端对客户端发送消息的读取并且回写,初步完成了服务端与客户端的通信,之后预计将代码改为多个客户端的群聊系统。
此外,代码中存在的一个问题是客户端的退出问题,目前是强制关闭,之后会成其他方式。
之后的几篇博客,会就服务端的代码,分析netty启动流程的细节。