株式会社イーブ|未経験・転職の方も就職可能。Javaプログラマー育成のエキスパート

HOMEJAVA技術者育成システム開発求人情報個人情報保護

第 110 回 ~ ソケットチャンネルの入出力 ~

トップページ > Java技術者育成 > Javaワンポイント > 第 110 回 ~ ソケットチャンネルの入出力 ~

引き続き「ソケットチャンネルの入出力」です。


ServerSocketChannel、SocketChannelはSelectableChannelを継承しています。
このチャンネルにはconfigureBlockingメソッドによりブロックモードを設定できます。
ブロックモードが非ブロックに設定された場合、入出力による待機が発生しません。
したがって、非ブロックモードの場合、入出力を行うタイミングが解らなければなりません。
そのためにはセレクタ(Selector)を使用します。
セレクタにはあらかじめチャンネルを登録しておきます。
そしてSelectorのselectメソッドは、登録されたチャンネルのうち入出力が可能なチャンネルを選択してくれます。


次のサンプルコードはサーバを起動し、クライアントの接続要求に応答するプログラムです。
入出力は非ブロックモードで行います。



import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;


import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;


public class Main extends JFrame implements ActionListener {


    private static final long serialVersionUID = 1L;


    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception e) {}
                new Main();
            }
        });
    }


    private final Service service = new Service();
    private final JPanel jPanel = new JPanel();
    private final JTextField port = new JTextField("9999");
    private final DefaultListModel defaultListModel = new DefaultListModel();
    private final JList jList = new JList(defaultListModel);
    private final JScrollPane jScrollPane = new JScrollPane(jList);
    private final JButton startupButton = new JButton("起動");
    private final JButton shutdownButton = new JButton("停止");


    public Main() {
        startupButton.addActionListener(this);
        shutdownButton.addActionListener(this);
        jPanel.add(port);
        jPanel.add(jScrollPane);
        jPanel.add(startupButton);
        jPanel.add(shutdownButton);
        setContentPane(jPanel);
        setTitle("SERVER");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 225);
        setResizable(false);
        setLocationRelativeTo(null);
        setVisible(true);
    }


    public void actionPerformed(ActionEvent actionEvent) {
        if (startupButton.equals(actionEvent.getSource())) {
            service.startup(port.getText());
        }
        if (shutdownButton.equals(actionEvent.getSource())) {
            service.shutdown();
        }
    }


    private void messageOut(final Object object) {
        if (SwingUtilities.isEventDispatchThread()) {
            defaultListModel.addElement(object);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    defaultListModel.addElement(object);
                }
            });
        }
    }


    private class Service implements Runnable {


        private ServerSocketChannel serverSocketChannel;
        private Selector selector;


        public void run() {
            try {
                while (selector.select() > 0) {
                    for (Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); iterator.hasNext();) {
                        SelectionKey selectionKey = iterator.next();
                        iterator.remove();
                        if (selectionKey.isAcceptable()) {
                            doAccept(selectionKey);
                        } else if (selectionKey.isReadable()) {
                            doResponse(selectionKey);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        private void startup(String port) {
            if (serverSocketChannel == null || !serverSocketChannel.isOpen()) {
                try {
                    serverSocketChannel = ServerSocketChannel.open();
                    serverSocketChannel.configureBlocking(false);
                    serverSocketChannel.socket().bind(new InetSocketAddress(Integer.parseInt(port)));
                    selector = Selector.open();
                    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                    new Thread(this).start();
                    messageOut("起動しました。");
                } catch (Exception e) {
                    try {
                        serverSocketChannel.close();
                    } catch (Exception e2) {}
                    messageOut("起動できませんでした。");
                }
            } else {
                messageOut("起動しています。");
            }
        }


        private void shutdown() {
            if (serverSocketChannel != null && serverSocketChannel.isOpen()) {
                try {
                    selector.wakeup();
                    serverSocketChannel.close();
                    messageOut("停止しました。");
                } catch (Exception e) {}
            } else {
                messageOut("起動していません。");
            }
        }


        private void doAccept(SelectionKey selectionKey) {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            try {
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        private void doResponse(SelectionKey selectionKey) {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            try {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                Charset charset = Charset.forName("Shift-JIS");
                socketChannel.read(byteBuffer);
                byteBuffer.flip();
                messageOut(charset.decode(byteBuffer));
                socketChannel.write(charset.encode("response from server"));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    socketChannel.close();
                } catch (Exception e) {}
            }
        }


    }


}



セレクタへのチャンネルの登録はregisterメソッドにより行います。
最初はServerSocketChannelに非ブロックの設定を行い、セレクタに登録しています。
このとき、このチャンネルが接続要求を受け取ることができる場合に、選択されるように設定しています。(registerメソッドの第二引数で設定)
このチャンネルが接続要求を受け取ることができる状態であれば、selectメソッドにより選択されます。
selectedKeysメソッドは選択されたチャンネルのキー(SelectionKey)の集合を返します。
SelectionKeyのchannelメソッドによりチャンネルを取得し、非ブロックのacceptメソッドでSocketChannelを取得しています。


次に取得したSocketChannelに非ブロックの設定を行い、セレクタに登録しています。
このとき、このチャンネルが読み込み可能である場合に、選択されるように設定しています。
このチャンネルが選択されたあとに、非ブロックのreadメソッドでクライアントからのメッセージを読み込んでいます。


このサンプルの利点はacceptメソッドやreadメソッドによる待機が発生しないため、前々回のようにクライアントの接続毎にスレッドを起動する必要がないことです。
チャンネルが選択されたときには、待機なしで入出力処理が行える状態なので、1つのスレッドで選択されたチャンネルから順に処理を行っていけばよいわけです。


[Javaワンポイント]内の前後の記事
第 111 回 ~ ソケットチャンネルの入出力 ~
→ 第 110 回 ~ ソケットチャンネルの入出力 ~
第 109 回 ~ ソケットチャンネルの入出力 ~


■更新日時での前後の記事
7月1日 お天気
→ 第 110 回 ~ ソケットチャンネルの入出力 ~
6月30日 お天気