工业互联网平台IMSA---1.4.启动过程详解3

我们已经实现了一些基础的功能,但是由于我们只是以最简实现的方式来实现这些功能,所以代码远谈不上优化,因此我们在这篇博文中,先暂时不开发新功能,而是对现有功能进行重构。

首先,我们不仅在门户Facade中需要Nio服务器,在消息总线Plato接收微服务注册、接收系统消息时也需要NIO服务器,同时微服务控制器和微服务接收消息总线消息时,也需要NIO服务器,目前这种实现方式,显然不能满足这些重用需求,因此我们需要在common项目中引入NioTcpServer基类,然后门户Facade、消息总线Plato和微服务所需要的NIO服务器均继承此类。

为了更好地实现功能,我们需要借鉴Python等语言,引入元组的功能。元组Turple是一个对象,动态拥有不可变的,任意数量的属性。之所以引入元组,是因为当函数需要返回多个值时,通常的做法为其定义一个新的值对象类,但是这样的结果是产生一大堆仅用于传值的类,不便于管理,所以我们有必要定义一个自己的元组类,让Java也能像Python那样方便的使用元组。我们先定义具有两个元素的元组。代码如下所示:

public class Turple2<T1, T2> {
	public final T1 v1;
	public final T2 v2;
	
	public Turple2(T1 v1, T2 v2) {
		this.v1 = v1;
		this.v2 = v2;
	}
	
	public String toString() {
		return "(" + v1 + ", " + v2 + ")";
	}
}
如上所示,利用泛型,我们定义Turple2具有两种类型的属性,且为不可变的,打印时显示格式为(v1, v2),与Python语言中的元素表现一致。

下面我们来看在common项目中的NIO服务器基类NioTcpServer的启动部分,如下所示:

public abstract class NioTcpServer {
	private short port = 8088; // 服务器监听端口
	protected abstract void processRequest(SelectionKey key, Selector selector);
	protected abstract void processResponse(SelectionKey key);
	
	/**
	 * 程序总入口,启动Imsa服务器
	 * @throws Exception
	 */
	public void start() throws Exception {  
        Selector selector = Selector.open();  
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
        serverSocketChannel.configureBlocking(false);  
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
        serverSocketChannel.socket().setReuseAddress(true);  
        serverSocketChannel.socket().bind(new InetSocketAddress(port));  
        while(true){  
            while (selector.select() > 0) {
                Iterator<SelectionKey> selectedKeys = selector.selectedKeys() .iterator();  
                while (selectedKeys.hasNext()) {  
                    SelectionKey key = selectedKeys.next();  
                    if (key.isAcceptable()) {
                    	acceptConnection(key, selector);
                    } else if (key.isReadable()) {  
                        //readRequest(key, selector);
                    	processRequest(key, selector);
                    } else if (key.isWritable()) {
                        //sendResponse(key, prepareTestResponse());
                    	processResponse(key);
                    }  
                }  
            }  
        }
    }
代码与之前博客上介绍的内容差不多,但是有两个重要的区别,我们定义了两个抽象方法:processRequest需要子类来实现,其会调用基类readRequest方法获取到请求内容,同时再执行一些额外的逻辑。processResponse方法,会先根据子类特定需要,生成需要发送的请求,然后调用基类的sendResponse方法发送相应的响应。

基类中readRequest方法需要返回一个二元元组,代码如下所示:

	/**
	 * 读取消息内容,并向消息总线plato发送消息
	 * @param key
	 * @param selector
	 * @return 二元元组,a代表请求文本内容,b为二进制对象URL数组
	 */
	protected Turple2<String, String[]> readRequest(SelectionKey key, Selector selector) {
		SocketChannel channel = (SocketChannel) key.channel();  
		Turple2<String, String[]> rst = new Turple2<String, String[]>("", null);
        try {
			channel.configureBlocking(false);
	        String receive = receive(channel);
	        // 如果没有接收到内容,就直接返回
	        if (receive.equals("")) {
	        	return rst;
	        }
	        BufferedReader b = new BufferedReader(new StringReader(receive));  
	        String s = b.readLine();  
	        StringBuilder req = new StringBuilder();
	        while (s != null) {  
	            req.append(s + "\r\n");
	            s = b.readLine();  
	        }  
	        b.close(); 
	        String[] urls = null;
	        channel.register(selector, SelectionKey.OP_WRITE);
	        rst = new Turple2<String, String[]>(req.toString(), urls);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return rst;
	}
其他方法的代码与原来的代码相同,这里就不再重复了,如果有疑问,请参考Github项目:https://github.com/yt7589/imsa。

下面我们再来看原来讨论的门户Facade中的NIO服务器,在继承NioTcpServer的情况如何实现,代码如下所示:

public class FacadeServer extends NioTcpServer {
	private short port = 8088; // 服务器监听端口
	
	/**
	 * 程序总入口,启动Imsa服务器
	 * @throws Exception
	 */
	public void start() throws Exception {
		super.start();
    }
	
	/**
	 * 接受客户端的连接请求
	 * @param key
	 * @param selector
	 */
	protected void acceptConnection(SelectionKey key, Selector selector) {
		super.acceptConnection(key, selector);
	}
	
	/**
	 * 读取消息内容,并向消息总线plato发送消息
	 * @param key
	 * @param selector
	 */
	protected void processRequest(SelectionKey key, Selector selector) {
		Turple2<String, String[]> reqObj = super.readRequest(key, selector);
		String msgStr = ImsaMsgEngine.createMsg(AppConsts.MT_HTTP_GET_REQ, AppConsts.MT_MSG_V1, reqObj.v1, null);
		// 发送消息到消息总线
	}
	
	/**
	 * 从消息总线接收到需要发送的HTTP响应,将响应发送给客户端
	 * @param key
	 * @param resp
	 */
	protected void processResponse(SelectionKey key) {
		String resp = prepareTestResponse();
		super.sendResponse(key, resp);
	}
如上所示,start和acceptConnection方法只需要简单的调用基类方法就可以了。

对processRequest方法,我们调用基类的readRequest方法,获取到包含原始请求内容的二元元组,调用消息引擎生成消息,然后再将消息发送到消息总线Plato上。

对processResponse方法,我们首先生成响应的内容,然后调用基类的sendResponse方法来进行发送。

好了,到此为止,一次简单的代码重构就完成了。在实际开发中,我们一定不要偷懒,要经常进行代码重构,否则代码会变得越来越臃肿,质量越来越差,越来越不可维护。

如果有不请楚的地方,请大家参考Github上的开源项目:https://github.com/yt7589/imsa ,如果大家觉得项目对大家有用,请点赞支持一下作者,谢谢!




猜你喜欢

转载自blog.csdn.net/yt7589/article/details/79170264