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 anasynchronous event-drivennetwork application frameworkfor rapid development of maintainable high performance protocol servers & clients.
Twisted:
Twisted is anevent-drivennetworking enginewritten in Python and licensed under the open source MIT license.
(Twisted官網的文案不專業啊,居然不寫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服務器。當接收到客戶端發過來的字符串后,向客戶端回寫一個字符串作為響應。
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"); } }
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(); } }
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服務器輸出結果:
sessionCreatedmessageReceived:第一次請求messageReceived:第二次請求sessionClosed
Netty服務器輸出結果:
channelActivechannelRead:第一次請求channelRead:第二次請求channelInactive
Twisted服務器輸出結果:
connectionMadedataReceived: 第一次請求dataReceived: 第二次請求connectionLost
新聞熱點
疑難解答