实验目的
实现一个 echo server,逐步让它的功能能够完成双端的对话。
什么是echo server?
Echo Server 是一种允许客户端和服务器连接的应用程序,因此客户端可以向服务器发送消息,服务器可以接收消息并将其发送或回显给客户端。
实验环境
创建一个maven项目
jdk版本:jdk8
Netty依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.67.Final</version>
</dependency>
日志依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
Start
先写一个最简单的 hello world?
hello world
Server
/**
* @author pixel-revolve
* @date 2022/4/5
*/
@Slf4j
public class Server {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//添加入站处理器
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf=(ByteBuf)msg;
log.debug(byteBuf.toString(Charset.defaultCharset()));
super.channelRead(ctx, msg);
}
});
}
})
.bind(8080);
}
}
建立服务器,绑定8080端口,添加入站处理器,并且监听读事件。
Client
/**
* @author pixel-revolve
* @date 2022/4/5
*/
public class Client {
public static void main(String[] args) throws InterruptedException {
Channel channel=new Bootstrap()
//添加 EventLoop
.group(new NioEventLoopGroup())
//选择客户端的channel实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
//在连接建立后被调用
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new StringEncoder());
}
})
//连接服务器
.connect(new InetSocketAddress("localhost",8080))
.sync()
.channel();
channel.writeAndFlush("hello world!");
}
}
和服务器建立起连接,在channel中写入并刷新字符串 "hello world!"
启动服务器和客户端,并在服务器端的日志中成功看到客户端写入的 "hello world!"
现在还是单端的通信(客户端向服务端),而且客户端还不能自由的发送信息。我们接着在客户端的主线程中创建一个新的线程负责客户端向服务器的信息发送。
创建一个线程负责客户端向服务器的信息发送
改进后的客户端
public class Client {
public static void main(String[] args) throws InterruptedException {
Channel channel=new Bootstrap()
//添加 EventLoop
.group(new NioEventLoopGroup())
//选择客户端的channel实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
//在连接建立后被调用
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new StringEncoder());
}
})
//连接服务器
.connect(new InetSocketAddress("localhost",8080))
.sync()
.channel();
new Thread(()->{
Scanner scanner = new Scanner(System.in);
while(true){
String line = scanner.nextLine();
if(line.equals("q")){
channel.close();
break;
}
channel.writeAndFlush(line);
}
},"input").start();
}
}
客户端扮演Chris Rock发送台词
现在的客户端已经能够自由的发送消息了,我们让它扮演Chris Rock发送台词。
在服务器端看到Chris Rock发送过来的台词。
现在就差我们的Will Smith上台了!
分析
现在的服务端只能够接收消息而不能发送消息。接着是客户端,客户端现在能够发送消息,但是并不能接收消息。接收和回应必须双端都能完成,这样才是正常的人类对话的模型。
完善客户端和服务端的欠缺功能
服务端完善发送信息功能
/**
* @author pixel-revolve
* @date 2022/4/5
*/
@Slf4j(topic = "ChrisRock")
public class Server {
public static void main(String[] args) throws InterruptedException {
Scanner scanner=new Scanner(System.in);
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
//添加入站处理器
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
log.debug(byteBuf.toString(Charset.defaultCharset()));
ByteBuf response = ctx.alloc().buffer();
String line = scanner.nextLine();
response.writeBytes(line.getBytes());
ctx.writeAndFlush(response);
super.channelRead(ctx, msg);
}
});
}
})
.bind(8080);
}
}
我们在read事件的每次获取消息之后阻塞住,并且通过scanner输入回应的消息给客户端,以让服务端实现对话的形式。
客户端完善接收消息功能
/**
* @author pixel-revolve
* @date 2022/4/5
*/
@Slf4j(topic = "WillSmith")
public class Client {
public static void main(String[] args) throws InterruptedException {
Channel channel=new Bootstrap()
//添加 EventLoop
.group(new NioEventLoopGroup())
//选择客户端的channel实现
.channel(NioSocketChannel.class)
//添加处理器
.handler(new ChannelInitializer<NioSocketChannel>() {
//在连接建立后被调用
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new StringEncoder());
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf=(ByteBuf) msg;
log.debug(byteBuf.toString(Charset.defaultCharset()));
super.channelRead(ctx, msg);
}
});
}
})
//连接服务器
.connect(new InetSocketAddress("localhost",8080))
.sync()
.channel();
new Thread(()->{
Scanner scanner = new Scanner(System.in);
while(true){
String line = scanner.nextLine();
if(line.equals("q")){
channel.close();
break;
}
channel.writeAndFlush(line);
}
},"ChrisRockSay").start();
}
}
客户端这里接收消息和服务端实现一样,添加入站处理器,实现channelRead方法。打印服务端的消息,并且调用自己的第二个线程发送消息。
奥斯卡颁奖开始!
好的 现在我们开始奥斯卡颁奖!
Chris Rock视角
Will Smith视角
原文
Chris Rock:wow! Will Smith just smacked the [_] out of me
Will Smith:keep my wife's name out your [_] mouth!
Chris Rock:wow dude, it was a g.i jane joke
Will Smith:keep my wife's name out your [_] mouth!
Chris Rock:i'm going to okay
Chris Rock:that was a greatest night in the history of television okay
总结
这次实验我们通过netty实现了一个简单的 echo server,当然它还有很多的不足之处,比如它还不能做到像qq一样的全双工的聊天。当然这些问题都将在以后的实验中完善并且解决!