第98回 ソケットの入出力
引き続き「ソケットの入出力」について見ていきます。
今回は前回の続きです。
前回はサーバ側のプログラムを実装したので、今回はクライアント側のプログラムを実装してみたいと思います。
クライアント側もサーバ側と同様、ソケットのストリームに対して読み書きを行うことで通信を行います。
クライアント側、またはサーバ側は、相手側が出力ストリームに書き込みフラッシュするのを待機して、入力ストリームから読み込みます。
また、クライアント側、またはサーバ側がソケットを閉じた場合における相手側の入力ストリームからの読み込みについては、BufferedReaderのreadLineメソッドの場合はnullを返すようです。
次のサンプルコードは、前回作成したサーバに接続し、通信を行うプログラムです。
Swingアプリケーションとして実装しました。
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; 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) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { UIManager.setLookAndFeel(UIManager .getSystemLookAndFeelClassName()); } catch (Exception e) {} new Main(); } }); } private final Session session = new Session(); 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 JPanel jPanel = new JPanel(); private final JButton connectButton = new JButton("接続"); private final JButton submitButton = new JButton("送信"); private final JButton closeButton = new JButton("切断"); public Main() { connectButton.addActionListener(this); closeButton.addActionListener(this); submitButton.addActionListener(this); jPanel.add(port); jPanel.add(jScrollPane); jPanel.add(connectButton); jPanel.add(submitButton); jPanel.add(closeButton); setContentPane(jPanel); setTitle("CLIENT"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300, 225); setResizable(false); setLocationRelativeTo(null); setVisible(true); } public void actionPerformed(ActionEvent actionEvent) { if (connectButton.equals(actionEvent.getSource())) { session.connect(port.getText()); } else if (submitButton.equals(actionEvent.getSource())) { session.submit(); } else if (closeButton.equals(actionEvent.getSource())) { session.close(); } } 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 Session implements Runnable { private ExecutorService executorService = Executors.newCachedThreadPool(); private String port; private Socket socket; private Lock lock = new ReentrantLock(); public void run() { if (lock.tryLock()) { try { socket = new Socket("localhost", Integer.parseInt(port)); executorService.execute(new Response(socket)); messageOut("接続しました。(client:" + socket .getLocalPort() + ")"); } catch (Exception e) { messageOut("接続できませんでした。"); } finally { lock.unlock(); } } else { messageOut("接続中です。しばらくお待ちください。"); } } private void connect(String port) { if (!isConnected()) { this.port = port; executorService.execute(this); } else { messageOut("既に接続しています。"); } } private void submit() { if (isConnected()) { executorService.execute(new Request(socket)); } else { messageOut("接続していません。"); } } private void close() { if (isConnected()) { try { socket.close(); messageOut("切断しました。"); } catch (Exception e) {} } else { messageOut("接続していません。"); } } private boolean isConnected() { if (lock.tryLock()) { try { return socket != null && socket.isConnected() && !socket.isClosed(); } finally { lock.unlock(); } } return false; } } private class Response implements Runnable { private Socket socket; private Response(Socket socket) { this.socket = socket; } public void run() { try { InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader (new InputStreamReader(inputStream, "Shift-JIS")); String responseText = null; while ((responseText = bufferedReader.readLine()) != null) { messageOut(responseText); } } catch (Exception e) { e.printStackTrace(); } finally { messageOut("通信終了"); try { socket.close(); } catch (Exception e) {} } } } private class Request implements Runnable { private Socket socket; private Request(Socket socket) { this.socket = socket; } public void run() { try { OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter (new OutputStreamWriter(outputStream,"Shift-JIS") , true); printWriter.println("request from client:" + socket.getLocalPort()); } catch (Exception e) { e.printStackTrace(); } } } }
接続先は「localhost」としているので、サーバプログラムとクライアントプログラムは同じマシン上で動かして下さい。
前回実装したプログラムはObserverパターンを使用しています。
起動中に「停止」ボタンが押された場合、notifyObserversメソッドを実行し、全てのソケットを閉じます。
これにより全てのクライアント側のreadLineメソッドはnullを返し、通信が終了するようになっています。
クライアント側のプログラムの動きは以下のようになります。
サーバのポート番号を指定し、「接続」ボタンが押された場合、Socketインスタンスを生成します。
サンプルコードの場合、この時点でサーバ側のacceptメソッドが接続要求を受け取ります。
接続中に「送信」ボタンが押された場合、送信する内容を出力ストリームに書き込み、フラッシュします。
接続中に「切断」ボタンが押された場合、ソケットを閉じます。
これによりサーバ側のreadLineメソッドはnullを返し、通信が終了するようになっています。