文章已获得作者授权
MINA、Netty、Twisted为什么放在一起学习?首先,不妨先分别看一下它们官方网站对其的介绍。
MINA:
Apache MINA is a network application framework which helps users develop high performance and high scalability network applications easily. It provides an abstract event-driven asynchronous API over various transports such as TCP/IP and UDP/IP via Java NIO.
Netty:
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
Twisted:
Twisted is an event-driven networking engine written in Python and licensed under the open source MIT license.
Twisted 官从上面简短的介绍中,就可以发现它们的共同特点:event-driven 以及 asynchronous。它们都是事件驱动、异步的网络编程框架。由此可见,它们之间的共同点还是很明显的。所以我这里将这三个框架放在一起,实现相同的功能,不但可以用少量的精力学三样东西,而且还可以对它们之间进行各方面的对比。网的文案不专业啊,居然不写 asynchronous。
从上面简短的介绍中,就可以发现它们的共同特点:event-driven 以及 asynchronous。它们都是事件驱动、异步的网络编程框架。由此可见,它们之间的共同点还是很明显的。所以我这里将这三个框架放在一起,实现相同的功能,不但可以用少量的精力学三样东西,而且还可以对它们之间进行各方面的对比。
其中 MINA 和 Netty 是基于 Java 语言的,而 Twisted 是 Python 语言的。不过语言不是重点,重点的是理念。
使用传统的 BIO(Blocking IO/阻塞IO)进行网络编程时,进行网络 IO 读写时都会阻塞当前线程,如果实现一个 TCP 服务器,都需要对每个客户端连接开启一个线程,而很多线程可能都在傻傻的阻塞住等待读写数据,系统资源消耗大。
而 NIO(Non-Blocking IO/非阻塞IO)或 AIO(Asynchronous IO/异步IO)则是通过 IO 多路复用技术实现,不需要为每个连接创建一个线程,其底层实现是通过操作系统的一些特性如 select、poll、epoll、iocp 等。这三个网络框架都是基于此实现。
下面分别用这三个框架实现一个最简单的 TCP 服务器。当接收到客户端发过来的字符串后,向客户端回写一个字符串作为响应。
Netty:
public class TcpServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new TcpServerHandler());
}
});
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
class TcpServerHandler extends ChannelInboundHandlerAdapter {
// 接收到新的数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
try {
// 接收客户端的数据
ByteBuf in = (ByteBuf) msg;
System.out.println("channelRead:" + in.toString(CharsetUtil.UTF_8));
// 发送到客户端
byte[] responseByteArray = "你好".getBytes("UTF-8");
ByteBuf out = ctx.alloc().buffer(responseByteArray.length);
out.writeBytes(responseByteArray);
ctx.writeAndFlush(out);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
@Override
public void channelInactive(ChannelHandlerContext ctx){
System.out.println("channelInactive");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
MINA:
public class TcpServer {
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.setHandler(new TcpServerHandle());
acceptor.bind(new InetSocketAddress(8080));
}
}
class TcpServerHandle extends IoHandlerAdapter {
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
cause.printStackTrace();
}
// 接收到新的数据
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
// 接收客户端的数据
IoBuffer ioBuffer = (IoBuffer) message;
byte[] byteArray = new byte[ioBuffer.limit()];
ioBuffer.get(byteArray, 0, ioBuffer.limit());
System.out.println("messageReceived:" + new String(byteArray, "UTF-8"));
// 发送到客户端
byte[] responseByteArray = "你好".getBytes("UTF-8");
IoBuffer responseIoBuffer = IoBuffer.allocate(responseByteArray.length);
responseIoBuffer.put(responseByteArray);
responseIoBuffer.flip();
session.write(responseIoBuffer);
}
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("sessionCreated");
}
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("sessionClosed");
}
}
Twisted:
# -*- coding:utf-8 –*-
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet import reactor
class TcpServerHandle(Protocol):
# 新的连接建立
def connectionMade(self):
print 'connectionMade'
# 连接断开
def connectionLost(self, reason):
print 'connectionLost'
# 接收到新数据
def dataReceived(self, data):
print 'dataReceived', data
self.transport.write('你好')
factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()
从上面的代码可以看出,这三个框架实现的 TCP 服务器,在连接建立、接收到客户端传来的数据、连接关闭时,都会触发某个事件。例如接收到客户端传来的数据时,MINA 会触发事件调用 messageReceived,Netty 会调用 channelRead,Twisted 会调用 dataReceived。编写代码时,只需要继承一个类并重写响应的方法即可。这就是 event-driven 事件驱动。
下面是 Java 写的一个 TCP 客户端用作测试,客户端没有使用这三个框架,也没有使用 NIO,只是一个普通的 BIO 的 TCP 客户端。
TCP 在建立连接到关闭连接的过程中,可以多次进行发送和接收数据。下面的客户端发送了两个字符串到服务器并两次获取服务器回应的数据,之间通过 Thread.sleep(5000) 间隔 5 秒。
public class TcpClient {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = null;
OutputStream out = null;
InputStream in = null;
try{
socket = new Socket("localhost", 8080);
out = socket.getOutputStream();
in = socket.getInputStream();
// 请求服务器
out.write("第一次请求".getBytes("UTF-8"));
out.flush();
// 获取服务器响应,输出
byte[] byteArray = new byte[1024];
int length = in.read(byteArray);
System.out.println(new String(byteArray, 0, length, "UTF-8"));
Thread.sleep(5000);
// 再次请求服务器
out.write("第二次请求".getBytes("UTF-8"));
out.flush();
// 再次获取服务器响应,输出
byteArray = new byte[1024];
length = in.read(byteArray);
System.out.println(new String(byteArray, 0, length, "UTF-8"));
} finally {
// 关闭连接
in.close();
out.close();
socket.close();
}
}
}
用客户端分别测试上面三个 TCP 服务器,看一下结果如何。
MINA服务器输出结果:
sessionCreated
messageReceived:第一次请求
messageReceived:第二次请求
sessionClosed
Netty服务器输出结果:
channelActive
channelRead:第一次请求
channelRead:第二次请求
channelInactive
Twisted服务器输出结果:
connectionMade
dataReceived: 第一次请求
dataReceived: 第二次请求
connectionLost