坦克大戰 - 設計模式、BIO、NIO、AIO、Netty

設計模式

1、策略模式

有時候你想發射單排子彈,有時候你想發射雙排子彈
當你想有不同的子彈發射方式時,應該怎麼做才能在儘可能少的修改代碼的前提下,快速完成這些子彈發射策略的切換呢?

辦法就是,你寫一個Interface,讓不同的策略去實現這個Interface
你去通過配置文件,選擇你想要的子彈發射方式。
在子彈發射邏輯中,通過反射獲取策略對應的類,然後直接調用接口的實現方法。
在這裏插入圖片描述

2、責任鏈模式

責任鏈模式,類似於 Servlet 的 Filter
在這裏插入圖片描述

在坦克大戰遊戲中,你怎麼判斷兩個遊戲物體的碰撞?通過 if - else 暴力遍歷嗎?這樣不好,因爲如果你想加入新的規則,對代碼的改動太大了。

使用碰撞器
你可以寫很多種不同的碰撞器(碰撞規則),每一個碰撞器是一個獨立的類,去實現Collider接口。
然後,通過每一個collider返回值,控制當前的鏈條是否要繼續。(如果在當前碰撞器中已經發生了碰撞,則後面的鏈條就不需要執行了)
你比如說,子彈、坦克相撞的碰撞器可以這樣定義:
在這裏插入圖片描述

