基于消息方式实现系统间通信

    大型应用通常会拆分为多个子系统来实现,但这些子系统又不是完全独立的,要相互通信来共同实现业务功能,对于此类Java应用,我们称之为分布式Java应用。本篇幅介绍说明基于消息方式实现系统间通信。

    当系统之间要通信时,就向外发送消息,消息可以是字节流、字节数组,甚至是Java对象,其他系统接收到消息后则进行相应的业务处理。消息方式的系统间通信,通常基于网络协议来实现,常用的实现系统间通信的协议有:TCP/IP、UDP/IP。

1.  基于Java自身技术实现消息方式的系统间通信

    基于Java自身包实现消息方式的系统间通信的方式有:TCP/IP+BIO、TCP/IP+NIO、UDP/IP+BIO以及UDP/IP+NIO 4种,这里略去了UDP/IP的两种方式介绍。

我的另一篇帖子有关于NIO的介绍讲解:http://my.oschina.net/u/865222/blog/277260 

1.1  TCP/IP+BIO

    在Java中可基于Socket、ServerSocket来实现TCP/IP+BIO的系统间通信。Socket主要用于实现建立连接及网络IO的操作,ServerSocket主要用于实现服务器端端口的监听及Socket对象的获取。基于Socket实现客户端的关键代码如下:

//创建连接
Socket socket = new Socket("127.0.0.1", 10001);      
//创建读取服务器端返回流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));      
//创建向服务器写入流
PrintWriter out = new PrintWriter(socket.getOutputStream());      
out.println("hello");      
in.readLine();

服务端关键代码如下:

//创建对本地指定端口的监听
ServerSocket ss = new ServerSocket(10001);   
/接收客户端建立连接的请求
Socket socket = ss.accept();

上面是基于Socket、ServerSocket实现的一个简单的系统间通信的例子。而在实际的系统中,通常要面对的是客户端同时要发送多个请求到服务器端,服务器端则同时要接受多个连接发送的请求,上面的代码显然是无法满足的。

    为了满足客户端能同时发送多个请求到服务器端,多个连接请求自然会产生多个socket,通常采用连接池的方式来维护Socket是比较好的,一方面限制了能创建的Socket的个数;另一方面由于将Socket放入了池中,避免了重复创建Socket带来的性能下降问题。

    为了满足服务器端能同时接收多个连接发送的请求,通常采用的方法是在accept获取Socket后,将此Socket放入一个线程中处理,通常将此方式称为一连接一线程。这样服务器端就可接受多个连接发送请求了,这种方式的缺点是无论连接上是否有真实的请求,都要耗费一个线程。为避免创建过多的线程导致服务器端资源耗尽,须限制创建的线程数量,这就造成了在采用BIO的情况下服务器端所能支撑的连接数是有限的。

1.2  TCP/IP+NIO

    在Java中可基于java.nio.channels中的Channel和Selector的相关类来实现TCP/IP+NIO方式的系统间通信。Channel有SocketChannel和ServerSocketChannel两种,SocketChannel用于建立连接、监听事件及操作读写,ServerSocket-Channel用于监听端口及监听连接事件;程序通过Selector来获取是否有要处理的事件。基于这两个类实现客户端的关键代码如下:

        SocketChannel channel = SocketChannel.open();
        
        //设置为非阻塞模式
        channel.configureBlocking(false);
        
        //对于非阻塞模式,立刻返回false,表示连接正在建立中
        channel.connect(new InetSocketAddress("127.0.0.1", 10002));
       
        Selector selector =  Selector.open();
        
        //向channel注册selector以及感兴趣的连接事件
        channel.register(selector, SelectionKey.OP_CONNECT);
        
        //阻塞至有感兴趣的IO事件发生,或到达超时时间,如果希望一直等至有感兴趣的IO事件发生,
        //可调用无参数的select方法,如果希望不阻塞直接返回目前是否有感兴 趣的事件发生,可调用selectNow方法 
        int nKeys = selector.select(5000);
        //若nkeys大于零,说明有感兴趣的IO事件发生
        SelectionKey sKey = null;
        if(nKeys >0) {
            Set<SelectionKey> keys = selector.selectedKeys();
            for(SelectionKey key : keys){
                //对于发生连接的事件
                if(key.isConnectable()){
                    SocketChannel sc = (SocketChannel) key.channel();
                    sc.configureBlocking(false);
                    //注册感兴趣的IO读事件,通常不直 接注册写事件,在发送缓冲区未满的情况下,一直是可写的,
                    //因此如注册了写事件,而又不用写数据,很容易造成CPU消耗100%的现象
                    sKey = sc.register(selector, SelectionKey.OP_READ);
                    //完成连接的建立
                    sc.finishConnect();
                }
                //有流可读取
                else if(key.isReadable()){
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    SocketChannel sc = (SocketChannel) key.channel();
                    int readBytes = 0;
                    try {
                        int ret = 0;
                        try{
                            //读取目前可读的流
                            while((ret=sc.read(buffer))>0){
                                readBytes += ret;
                            }
                        } finally{
                            buffer.flip();
                        }
                    } finally{
                        if(buffer!=null){
                            buffer.clear();
                        }
                    }
                }
                else if(key.isWritable()){
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //取消对OP_WRITE事件的注册
                    key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
                    SocketChannel sc = (SocketChannel) key.channel();
                    
                    //
                    int writtendSize = sc.write(buffer);
                    
                    //如未写入,则继续注册感兴趣的OP_WRITE事件
                    if(writtendSize==0){
                        key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
                    }
                    
                //对于要写入的流,可直接调用channel.write来完成,只有在写入未成功时才要注册OP_WRITE事件 
                    int wSize = channel.write(buffer);
                    if(wSize == 0){
                        key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); 
                    }
                }
            }
            selector.selectedKeys().clear();
        }

