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、pool、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

时间: 2024-08-03 00:57:31

Mina、Netty、Twisted一起学(一):实现简单的TCP服务器的相关文章

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

如何使用C++实现简单的TCP服务器的编写

简单的TCP服务器实现 #include <stdio.h> #include <ctype.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #def

Mina、Netty、Twisted一起学(八):HTTP服务器

HTTP协议应该是目前使用最多的应用层协议了,用浏览器打开一个网站就是使用HTTP协议进行数据传输. HTTP协议也是基于TCP协议,所以也有服务器和客户端.HTTP客户端一般是浏览器,当然还有可能是其他东西.HTTP服务器,也就是Web服务器,目前已经有很多成熟的产品,例如Apache HTTP Server.Tomcat.Nginx.IIS等. 本文的内容不是讲解如何使用以上的HTTP服务器,而是要分别用MINA.Netty.Twisted实现一个简单的HTTP服务器. 首先,要简单了解一下

Mina、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息

在TCP连接开始到结束连接,之间可能会多次传输数据,也就是服务器和客户端之间可能会在连接过程中互相传输多条消息.理想状况是一方每发送一条消息,另一方就立即接收到一条,也就是一次write对应一次read.但是,现实不总是按照剧本来走. MINA官方文档节选: TCP guarantess delivery of all packets in the correct order. But there is no guarantee that one write operation on the s

Mina、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)

在上一篇博文中,有介绍到用换行符分割消息的方法.但是这种方法有个小问题,如果消息中本身就包含换行符,那将会将这条消息分割成两条,结果就不对了. 本文介绍另外一种消息分割方式,即上一篇博文中讲的第2条:use a fixed length header that indicates the length of the body,用一个固定字节数的Header前缀来指定Body的字节数,以此来分割消息. 上面图中Header固定为4字节,Header中保存的是一个4字节(32位)的整数,例如12即为

Windows 下的最简单的TCP服务器客户端

他们是短连接的,服务器接受客户端之后,马上发送一个消息,发送完以后立即将客户端断开掉,然后继续等待下一个连接. 使用Winsocket2必须要引用到的头文件和需要包含到的链接库文件: #include <WinSock2.h> #pragma comment( lib, "ws2_32.lib" ) 以下代码是Winsocket2的系统初始化和关闭的封装代码. class WinSocketSystem { public: WinSocketSystem() { int i

python写一些简单的tcp服务器和客户端

代码贴上,做个记录 TcpClient # -*- coding:utf-8 -*- import socket target_host = "127.0.0.1" #服务器端地址 target_port = 5555 #与服务器的端口号一致 while True: client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect((target_host,target_port)) #data存储要发送的

Mina、Netty、Twisted一起学(五):整合protobuf

protobuf是谷歌的Protocol Buffers的简称,用于结构化数据和字节码之间互相转换(序列化.反序列化),一般应用于网络传输,可支持多种编程语言. protobuf怎样使用这里不再介绍,本文主要介绍在MINA.Netty.Twisted中怎样使用protobuf,不了解protobuf的同学能够去參考我的还有一篇博文. 在前面的一篇博文中.有介绍到一种用一个固定为4字节的前缀Header来指定Body的字节数的一种消息切割方式.在这里相同要使用到. 仅仅是当中Body的内容不再是字

Mina、Netty、Twisted一起学(十):线程模型

要想开发一个高性能的TCPserver,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,假设再搭配上高效率的代码.才干实现一个高大上的server. 可是假设不了解它们的线程模型.就非常难写出高性能的代码.框架本身效率再高.程序写的太差,那么server总体的性能也不会太高.就像一个电脑,CPU再好.内存小硬盘慢散热差,总体的性能也不会太高. 玩过Android开发的同学会知道,在Android应用中有一个非常重要线程:UI线程(即主线程). UI