手動でI / Oネットワーク通信フレームワークを構築する4:AIOプログラミングモデル、チャットルームの最終的な変換I / Oネットワーク通信フレームワークを手動で構築する1:SocketとServerSocketエントリの戦闘、シングルチャットを実現して手動でI / Oネットワーク通信フレームワークを構築する2:BIOプログラミングモデルグループチャットを実現し、手動でI / Oネットワーク通信フレームワークを構築する3:NIOプログラミングモデル、チャットルームのアップグレードと変換

第1章:手動でI / Oネットワーク通信フレームワークを構築する1:SocketおよびServerSocketの概要、シングルチャットの実現

第2章:I / Oネットワーク通信フレームワークを手動で構築する2:グループチャットを実現するBIOプログラミングモデル

第3章:I / Oネットワーク通信フレームワークを手動で構築する3:NIOプログラミングモデル、チャットルームをアップグレードおよび変換する

  前章で述べたNIOプログラミングモデルはより主流であり、非常に有名なNettyはNIOプログラミングモデルに基づいています。この章では、非同期で非ブロッキングであるAIOプログラミングモデルについて説明します。チャットルーム機能も実装されていますが、実装ロジックはNIOやBIOよりも少し複雑です。ただし、全体的なコンテキストを理解する方が簡単です。最初に、コンセプトについて話しましょう:

  BIOとNIOの違いはブロッキングと非ブロッキングですが、AIOは非同期IOを表します。これに先立ち、ブロッキングと非ブロッキングのみが言及され、非同期または同期の言及はありませんでした。これは、Zhizhiで見た文で表すことができます。[IOを処理する場合、ブロッキングと非ブロッキングの両方が同期IOであり、特別なAPIを使用するだけで非同期IOになります]。これらの「特別なAPI」については、以下で説明します。AIOについて説明する前に、まず、非ブロッキングの非同期同期をブロックする概念を要約します。

  ブロッキングとノンブロッキング、結果のリクエストを説明します。ブロッキング:結果が得られるまでそのままにし、何もせず、スレッドがハングします。名前が示すように、スレッドはブロックされます。ノンブロッキング:結果が得られない場合は戻り、しばらく待ってから、結果が得られるまで要求します。非同期および同期。発信者のリクエストが入ったときの結果の問題を説明します。同期:結果が取得されるまで、呼び出し元は呼び出し元に返されません。呼び出し元がブロックされている場合、呼び出し元は常に待機します。呼び出し元が非ブロッキングの場合、呼び出し元は最初に戻り、結果を取得するまでしばらく待機します。非同期:呼び出し側が到着したときに、それが非ブロッキングの場合は、最初にコールバックしてください。後で説明します。ブロックされている場合、非同期に通知されますが、それでも待機する必要がありますが、皮肉なことです。

  AIOでの非同期操作

  CompletionHandler

  AIOプログラミングモデルでは、接続、受け入れ、読み取り、書き込みなどの一般的に使用されるAPIはすべて非同期操作をサポートしています。これらのメソッドを呼び出すときに、いくつかのコールバック関数を提供するCompletionHandlerパラメーターを運ぶことができます。これらのコールバック関数には、次のものが含まれます。1.これらの操作が成功したときに何をする必要があるか; 2.これらの操作を行う場合は、そうする必要があります。CompletionHandlerパラメータに関しては、CompletionHandlerポートを実装し、内部に2つのメソッドを実装するクラスを記述するだけで済みます。

  では、非同期を実現するために、接続、受け入れ、読み取り、および書き込みの4つのメソッドを呼び出すときに、CompletionHandlerパラメーターをどのように渡しますか?以下は、これら4つの方法の使用例です。

  最初にSocketとServerSocketについて話しましょう。NIOでは、これらはチャネルになり、バッファと協力して非ブロッキングを実現します。AIOでは、それらは非同期チャネルになります。つまり、AsynchronousServerSocketChannelとAsynchronousSocketChannelです。次の例のオブジェクト名は、それぞれserverSocketとsocketです。

  accept:serverSocket.accept(添付ファイル、ハンドラー)。ハンドラーは、CompletionHandlerインターフェイスを実装し、2つのコールバック関数を実装するクラスです。ハンドラーの記述方法は、以下の実際のコードで確認できます。添付ファイルは、ハンドラーで使用できる補助データです。それ以外の場合は、nullを入力します。

  読み取り:socket.read(バッファー、添付ファイル、ハンドラー)。bufferは、読み込まれた情報を格納するために使用されるバッファです。後の2つのパラメーターは、acceptと同じです。

  書き込み:socket.write(バッファ、添付ファイル、ハンドラ)。読み取りパラメータと同じです。

  connect:socket.connect(アドレス、添付ファイル、ハンドラー)。addressはサーバーのIPとポートです。後の2つのパラメーターは前のパラメーターと同じです。

  未来

  非同期操作になるので、CompletionHandlerインターフェースを実装する方法を使用することに加えて、Futureを考える必要がありますクライアントロジックは比較的単純です。CompletionHandlerを使用すると、コードがより複雑になるため、次の実際のクライアントコードではFutureを使用します。簡単に言えば、Futureは非同期操作の将来の結果、つまり将来を理解する方法を表します。たとえば、クライアントはreadメソッドを呼び出して、サーバーから送信されたメッセージを取得します。

