文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Netty、MINA、Twisted一起学系列01:实现简单的TCP服务器

2023-06-04 20:01

关注

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-服务器
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