読者です 読者をやめる 読者になる 読者になる

そもそもAndroidOS2.2以前では別ThreadからのSocket#close()が効かない件

android socket

http://d.hatena.ne.jp/komamitsu/20111103/1320338412 の続きで少し見てみたら、HttpRequestBase#abort() -> AbstractClientConnAdapter#abortConnection() -> SocketHttpClientConnection#shutdown() -> Socket#close() と来ているので、やっぱりこの辺かなぁと。

今度はこんなのを作って確かめてみました。

public class SocketAbortAndroidActivity extends Activity {
  private static final String TAG = SocketAbortAndroidActivity.class.getSimpleName();
  private final String HOSTNAME = "10.0.2.2";
  private final int PORT = 8080;
  private Socket socket;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    try {
      socket = new Socket(HOSTNAME, PORT);
    } catch (UnknownHostException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }

    ExecutorService es = Executors.newSingleThreadExecutor();
    es.execute(new Runnable() {
      @Override
      public void run() {
        try {
          OutputStream output = socket.getOutputStream();
          output.write("Hello, World!".getBytes());
          InputStream input = socket.getInputStream();
          byte[] buf = new byte[256];
          input.read(buf);
          Log.d(TAG, "received => " + buf);
        } catch (UnknownHostException e) {
          e.printStackTrace();
        } catch (IOException e) {
          e.printStackTrace();
        } finally {
          Log.d(TAG, "Finish");
        }
      }
    });

    try {
      Thread.sleep(5 * 1000);
    } catch (InterruptedException e) {}

    try {
      socket.close();
    } catch (IOException e) {
      e.printStackTrace();
    }

    Log.i(TAG, "Close");

    try {
      Thread.sleep(5 * 1000);
    } catch (InterruptedException e) {}

    es.shutdown();
  }
}

OS2.3.3のEmulatorではclose()されると前回同様以下のようにExceptionが発生するのですが、

I/SocketAbortAndroidActivity(  357): Close
W/System.err(  357): java.net.SocketException: Socket closed
W/System.err(  357):    at org.apache.harmony.luni.platform.OSNetworkSystem.read(Native Method)
W/System.err(  357):    at dalvik.system.BlockGuard$WrappedNetworkSystem.read(BlockGuard.java:273)
W/System.err(  357):    at org.apache.harmony.luni.net.PlainSocketImpl.read(PlainSocketImpl.java:458)
W/System.err(  357):    at org.apache.harmony.luni.net.SocketInputStream.read(SocketInputStream.java:85)
W/System.err(  357):    at org.apache.harmony.luni.net.SocketInputStream.read(SocketInputStream.java:65)
W/System.err(  357):    at com.komamitsu.SocketAbortAndroidActivity$1.run(SocketAbortAndroidActivity.java:43)
W/System.err(  357):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
W/System.err(  357):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
W/System.err(  357):    at java.lang.Thread.run(Thread.java:1019)
D/SocketAbortAndroidActivity(  357): Finish

OS2.2ではException発生せず... というかTCPレベルでFINが飛んでないす。

# tcpdump -n tcp port 8080
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes

00:36:00.607484 IP 10.0.2.15.51333 > 10.0.2.2.8080: S 3496404316:3496404316(0) win 5840 <mss 1460,sackOK,timestamp 128072 0,nop,wscale 1>
00:36:00.608027 IP 10.0.2.2.8080 > 10.0.2.15.51333: S 203136001:203136001(0) ack 3496404317 win 8192 <mss 1460>
00:36:00.608117 IP 10.0.2.15.51333 > 10.0.2.2.8080: . ack 1 win 5840
00:36:00.637904 IP 10.0.2.15.51333 > 10.0.2.2.8080: P 1:14(13) ack 1 win 5840
00:36:00.638026 IP 10.0.2.2.8080 > 10.0.2.15.51333: . ack 14 win 8760
(ずっとこのまま...)

明日(というか今日か...)、子供の遊び相手の隙をついてちゃんとAndroidソースコードを見たいところ...


追記 2011-11-06 22:54

まぁ、結構どうでも良いのだけども、2.2のばあい、
org.apache.harmony.luni.net.PlainSocketImpl#connect() -> org.apache.harmony.luni.platform.OSNetworkSystem#connectWithTimeoutSocketImpl() -> (native) osNetworkSystem_connectWithTimeoutSocketImpl() -> (native) sockConnectWithTimeout() -> (native) doConnect() -> connect()
とconnect(2)を呼んでいるんだけども、その直前で

        /* set the socket to non-blocking */
        int block = JNI_TRUE;
        rc = ioctl(handle, FIONBIO, &block);

とsocketをnon-blockingにしている。

2.3のほうは、この辺り (libcore/luni/src/main/native) のコードの構成がガラリと変わっているんだけども結局は

    if (!blocking) {
        flags |= O_NONBLOCK;
    } else {
        flags &= ~O_NONBLOCK;
    }
    int rc = fcntl(fd, F_SETFL, flags);

とかやってる(non-blockingにしている)。