Future <Integer> readResult = clientChannel.read(buffer)

  Integerはread()の戻り型です。現時点では、変数readResultに必ずしもデータがあるわけではありませんが、read()メソッドの将来の結果を表しています。このとき、readResultにはisDone()という2つのメソッドがあります。ブール値を返し、プログラムが処理完了後、trueを返すと結果が出ますが、現時点ではget()で結果を取得できます。isDone()を事前に判断せずにget()を直接呼び出す場合は、ブロックされているだけです。この間にブロックしたくない場合や何かをしたい場合は、isDone()を使用します。

  別の質問がありますこれらのハンドラーメソッドはどのスレッドで実行されますか?serverSocket.acceptメソッドはメインスレッドで呼び出す必要があり、渡されたコールバックメソッドは実際には他のスレッドで実行されます。AIOには、AsynchronousServerSocketChannelと一緒にバインドされるAsynchronousChannelGroupがあり、これらの非同期チャネルにシステムリソースを提供します。スレッドはシステムリソースの1つなので、理解を容易にするために、一時的に彼をスレッドプール。メインスレッドで実行するのではなく、これらのハンドラーにスレッドを割り当てます。

   AIOプログラミングモデル

  上記は一部の断片化された概念についてのみ説明しました。理解を深めるために、一般的なワークフローについて説明します(主にサーバーの場合、クライアントロジックは比較的単純で、コードコメントは比較的小さく、前の章を参照できます)。

  1.最初に準備します。NIOと同様に、AIOが非同期チャネルであることを除いて、チャネルを最初に作成する必要があります。次に、AsyncChannelGroupを作成します。スレッドプールのカスタマイズを選択できます。最後に、AsyncServerSocketとAsyncChannelGroupをバインドして、同じAsyncChannelGroup内のチャネルがシステムリソースを共有できるようにします。

  2.最后一步准备工作,创建好handler类,并实现接口和里面两个回调方法。(如图:客户端1对应的handler,里面的回调方法会实现读取消息和转发消息的功能;serverSocket的handler里的回调方法会实现accept功能。)

  3.准备工作完成,当客户端1连接请求进来,客户端会马上回去,ServerSocket的异步方法会在连接成功后把客户端的SocketChannel存进在线用户列表,并利用客户端1的handler开始异步监听客户端1发送的消息。

  4.当客户端1发送消息时,如果上一步中的handler成功监听到,就会回调成功后的回调方法,这个方法里会把这个消息转发给其他客户端。转发完成后,接着利用handler监听客户端1发送的消息。

 

  代码一共有三个类:

  ChatServer:功能基本上和上面讲的工作流程差不多,还会有一些工具方法,都比较简单,就不多说了,如:转发消息,客户端下线后从在线列表移除客户端等。

  ChatClient:基本和前两章的BIO、NIO没什么区别,一个线程监听用户输入信息并发送,主线程异步的读取服务器信息。

  UserInputHandler:监听用户输入信息的线程。

  ChatServer

