您好,登錄后才能下訂單哦!
文章已獲得作者授權
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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。