从上可见,NIO是典型的Reactor模式的实现,通过注册感兴趣的事件及扫描是否有感兴趣的事件发生,从而做相应的动作。

服务器端关键代码如下:

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ServerSocket serverSocket = ssc.socket();
        
        //绑定要监听的端口
        serverSocket.bind(new InetSocketAddress(10002));
        
        ssc.configureBlocking(false);
        
        Selector selector =  Selector.open();
        //注册感兴趣的连接建立事件
        ssc.register(selector, SelectionKey.OP_ACCEPT);

之后则可采取和客户端同样的方式对selector.select进行轮询,只是要增加一个对key.isAcceptable的处理,代码如下:

        if(key.isAcceptable()){  
            ServerSocketChannel server=(ServerSocketChannel)key.channel();  
            SocketChannel sc=server.accept();  
            if(sc==null){  
                continue;  
        }  
        sc.configureBlocking(false);  
        sc.register(selector,SelectionKey.OP_READ);  
        }

在讲述NIO的那篇帖子末尾有提供一个完整可运行示例,更详细分析了TCP/IP+NIO。

对于高访问量的系统而言,TCP/IP+NIO方式结合一定的改造在客户端能够带来更高的性能,在服务器端能支撑更高的连接数。

    在Java中可基于DatagramSocket和DatagramPacket来实现UDP/IP+BIO方式的系统间通信,DatagramSocket负责监听端口及读写数据,DatagramPacket作为数据流对象进行传输。通过DatagramChannel和ByteBuffer来实现UDP/IP+NIO方式的系统间通信,DatagramChannel负责监听端口及进行读写,ByteBuffer则用于数据流传输。感兴趣的读者可自行学习这块。

2.  基于开源框架实现消息方式的系统间通信

    使用Java包来实现基于消息方式的系统间通信还是比较麻烦,为了让开发人员能更加专注对数据进行业务处理,而不用过多关注纯技术细节,开源业界诞生了很多优秀的基于以上各种协议的系统间通信的框架。其中的佼佼者有Apache Mina和Jboss Netty,这两框架的作者是同一人。

    关于这两框架的介绍,我整了专门类别的帖子讲述,可查看博客分类栏Mina&Netty

   

    还可以基于消息队列实现系统间的消息通信,消息队列遵循JMS(Java message service)规范,业界流行的开源框架有ActiveMQ、RabbitMQ、ZeroMQ等。

    以上介绍了基于Java自身包及开源通信框架来实现消息方式的系统间通信,Java系统内的通信都是以Java对象调用的方式来实现的,例如A a =new AImpl();a.call();,但当系统变成分布式后,就无法用以上的方式直接调用了,因为在调用端并不会有AImpl这个类。这时如果通过基于以上的消息方式来做,对于开发而言就会显得比较晦涩了,因此Java中也提供了各种各样的支持对象方式的系统间通信的技术,例如RMI、WebService等。同样,在Java中也有众多的开源框架提供了RMI、WebService的实现和封装,例如Spring RMI、CXF等,distributed博客类别的另一帖子将来看看基于远程调用方式如何实现系统间的通信。


转载于:https://my.oschina.net/zhupanxin/blog/269050

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值