可中断套接字(网络)
当连接到一个套接字时,当前线程将会被阻塞直到建立连接或产生超时为止。同样地,当通过套接字读写数据时,当前线程也会被阻塞知道操作成功或产生超时为止。
在交互式的应用中,也许会考虑为用户提供一个选项,用以取消那些不会成功的连接。但是当线程因套接字长时间无法响应而发生阻塞时,无法通过调用interrupt来解除阻塞。
为了中断套接字操作,可以使用java.nio包提供的一个特性——SocketChannel 类。可以使用如下方法打开SocketChannel:
SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port));
通道(channel)并没有与之相关联的流。实际上,它所拥有的read和write方法都是通过使用Buffer对象来实现的。ReadableByteChannel接口和WritableByteChannel接口都声明了这两个方法。
如果不想处理缓冲区,可以使用Scanner类从SocketChannel中读取信息,因为Scanner有一个带有ReadableByteChannel参数的构造器。
Scanner in = new Scanner(channel);
通过调用静态方法Channels.newOutputStream,可以将通道转换成输出流。
OutputStream outStream = Channels.newOutputStream(channel);
上述操作都是必须做的。假设线程正在执行打开、读取或写入操作,此时如果线程发生中断,那么这些操作将不会陷入阻塞,而是以抛出异常的方式结束。
下面的程序对比了可中断套接字和阻塞套接字:服务器将连续发送数字,并在每发送十个数字之后停滞一下。点击两个按钮中的任何一个,都会启动一个线程来连连接服务器并打印输出。第一个线程使用可中断套接字,而第二个线程使用阻塞套接字。如果在第一批的十个数字的读取过程中点击“Cancel”按钮,这两个线程都会中断。
但是,在第一批十个数字之后,就只能中断第一个线程了,第二个线程将保持阻塞直到服务器最终关闭连接。
package interruptible; import java.awt.*; import java.awt.event.*; import java.util.*; import java.net.*; import java.io.*; import java.nio.channels.*; import javax.swing.*; /** * This program shows how to interrupt a socket channel. * @author Cay Horstmann * @version 1.03 2012-06-04 */ public class InterruptibleSocketTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new InterruptibleSocketFrame(); frame.setTitle("InterruptibleSocketTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class InterruptibleSocketFrame extends JFrame { public static final int TEXT_ROWS = 20; public static final int TEXT_COLUMNS = 60; private Scanner in; private JButton interruptibleButton; private JButton blockingButton; private JButton cancelButton; private JTextArea messages; private TestServer server; private Thread connectThread; public InterruptibleSocketFrame() { JPanel northPanel = new JPanel(); add(northPanel, BorderLayout.NORTH); messages = new JTextArea(TEXT_ROWS, TEXT_COLUMNS); add(new JScrollPane(messages)); interruptibleButton = new JButton("Interruptible"); blockingButton = new JButton("Blocking"); northPanel.add(interruptibleButton); northPanel.add(blockingButton); interruptibleButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { interruptibleButton.setEnabled(false); blockingButton.setEnabled(false); cancelButton.setEnabled(true); connectThread = new Thread(new Runnable() { public void run() { try { connectInterruptibly(); } catch (IOException e) { messages.append("\nInterruptibleSocketTest.connectInterruptibly: " + e); } } }); connectThread.start(); } }); blockingButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { interruptibleButton.setEnabled(false); blockingButton.setEnabled(false); cancelButton.setEnabled(true); connectThread = new Thread(new Runnable() { public void run() { try { connectBlocking(); } catch (IOException e) { messages.append("\nInterruptibleSocketTest.connectBlocking: " + e); } } }); connectThread.start(); } }); cancelButton = new JButton("Cancel"); cancelButton.setEnabled(false); northPanel.add(cancelButton); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { connectThread.interrupt(); cancelButton.setEnabled(false); } }); server = new TestServer(); new Thread(server).start(); pack(); } /** * Connects to the test server, using interruptible I/O */ public void connectInterruptibly() throws IOException { messages.append("Interruptible:\n"); try (SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", 8189))) { in = new Scanner(channel); while (!Thread.currentThread().isInterrupted()) { messages.append("Reading "); if (in.hasNextLine()) { String line = in.nextLine(); messages.append(line); messages.append("\n"); } } } finally { EventQueue.invokeLater(new Runnable() { public void run() { messages.append("Channel closed\n"); interruptibleButton.setEnabled(true); blockingButton.setEnabled(true); } }); } } /** * Connects to the test server, using blocking I/O */ public void connectBlocking() throws IOException { messages.append("Blocking:\n"); try (Socket sock = new Socket("localhost", 8189)) { in = new Scanner(sock.getInputStream()); while (!Thread.currentThread().isInterrupted()) { messages.append("Reading "); if (in.hasNextLine()) { String line = in.nextLine(); messages.append(line); messages.append("\n"); } } } finally { EventQueue.invokeLater(new Runnable() { public void run() { messages.append("Socket closed\n"); interruptibleButton.setEnabled(true); blockingButton.setEnabled(true); } }); } } /** * A multithreaded server that listens to port 8189 and sends numbers to the client, simulating a * hanging server after 10 numbers. */ class TestServer implements Runnable { public void run() { try { ServerSocket s = new ServerSocket(8189); while (true) { Socket incoming = s.accept(); Runnable r = new TestServerHandler(incoming); Thread t = new Thread(r); t.start(); } } catch (IOException e) { messages.append("\nTestServer.run: " + e); } } } /** * This class handles the client input for one server socket connection. */ class TestServerHandler implements Runnable { private Socket incoming; private int counter; /** * Constructs a handler. * @param i the incoming socket */ public TestServerHandler(Socket i) { incoming = i; } public void run() { try { try { OutputStream outStream = incoming.getOutputStream(); PrintWriter out = new PrintWriter(outStream, true /* autoFlush */); while (counter < 100) { counter++; if (counter <= 10) out.println(counter); Thread.sleep(100); } } finally { incoming.close(); messages.append("Closing server\n"); } } catch (Exception e) { messages.append("\nTestServerHandler.run: " + e); } } } }