public class ChatServer {
    //设置缓冲区字节大小
    private static final int BUFFER = 1024;

    //声明AsynchronousServerSocketChannel和AsynchronousChannelGroup
    private AsynchronousServerSocketChannel serverSocketChannel;
    private AsynchronousChannelGroup channelGroup;

    //在线用户列表。为了并发下的线程安全,所以使用CopyOnWriteArrayList
    //CopyOnWriteArrayList在写时加锁,读时不加锁,而本项目正好在转发消息时需要频繁读取.
    //ClientHandler包含每个客户端的通道,类型选择为ClientHandler是为了在write的时候调用每个客户端的handler
    private CopyOnWriteArrayList<ClientHandler> clientHandlerList;
    //字符和字符串互转需要用到,规定编码方式,避免中文乱码
    private Charset charset = Charset.forName("UTF-8");

    //通过构造函数设置监听端口
    private int port;
    public ChatServer(int port) {
        this.port = port;
        clientHandlerList=new CopyOnWriteArrayList<>();
    }

    public void start() {
        try {
            /**
             *创建一个线程池并把线程池和AsynchronousChannelGroup绑定,前面提到了AsynchronousChannelGroup包括一些系统资源,而线程就是其中一种。
             *为了方便理解我们就暂且把它当作线程池,实际上并不止包含线程池。如果你需要自己选定线程池类型和数量,就可以如下操作
             *如果不需要自定义线程池类型和数量,可以不用写下面两行代码。
             * */
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
            serverSocketChannel=AsynchronousServerSocketChannel.open(channelGroup);
            serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",port));
            System.out.println("服务器启动:端口【"+port+"】");
            /**
             * AIO中accept可以异步调用,就用上面说到的CompletionHandler方式
             * 第一个参数是辅助参数,回调函数中可能会用上的,如果没有就填null;第二个参数为CompletionHandler接口的实现
             * 这里使用while和System.in.read()的原因:
             * while是为了让服务器保持运行状态,前面的NIO,BIO都有用到while无限循环来保持服务器运行,但是它们用的地方可能更好理解
             * System.in.read()是阻塞式的调用,只是单纯的避免无限循环而让accept频繁被调用,无实际业务功能。
             */
            while (true) {
                serverSocketChannel.accept(null, new AcceptHandler());
                System.in.read();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocketChannel!=null){
                try {
                    serverSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //AsynchronousSocketChannel为accept返回的类型,Object为辅助参数类型,没有就填Object
    private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object>{
        //如果成功,执行的回调方法
        @Override
        public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
            //如果服务器没关闭,在接收完当前客户端的请求后,再次调用,以接着接收其他客户端的请求
            if(serverSocketChannel.isOpen()){
                serverSocketChannel.accept(null,this);
            }
            //如果客户端的channel没有关闭
            if(clientChannel!=null&&clientChannel.isOpen()){
                //这个就是异步read和write要用到的handler,并传入当前客户端的channel
                ClientHandler handler=new ClientHandler(clientChannel);
                //把新用户添加到在线用户列表里
                clientHandlerList.add(handler);
                System.out.println(getPort(clientChannel)+"上线啦!");
                ByteBuffer buffer=ByteBuffer.allocate(BUFFER);
                //异步调用read,第一个buffer是存放读到数据的容器,第二个是辅助参数。
                //因为真正的处理是在handler里的回调函数进行的,辅助参数会直接传进回调函数,所以为了方便使用,buffer就当作辅助参数
                clientChannel.read(buffer,buffer,handler);
            }
        }
        //如果失败,执行的回调方法
        @Override
        public void failed(Throwable exc, Object attachment) {
            System.out.println("连接失败"+exc);
        }
    }

    private class ClientHandler implements CompletionHandler<Integer, ByteBuffer>{
        private AsynchronousSocketChannel clientChannel;
        public ClientHandler(AsynchronousSocketChannel clientChannel) {
            this.clientChannel = clientChannel;
        }
        @Override
        public void completed(Integer result, ByteBuffer buffer) {
            if(buffer!=null){
                //如果read返回的结果小于等于0,而buffer不为空,说明客户端通道出现异常,做下线操作
                if(result<=0){
                    removeClient(this);
                }else {
                    //转换buffer读写模式并获取消息
                    buffer.flip();
                    String msg=String.valueOf(charset.decode(buffer));
                    //在服务器上打印客户端发来的消息
                    System.out.println(getPort(clientChannel)+msg);
                    //把消息转发给其他客户端
                    sendMessage(clientChannel,getPort(clientChannel)+msg);
                    buffer=ByteBuffer.allocate(BUFFER);

                    //如果用户输入的是退出,就从在线列表里移除。否则接着监听这个用户发送消息
                    if(msg.equals("quit"))
                        removeClient(this);
                    else
                        clientChannel.read(buffer, buffer, this);
                }
            }
        }

        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            System.out.println("客户端读写异常:"+exc);
        }
    }

    //转发消息的方法
    private void sendMessage(AsynchronousSocketChannel clientChannel,String msg){
        for(ClientHandler handler:clientHandlerList){
            if(!handler.clientChannel.equals(clientChannel)){
                ByteBuffer buffer=charset.encode(msg);
                //write不需要buffer当辅助参数,因为写到客户端的通道就完事了,而读还需要回调函数转发给其他客户端。
                handler.clientChannel.write(buffer,null,handler);
            }
        }
    }
    //根据客户端channel获取对应端口号的方法
    private String getPort(AsynchronousSocketChannel clientChannel){
        try {
            InetSocketAddress address=(InetSocketAddress)clientChannel.getRemoteAddress();
            return "客户端["+address.getPort()+"]:";
        } catch (IOException e) {
            e.printStackTrace();
            return "客户端[Undefined]:";
        }
    }
    //移除客户端
    private void removeClient(ClientHandler handler){
        clientHandlerList.remove(handler);
        System.out.println(getPort(handler.clientChannel)+"断开连接...");
        if(handler.clientChannel!=null){
            try {
                handler.clientChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new ChatServer(8888).start();
    }
}

  ChatClient

public class ChatClient {
    private static final int BUFFER = 1024;
    private AsynchronousSocketChannel clientChannel;
    private Charset charset = Charset.forName("UTF-8");

    private String host;
    private int port;
    //设置服务器IP和端口
    public ChatClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() {
        try {
            clientChannel = AsynchronousSocketChannel.open();
            //连接服务器
            Future<Void> future = clientChannel.connect(new InetSocketAddress(host, port));
            future.get();
            //新建一个线程去等待用户输入
            new Thread(new UserInputHandler(this)).start();
            ByteBuffer buffer=ByteBuffer.allocate(BUFFER);
            //无限循环让客户端保持运行状态
            while (true){
                //获取服务器发来的消息并存入到buffer
                Future<Integer> read=clientChannel.read(buffer);
                if(read.get()>0){
                    buffer.flip();
                    String msg=String.valueOf(charset.decode(buffer));
                    System.out.println(msg);
                    buffer.clear();
                }else {
                    //如果read的结果小于等于0说明和服务器连接出现异常
                    System.out.println("服务器断开连接");
                    if(clientChannel!=null){
                        clientChannel.close();
                    }
                    System.exit(-1);
                }
            }
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public void send(String msg) {
        if (msg.isEmpty())
            return;
        ByteBuffer buffer = charset.encode(msg);
        Future<Integer> write=clientChannel.write(buffer);
        try {
            //获取发送结果,如果get方法发生异常说明发送失败
            write.get();
        } catch (ExecutionException|InterruptedException e) {
            System.out.println("消息发送失败");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new ChatClient("127.0.0.1",8888).start();
    }
}

  UserInputHandler

public class UserInputHandler implements Runnable {
    ChatClient client;
    public UserInputHandler(ChatClient chatClient) {
        this.client=chatClient;
    }
    @Override
    public void run() {
        BufferedReader read=new BufferedReader(
                new InputStreamReader(System.in)
        );
        while (true){
            try {
                String input=read.readLine();
                client.send(input);
                if(input.equals("quit"))
                    break;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

  运行测试:

 

おすすめ

転載: www.cnblogs.com/lbhym/p/12720944.html