寫完很多碰撞器之後,怎麼在邏輯中檢測是否發生了各種碰撞之一呢?
在這裏插入圖片描述
你可以通過編寫配置文件,在邏輯中反射獲取所有你需要加載的碰撞器,放在一個list裏面(colliders是一個list
在這裏插入圖片描述
通過遍歷這個裝有各種碰撞器list,去檢測每一種碰撞是否發生。
在這裏插入圖片描述
用這種方式,當你想要添加新的碰撞規則時,你只需要在碰撞器包下,新建一個碰撞器,然後在配置文件中把新的碰撞器添加進去,就可以啦。代碼就會變得非常靈活,擴展方便。

3、Facade 門面模式

根據MVC,進行Model和View的分離,寫一個GameModel類,將來所有的Model都可以放在GameModel中

4、Mediator 調停者模式

如果你有非常複雜的業務邏輯(比如說在我們的坦克大戰中,坦克坦克碰撞,坦克子彈碰撞,子彈和牆碰撞,…),這時候我們專門給這些複雜的業務邏輯單獨寫一個調停者。


網絡模型

在這裏插入圖片描述
TCP的模型有下面三種:BIO,NIO,AIO

1、BIO

BIO 是阻塞式的 IO,需要等待。

  • 等待客戶端的連接,連接後,會拋出一個線程
  • 等待客戶端發送消息

服務端:

package com.bjmashibing.system.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(9090, 20);
        while (true) {
            Socket client = server.accept();  //阻塞1,等待客戶端連接,連接後拋出一個線程
            new Thread(() -> {
                InputStream in = null;
                try {
                    in = client.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    while (true) {
                        String dataline = reader.readLine(); //阻塞2,等待客戶端發來消息
                        if (null != dataline) {
                            System.out.println(dataline);
                        } else {
                            client.close();
                            break;
                        }
                    }
                    System.out.println("客戶端斷開");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

客戶端:

package com.bjmashibing.system.io;

import java.io.*;
import java.net.Socket;

/**
 * @author: 馬士兵教育
 * @create: 2020-05-17 16:18
 */
public class SocketClient {

    public static void main(String[] args) {
        try {
            Socket client = new Socket("192.168.150.11", 9090);
            client.setSendBufferSize(20);
            client.setTcpNoDelay(true);
            OutputStream out = client.getOutputStream();
            InputStream in = System.in;
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            while (true) {
                String line = reader.readLine();
                if (line != null) {
                    byte[] bb = line.getBytes();
                    for (byte b : bb) {
                        out.write(b);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、NIO (Non Blocking IO)

非阻塞式的,叫ServerSocket
非阻塞式的,叫ServerSocketChannel
在這裏插入圖片描述

package com.bjmashibing.system.io;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class SocketMultiplexingSingleThreadv1 {

    //這個代碼看不懂的話,可以去看馬老師的坦克 一、二期(netty)
    private ServerSocketChannel server = null;
    private Selector selector = null;   //linux 多路複用器(select poll epoll kqueue) nginx  event{}
    int port = 9090;

    public void initServer() {
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false); // 設置成非阻塞
            server.bind(new InetSocketAddress(port));  // 綁定監聽的端口號

            //如果在epoll模型下,Selector.open()其實完成了epoll_create,可能給你返回了一個 fd3
            selector = Selector.open();  // 可以選擇 select  poll  *epoll,在linux中會優先選擇epoll  但是可以在JVM使用-D參數修正

            //server 約等於 listen 狀態的 fd4
            /*
                server.register()初始化過程
                如果在select,poll的模型下,是在jvm裏開闢一個數組,把fd4放進去
                如果在epoll的模型下,調用了epoll_ctl(fd3,ADD,fd4,關注的是EPOLLIN
             */
            server.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        initServer();
        System.out.println("服務器啓動了。。。。。");
        try {
            while (true) {
                Set<SelectionKey> keys = selector.keys();
                System.out.println(keys.size() + "   size");
                //1,調用多路複用器(select,poll or epoll(實質上是調用的epoll_wait))
                /*
                    java中的select()是啥意思:
                    1,如果用select,poll 模型,其實調的是內核的select方法,並傳入參數(fd4),或者poll(fd4)
                    2,如果用epoll模型,其實調用的是內核的epoll_wait()
                    注意:參數可以帶時間。如果沒有時間,或者時間是0,代表阻塞。如果有時間,則設置一個超時時間。
                         方法selector.wakeup()可以外部控制讓它不阻塞。這時select的結果返回是0。
                懶加載:
                    其實再觸碰到selector.select()調用的時候觸發了epoll_ctl的調用
                 */
                while (selector.select(500) > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();  //拿到返回的有狀態的fd集合
                    Iterator<SelectionKey> iter = selectionKeys.iterator();  // 轉成迭代器
                    //所以,不管你是啥多路複用器,你只能告訴我fd的狀態,我還得一個一個的去處理他們的R/W。同步好辛苦!!!
                    //我們之前用NIO的時候,需要自己對着每一個fd調用系統調用,浪費資源,那麼你看,這裏是不是調用了一次select方法,知道具體的那些可以R/W了?是不是很省力?
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        iter.remove(); //這時一個set,不移除的話會重複循環處理
                        if (key.isAcceptable()) { //我前邊強調過,socket分爲兩種,一種是listen的,一種是用於通信 R/W 的
                            //這裏是重點,如果要去接受一個新的連接
                            //語義上,accept接受連接且返回新連接的FD,對吧?
                            //那新的FD怎麼辦?
                            //如果使用select,poll的時候,因爲他們內核沒有空間,那麼在jvm中保存,和前邊的fd4那個listen的放在一起
                            //如果使用epoll的話,我們希望通過epoll_ctl把新的客戶端fd註冊到內核空間
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            readHandler(key);
                            //在當前線程,這個方法可能會阻塞,如果阻塞了十年,其他的IO早就沒電了。。。
                            //所以,爲什麼提出了 IO THREADS,我把讀到的東西扔出去,而不是現場處理
                            //你想,redis是不是用了epoll?redis是不是有個io threads的概念?redis是不是單線程的?
                            //你想,tomcat 8,9版本之後,是不是也提出了一種異步的處理方式?是不是也在 IO 和處理上解耦?
                            //這些都是等效的。
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void acceptHandler(SelectionKey key) {
        try {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel client = ssc.accept(); //來啦,目的是調用accept接受客戶端  fd7
            client.configureBlocking(false);

            ByteBuffer buffer = ByteBuffer.allocate(8192);  //前邊講過了

            // 0.0  我類個去
            //你看,調用了register
            /*
                select,poll:    jvm裏開闢一個數組 fd7 放進去
                epoll:          epoll_ctl(fd3,ADD,fd7,EPOLLIN
             */
            client.register(selector, SelectionKey.OP_READ, buffer);
            System.out.println("新客戶端:" + client.getRemoteAddress());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void readHandler(SelectionKey key) {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();
        int read = 0;
        try {
            while (true) {
                read = client.read(buffer);
                if (read > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        client.write(buffer);
                    }
                    buffer.clear();
                } else if (read == 0) {
                    break;
                } else {
                    client.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SocketMultiplexingSingleThreadv1 service = new SocketMultiplexingSingleThreadv1();
        service.start();
    }
}

3、AIO

AIO 是異步的模型,使用的是 callback / hook / templateMethod 回調,是基於事件模型的 IO
AIO只有Window支持(內核中使用CompletionPort完成端口),而在Linux上的AIO只不過是對NIO的封裝而已(是基於epoll的輪詢)
所以Netty封裝的是NIO
在這裏插入圖片描述

package com.mashibing.io.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class Server {
    public static void main(String[] args) throws Exception {
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(8888));

        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                serverChannel.accept(null, this);
                try {
                    System.out.println(client.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.flip();
                            System.out.println(new String(attachment.array(), 0, result));
                            client.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });


                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        while (true) {
            Thread.sleep(1000);
        }
    }
}

使用線程組的AIO

package com.mashibing.io.aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerWithThreadGroup {
    public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newCachedThreadPool();
        AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);

        //中文測試
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(threadGroup)
                .bind(new InetSocketAddress(8888));

        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                serverChannel.accept(null, this);
                try {
                    System.out.println(client.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.flip();
                            System.out.println(new String(attachment.array(), 0, result));
                            client.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });


                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        while (true) {
            Thread.sleep(1000);
        }

    }
}

4、Nettty

Netty主要用於網絡通信。很多網頁遊戲的服務器都是用Netty寫的。
Tomcat,Zookeeper,很多開源分佈式的底層都是netty寫的。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章