Index: webrtc/examples/androidapp/src/org/appspot/apprtc/TCPChannelClient.java |
diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/TCPChannelClient.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/TCPChannelClient.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d348cda653c0f7edffa288424f389b2d188d4bf4 |
--- /dev/null |
+++ b/webrtc/examples/androidapp/src/org/appspot/apprtc/TCPChannelClient.java |
@@ -0,0 +1,333 @@ |
+/* |
+ * Copyright 2015 The WebRTC Project Authors. All rights reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+package org.appspot.apprtc; |
+ |
+import android.util.Log; |
+ |
+import org.appspot.apprtc.util.LooperExecutor; |
+ |
+import java.io.BufferedReader; |
+import java.io.IOException; |
+import java.io.InputStreamReader; |
+import java.io.PrintWriter; |
+import java.net.InetAddress; |
+import java.net.ServerSocket; |
+import java.net.Socket; |
+import java.net.UnknownHostException; |
+ |
+/** |
+ * Replacement for WebSocketChannelClient for direct communication between two IP addresses. Handles |
+ * the signaling between the two clients using a TCP connection. |
+ * |
+ * <p>All public methods should be called from a looper executor thread |
+ * passed in a constructor, otherwise exception will be thrown. |
+ * All events are dispatched on the same thread. |
+ */ |
+public class TCPChannelClient { |
+ private static final String TAG = "TCPChannelClient"; |
+ |
+ private final LooperExecutor executor; |
+ private final TCPChannelEvents eventListener; |
+ private TCPSocket socket; |
+ |
+ /** |
+ * Callback interface for messages delivered on TCP Connection. All callbacks are invoked from the |
+ * looper executor thread. |
+ */ |
+ public interface TCPChannelEvents { |
+ void onTCPConnected(boolean server); |
+ void onTCPMessage(String message); |
+ void onTCPError(String description); |
+ void onTCPClose(); |
+ } |
+ |
+ /** |
+ * Initializes the TCPChannelClient. If IP is a local IP address, starts a listening server on |
+ * that IP. If not, instead connects to the IP. |
+ * |
+ * @param eventListener Listener that will receive events from the client. |
+ * @param ip IP address to listen on or connect to. |
+ * @param port Port to listen on or connect to. |
+ */ |
+ public TCPChannelClient( |
+ LooperExecutor executor, TCPChannelEvents eventListener, String ip, int port) { |
+ this.executor = executor; |
+ this.eventListener = eventListener; |
+ |
+ InetAddress address; |
+ try { |
+ address = InetAddress.getByName(ip); |
+ } catch (UnknownHostException e) { |
+ reportError("Invalid IP address."); |
+ return; |
+ } |
+ |
+ if (address.isAnyLocalAddress()) { |
+ socket = new TCPSocketServer(address, port); |
+ } else { |
+ socket = new TCPSocketClient(address, port); |
+ } |
+ |
+ socket.start(); |
+ } |
+ |
+ /** |
+ * Disconnects the client if not already disconnected. This will fire the onTCPClose event. |
+ */ |
+ public void disconnect() { |
+ checkIfCalledOnValidThread(); |
+ |
+ socket.disconnect(); |
+ } |
+ |
+ /** |
+ * Sends a message on the socket. |
+ * |
+ * @param message Message to be sent. |
+ */ |
+ public void send(String message) { |
+ checkIfCalledOnValidThread(); |
+ |
+ socket.send(message); |
+ } |
+ |
+ /** |
+ * Helper method for firing onTCPError events. Calls onTCPError on the executor thread. |
+ */ |
+ private void reportError(final String message) { |
+ executor.execute(new Runnable() { |
+ @Override |
+ public void run() { |
+ eventListener.onTCPError(message); |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Helper method for debugging purposes. |
+ * Ensures that TCPChannelClient method is called on a looper thread. |
+ */ |
+ private void checkIfCalledOnValidThread() { |
+ if (!executor.checkOnLooperThread()) { |
+ throw new IllegalStateException( |
+ "TCPChannelClient method is not called on valid thread"); |
+ } |
+ } |
+ |
+ |
+ /** |
+ * Base class for server and client sockets. Contains a listening thread that will call |
+ * eventListener.onTCPMessage on new messages. |
+ */ |
+ private abstract class TCPSocket extends Thread { |
+ // Lock for editing out and rawSocket |
+ protected final Object rawSocketLock; |
+ private PrintWriter out; |
+ private Socket rawSocket; |
+ |
+ /** |
+ * Connect to the peer, potentially a slow operation. |
+ * |
+ * @return Socket connection, null if connection failed. |
+ */ |
+ public abstract Socket connect(); |
+ /** Returns true if sockets is a server rawSocket. */ |
+ public abstract boolean isServer(); |
+ |
+ TCPSocket() { |
+ rawSocketLock = new Object(); |
+ } |
+ |
+ /** |
+ * The listening thread. |
+ */ |
+ @Override |
+ public void run() { |
+ // Receive connection to temporary variable first, so we don't block |
+ Socket tempSocket = connect(); |
+ BufferedReader in; |
+ |
+ synchronized (rawSocketLock) { |
+ if (rawSocket != null) { |
+ Log.e(TAG, "Socket already existed and will be replaced."); |
+ } |
+ |
+ rawSocket = tempSocket; |
+ |
+ // Connecting failed, error has already been reported, just exit |
+ if (rawSocket == null) { |
+ return; |
+ } |
+ |
+ try { |
+ out = new PrintWriter(rawSocket.getOutputStream(), true); |
+ in = new BufferedReader(new InputStreamReader(rawSocket.getInputStream())); |
+ } catch (IOException e) { |
+ reportError("Failed to open IO on rawSocket: " + e.getMessage()); |
+ return; |
+ } |
+ } |
+ |
+ executor.execute(new Runnable() { |
+ @Override |
+ public void run() { |
+ eventListener.onTCPConnected(isServer()); |
+ } |
+ }); |
+ |
+ String message; |
+ while (true) { |
+ try { |
+ message = in.readLine(); |
+ } catch (IOException e) { |
+ reportError("Failed to read from rawSocket: " + e.getMessage()); |
+ return; |
magjed_webrtc
2016/05/10 14:50:02
break instead of return?
sakal
2016/05/11 08:38:50
Yep, you're right.
|
+ } |
+ |
+ // No data received, rawSocket probably closed |
+ if (message == null) { |
+ break; |
+ } |
+ |
+ // We need final variable for the Runnable |
+ final String executorMessage = message; |
magjed_webrtc
2016/05/10 14:50:03
Move declaration of |message| inside while loop an
sakal
2016/05/11 08:38:50
Done.
|
+ executor.execute(new Runnable() { |
+ @Override |
+ public void run() { |
+ Log.v(TAG, "Receive: " + executorMessage); |
+ eventListener.onTCPMessage(executorMessage); |
+ } |
+ }); |
+ } |
+ |
+ Log.d(TAG, "Receiving thread exiting..."); |
+ |
+ // Close the rawSocket if it is still open |
+ disconnect(); |
+ } |
+ |
+ /** |
+ * Closes the rawSocket if it is still open. Also fires the onTCPClose event. |
+ */ |
+ public void disconnect() { |
+ try { |
+ synchronized (rawSocketLock) { |
+ if (rawSocket != null) { |
+ rawSocket.close(); |
+ rawSocket = null; |
+ out = null; |
+ |
+ eventListener.onTCPClose(); |
+ } |
+ } |
+ } catch (IOException e) { |
magjed_webrtc
2016/05/10 14:50:03
Catch just rawSocket.close() so that eventListener
sakal
2016/05/11 08:38:50
This is something I thought about. eventListener.o
magjed_webrtc
2016/05/11 11:22:55
Probably not. Then the code is good as it is.
|
+ reportError("Failed to close rawSocket: " + e.getMessage()); |
+ } |
+ } |
+ |
+ /** |
+ * Sends a message on the socket. Should only be called on the executor thread. |
+ * |
+ * @param message |
+ */ |
+ public void send(String message) { |
+ Log.v(TAG, "Send: " + message); |
+ |
+ synchronized (rawSocketLock) { |
+ if (out == null) { |
+ Log.e(TAG, "Sending data on closed socket."); |
magjed_webrtc
2016/05/10 14:50:03
You need to return here, otherwise it will crash o
sakal
2016/05/11 08:38:50
Correct, fixed. Also changed this to reportError b
|
+ } |
+ |
+ out.write(message + "\n"); |
+ out.flush(); |
+ } |
+ } |
+ } |
+ |
+ private class TCPSocketServer extends TCPSocket { |
+ // Server socket also guarded by rawSocketLock |
+ private ServerSocket serverSocket; |
+ |
+ private InetAddress address; |
magjed_webrtc
2016/05/10 14:50:03
Make these variables final.
sakal
2016/05/11 08:38:50
Done.
|
+ private int port; |
+ |
+ public TCPSocketServer(InetAddress address, int port) { |
+ this.address = address; |
+ this.port = port; |
+ } |
+ |
+ /** Opens a listening socket and waits for a connection. */ |
+ public Socket connect() { |
+ Log.d(TAG, "Listening on " + address.getHostAddress() + ":" + Integer.toString(port)); |
+ |
+ synchronized (rawSocketLock) { |
+ if (serverSocket != null) { |
+ Log.e(TAG, "Server rawSocket was already listening and new will be opened."); |
+ } |
+ |
+ try { |
+ serverSocket = new ServerSocket(port, 0, address); |
+ return serverSocket.accept(); |
+ } catch (IOException e) { |
+ reportError("Failed to receive connection: " + e.getMessage()); |
+ return null; |
+ } |
+ } |
+ } |
+ |
+ /** Closes the listening socket and calls super. */ |
+ @Override |
+ public void disconnect() { |
+ try { |
+ synchronized (rawSocketLock) { |
+ if (serverSocket != null) { |
+ serverSocket.close(); |
+ serverSocket = null; |
+ } |
+ } |
+ } catch (IOException e) { |
+ reportError("Failed to close server socket: " + e.getMessage()); |
+ } |
+ |
+ super.disconnect(); |
+ } |
+ |
+ public boolean isServer() { |
magjed_webrtc
2016/05/10 14:50:03
add @Override?
sakal
2016/05/11 08:38:50
Done.
|
+ return true; |
+ } |
+ } |
+ |
+ private class TCPSocketClient extends TCPSocket { |
+ private InetAddress address; |
magjed_webrtc
2016/05/10 14:50:02
Make these variables final.
sakal
2016/05/11 08:38:50
Done.
|
+ private int port; |
+ |
+ public TCPSocketClient(InetAddress address, int port) { |
+ this.address = address; |
+ this.port = port; |
+ } |
+ |
+ /** Connects to the peer. */ |
+ public Socket connect() { |
magjed_webrtc
2016/05/10 14:50:03
add @Override?
sakal
2016/05/11 08:38:50
Done.
|
+ Log.d(TAG, "Connecting to [" + address.getHostAddress() + "]:" + Integer.toString(port)); |
+ |
+ try { |
+ return new Socket(address, port); |
+ } catch (IOException e) { |
+ reportError("Failed to connect: " + e.getMessage()); |
+ return null; |
+ } |
+ } |
+ |
+ public boolean isServer() { |
magjed_webrtc
2016/05/10 14:50:03
add @Override?
sakal
2016/05/11 08:38:50
Done.
|
+ return false; |
+ } |
+ } |
+} |