导读
(1) 书写一个Netty网络服务器,一般需要4个文件
- EchoServer : 服务端启动类
- EchoServerHandler:服务端处理类
- EchoClient: 客户端启动类
- EchoClientHandler:客户端处理类
(2) 内容概括:
- 本章节侧重于将Handler的用法,至于启动类,下一章节会详细的将每一行语句
(3) 开发工具
- 若导入到eclipse,直接右键-》main运行即可
(4) 代码下载
git->echo为本节
(5) 实现代码:
- 客户端建立连接之后发送“hello wenguang”到服务端
- 服务端打印到终端并且将代码返回
- 客户端将收到的代码打印出来
项目代码
- EchoServer
(1) EchoSevre
package com.qinwenguang;
import java.net.InetSocketAddress;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class EchoServer {
private int port;
public EchoServer(int port) {
super();
this.port = port;
}
//程序主要启动流程
public void start() throws InterruptedException {
final EchoServerHandler handler = new EchoServerHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group).channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//绑定入站事件
ch.pipeline().addLast(handler);
}
});
ChannelFuture future = b.bind().sync();
System.out.println("start on port: " + port);
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) {
try {
new EchoServer(17746).start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(2) EchoServerHandler
package com.qinwenguang;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
class EchoServerHandler extends ChannelInboundHandlerAdapter {
//有数据读取的时候调用
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(
"Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
//本次读取完成调用
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
- EchoClient
1)
package com.qinwenguang;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
public class EchoClient {
private String host;
private int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
/**程序绑定流程*/
public void start()
throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast( new EchoClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args)
throws Exception {
new EchoClient("127.0.0.1", 17746).start();
}
}
(2) EchoClientHandler
package com.qinwenguang;
import java.io.UnsupportedEncodingException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
@Sharable
public class EchoClientHandler
extends SimpleChannelInboundHandler {
@Override
public void channelActive(ChannelHandlerContext ctx) throws UnsupportedEncodingException {
ByteBuf buff = Unpooled.buffer();
buff.writeBytes("Netty rocks!".getBytes("UTF-8"));
ctx.writeAndFlush(buff);
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
System.out.println( "Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
- 运行结果
(1) server
mvn compile
mvn exec:java -Dexec.mainClass="com.qinwenguang.EchoServer"
(2) client
mvn compile
mvn exec:java -Dexec.mainClass="com.qinwenguang.EchoClient"
Handler解析
- handler部分类关系
(1) 涉及的类
- ChannelHandlerContext: Channel之间交换数据使用
- ChannelInboundHandlerAdapter:提供了很多方法对入站数据进行操作
- ByteBuf: 缓冲区:包含要读取的内容(读取和写入可以使用一个缓冲区)
(2) 动作
- 对于入栈事件:直接读取ByteBuf操作即可(netty已经封装好一切)
- 对于每一次客户端向服务端发送请求,服务端都会调用channelRead() channelReadComplete()两个方法
(3) 服务端输出
ctx.write(buff): 将数据刷新到channel上下文中
ctx. flush() 刷新后才将数据发出到SocketChannel
ctx.close() 显式调用关闭之后, 连接才会断开,否则一直连接,而不管是否有数据发送
- serverHandler方法解析
(1) channelRead: 从当前Channel的对端读取消息
/*服务端从客户端中读取消息*/
public void channelRead(ChannelHandlerContext ctx, java.lang.Object msg)
throws java.lang.Exception;
- msg为读取缓冲区, 需要强制转为ByteBuf
(2) channelReadComplete:本次读取完毕有执行时执行
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception
- 本方法常常用于本次刷新缓冲区和关闭连接使用
//显式关闭,否则两者连接仍然保持
ctx.close()
(3) exceptionCaught: 重写父类ChannelHandler的方法,处理异常.
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.out.println("server exceptionCaught..");
ctx.close();
}
- 此方法服务端和客户端都必须调用
- ClientHandler
(1) channelActive: 连接可用的时候执行
public void channelActive(ChannelHandlerContext ctx)
- 一般在此方法中客户端连接上服务端后向服务端发送请求
- 也可以在此方法中client连接上server后, Server像客户端发送数据
4.ByteBuf
(1) API
io.netty.buffer
Class ByteBuf
/*Returns the number of readable bytes which is equal to (this.writerIndex - this.readerIndex).*/
public abstract int readableBytes()
/*Decodes this buffer's readable bytes into a string with the specified character set name+*/
public abstract java.lang.String toString(java.nio.charset.Charset charset);
(2) 新建ByteBuf
ByteBuf directBuffer = Unpooled.buffer();
System.out.println(heapBuffer);
ByteBuf directBuffer = Unpooled.directBuffer();
System.out.println(directBuffer);
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(new byte[•128]);
System.out.println(wrappedBuffer);
ByteBuf copiedBuffer = Unpooled.copiedBuffer(new byte[128]);
System.out.println(copiedBuffer);
(3) String -> ByteBuf
//新建ByteBuf
ByteBuf buff = Unpooled.buffer();
//写入数据
buff.writeBytes("Netty rocks!".getBytes("UTF-8"));
//写入和刷新
ctx.writeAndFlush(buff);
简单写法
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));
(4) ByetBuf -> String
buff.toString(CharsetUtil.UTF_8))
(5) ByteBuffer->byte
byte[] req = new byte[buf.readableBytes()]; /*创建字节数组的大小*/
buf.readBytes(req); /*读取内容到数组中*/
String body = new String(req, "UTF-8");
(6) byte -> ByteBuf
/*将byte数组写入Bytebuf中*/
resp.writeBytes("测试测试测试".getBytes());
(7) 什么时候需要新建ByteBuf
- 需要新建ByteBuf:channelActive()中
- 如果方法有传入ByteBuf的话,一般不需要新建