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

posted @ 2014-08-25 14:36 叉叉哥 阅读(...) 评论(...) 编辑 收藏