Netty是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。 ----摘自《Netty in action》
如果对于Java网络编程不是很熟悉的同学,在第一遍看书的时候,心里肯定是一个大写的懵 - -,所以老老实实的照着教程写了第一个netty的客户端和服务端,并思考了netty的核心组件。
netty的核心组件:
- Channel (渠道,出入站数据的载体)
- 回调
- Future
- 事件和ChannelHandler
新建项目
- 通过file -> new -> project -> maven
新建一个maven项目,不用选择原型,填写GroupId和ArtifactId,版本号默认。
-
通过file -> new -> Module 新建客户端模块
GroupId和version是默认的,artifactId可以自定义。
同样的方式建立服务端模块
- 项目结构如下
添加依赖与maven插件
- 主pom.xml文件(最外部的)
这里加了两个插件,一个是maven-compiler-plugin,主要是用在项目编译、打包的时候。一个是exe-maven-plugin,主要是用来执行main函数。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.netty</groupId>
<artifactId>nettyJoin</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>nettyClient</module>
<module>nettyServer</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.13.Final</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<!-- put your configurations here -->
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- nettyClient的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nettyJoin</artifactId>
<groupId>com.netty</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.netty</groupId>
<artifactId>nettyClient</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
</dependencies>
</project>
- nettyServer的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nettyJoin</artifactId>
<groupId>com.netty</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.netty</groupId>
<artifactId>nettyServer</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
</dependencies>
</project>
注:jdk、maven什么的环境变量这里就不累赘了
编写客户端代码
注:代码都是参考《netty in action》中的例子
- 处理器EchoClientHandler
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext,
ByteBuf byteBuf) throws Exception {
//记录已接收消息的转储
System.out.println("Client received: " + byteBuf.toString(CharsetUtil.UTF_8));
}
/**
* 作为一个回调函数
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当被通知Channel是活跃的时候,发送一条消息
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- main函数
public class EchoClient {
private int port;
private String host;
public EchoClient(int port, String host) {
this.port = port;
this.host = host;
}
public void start() throws InterruptedException {
//进行事件处理分配,包括创建新的连接以及处理入站和出站数据
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建Bootstrap 初始化客户端
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel)
throws Exception {
socketChannel.pipeline().addLast(new EchoClientHandler());
}
});
//连接到远程节点,阻塞等待直到连接完成
ChannelFuture f = b.connect().sync();
//阻塞,直到channel关闭
f.channel().closeFuture().sync();
} finally {
//关闭线程池并且释放所有的资源
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException {
if (args.length != 2) {
System.err.println("Usages: " + EchoClient.class.getSimpleName() + "<host><port>");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(port, host).start();
}
}
编写服务端代码
- 处理器EchoServerHandler
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
private Logger logger = Logger.getLogger("com.EchoServerHandler");
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
//将接收到的消息写给发送者,而不冲刷出站消息
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将未决消息冲刷到远程节点,并且关闭该channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- main函数
public class EchoServer {
private int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
if (args.length != 1) {
System.err.println("Usages: " + EchoServer.class.getSimpleName() + "<port>");
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
/**
* 启动方法
*
* @throws InterruptedException
*/
public void start() throws InterruptedException {
final EchoServerHandler serverHandler = new EchoServerHandler();
//1创建EventLoopGroup 用来接收和处理新的连接
EventLoopGroup group = new NioEventLoopGroup();
try {
//2 创建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//3 制定所使用的NIO传输channel
.channel(NioServerSocketChannel.class)
//4 使用制定的端口设置套接字地址
.localAddress(new InetSocketAddress((port)))
//5 添加一个EchoServerHandler到子channel的ChannelPipeLine
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel)
throws Exception {
//EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
socketChannel.pipeline().addLast(serverHandler);
}
});
//6 异地绑定服务器;调用sync方法阻塞等待直到绑定完成
ChannelFuture f = b.bind().sync();
//7 获取channel的closeFuture,并且阻塞当前线程直到它完成
f.channel().closeFuture().sync();
} finally {
//8 关闭EventLoopGroup释放所有的资源
group.shutdownGracefully().sync();
}
}
}
目录结构
运行项目
现在本地将客户端和服务端的代码打包,进入两个模块的根目录,运行mvn clean package即可。
-
项目打包
- 运行服务端代码
进入服务端模块的根目录,运行下面的命令,后面跟的是main函数和入参
mvn exec:java -Dexec.mainClass="com.EchoServer" -Dexec.args="9999"
服务器启动完毕,并等待连接。
- 启动客户端
进入客户端模块的根目录,运行下面的命令,后面跟的是main函数和入参
mvn exec:java -Dexec.mainClass="com.EchoClient" -Dexec.args="127.0.0.1 9999"
图中右边的是客户端命令行,可以看到已经打印出“Client received: Netty rocks!”,服务端也正常接收到消息,并打印出了“Server received: Netty rocks!”。
小结
可能在代码编写过程中,或是编译打包的过程中,都会碰到各种错误,因为大家的运行环境都各不相同,碰到问题的同学可以在下面留言,也可以自行百度。
这也是我第一次接触netty,才看了书的前两章,接下来也会把自己的总结、感悟写在netty上。