Netty、MINA、Twisted一起學系列01:實現簡單的TCP伺服器
文章已獲得作者授權
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
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31558358/viewspace-2295477/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Netty、MINA、Twisted一起學系列10:SSL / TLSNettyTLS
- Netty、MINA、Twisted一起學系列05:整合protobufNetty
- Netty、MINA、Twisted一起學系列03:TCP訊息固定大小的字首(Header)NettyTCPHeader
- Netty、MINA、Twisted一起學系列10:執行緒模型Netty執行緒模型
- Netty、MINA、Twisted一起學系列04:定製自己的協議Netty協議
- Netty、MINA、Twisted一起學系列02:TCP訊息邊界問題及按行分割訊息NettyTCP
- Netty(二) 實現簡單Http伺服器NettyHTTP伺服器
- netty 實現簡單的rpc呼叫NettyRPC
- netty搭建Tcp伺服器實踐NettyTCP伺服器
- tcp 實現簡單http 問題TCPHTTP
- netty系列之:使用netty實現支援http2的伺服器NettyHTTP伺服器
- Java使用Netty實現簡單的RPCJavaNettyRPC
- 簡單的Java實現Netty進行通訊JavaNetty
- python 實現 TCP 伺服器最簡流程PythonTCP伺服器
- Python使用TCP實現簡單對話PythonTCP
- 自己用 Netty 實現一個簡單的 RPCNettyRPC
- Java - Apache Mina 簡單示例JavaApache
- Python的學習(十四)---- 實現簡單的HTTP伺服器PythonHTTP伺服器
- netty系列之:EventExecutor,EventExecutorGroup和netty中的實現Netty
- netty系列之:Bootstrap,ServerBootstrap和netty中的實現NettybootServer
- netty系列之:channel,ServerChannel和netty中的實現NettyServer
- 簡單實現TCP下的大檔案高效傳輸TCP
- Netty-Mina深入學習與對比(二)Netty
- netty系列之:EventLoop,EventLoopGroup和netty的預設實現NettyOOP
- 跟我一起動手實現Tomcat(二):實現簡單的Servlet容器TomcatServlet
- 基於Netty的Android系統IM簡單實現原理NettyAndroid
- MessagePack 序列化框架在netty中的簡單實現。框架Netty
- 最簡單的web伺服器實現(一)Web伺服器
- netty系列之:netty實現http2中的流控制NettyHTTP
- netty系列之:小白福利!手把手教你做一個簡單的代理伺服器Netty伺服器
- 使用Netty和動態代理實現一個簡單的RPCNettyRPC
- 聊聊UDP、TCP和實現一個簡單的JAVA UDP小DemoUDPTCPJava
- TCP和UDP實現簡單一對一通訊TCPUDP
- 實現一個簡單的FTP伺服器(十四)FTP伺服器
- 建立一個Twisted Reactor TCP客戶端ReactTCP客戶端
- netty系列之:使用netty搭建websocket伺服器NettyWeb伺服器
- Netty 實現HTTP檔案伺服器NettyHTTP伺服器
- 實現簡單元件到部署伺服器——react元件伺服器React