Mina、Netty、Twisted一起學(一):實現簡單的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官網的文案不專業啊,居然不寫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服務器輸出結果:

sessionCreated
messageReceived:第一次請求
messageReceived:第二次請求
sessionClosed

Netty服務器輸出結果:

channelActive
channelRead:第一次請求
channelRead:第二次請求
channelInactive

Twisted服務器輸出結果:

connectionMade
dataReceived: 第一次請求
dataReceived: 第二次請求
connectionLost

MINA、Netty、Twisted一起學系列

MINA、Netty、Twisted一起學(一):實現簡單的TCP服務器

MINA、Netty、Twisted一起學(二):TCP消息邊界問題及按行分割消息

MINA、Netty、Twisted一起學(三):TCP消息固定大小的前綴(Header)

MINA、Netty、Twisted一起學(四):定制自己的協議

MINA、Netty、Twisted一起學(五):整合protobuf

MINA、Netty、Twisted一起學(六):session

MINA、Netty、Twisted一起學(七):發布/訂閱(Publish/Subscribe)

MINA、Netty、Twisted一起學(八):HTTP服務器

MINA、Netty、Twisted一起學(九):異步IO和回調函數

MINA、Netty、Twisted一起學(十):線程模型

MINA、Netty、Twisted一起學(十一):SSL/TLS

MINA、Netty、Twisted一起學(十二):HTTPS

源碼

https://github.com/wucao/mina-netty-twisted


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM