OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. |
| 3 * |
| 4 * Use of this source code is governed by a BSD-style license |
| 5 * that can be found in the LICENSE file in the root of the source |
| 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ |
| 10 |
| 11 package org.appspot.apprtc; |
| 12 |
| 13 import android.util.Log; |
| 14 |
| 15 import org.appspot.apprtc.util.LooperExecutor; |
| 16 |
| 17 import java.io.BufferedReader; |
| 18 import java.io.IOException; |
| 19 import java.io.InputStreamReader; |
| 20 import java.io.PrintWriter; |
| 21 import java.net.InetAddress; |
| 22 import java.net.ServerSocket; |
| 23 import java.net.Socket; |
| 24 import java.net.UnknownHostException; |
| 25 |
| 26 /** |
| 27 * Replacement for WebSocketChannelClient for direct communication between two I
P addresses. Handles |
| 28 * the signaling between the two clients using a TCP connection. |
| 29 * |
| 30 * <p>All public methods should be called from a looper executor thread |
| 31 * passed in a constructor, otherwise exception will be thrown. |
| 32 * All events are dispatched on the same thread. |
| 33 */ |
| 34 public class TCPChannelClient { |
| 35 private static final String TAG = "TCPChannelClient"; |
| 36 |
| 37 private final LooperExecutor executor; |
| 38 private final TCPChannelEvents eventListener; |
| 39 private TCPSocket socket; |
| 40 |
| 41 /** |
| 42 * Callback interface for messages delivered on TCP Connection. All callbacks
are invoked from the |
| 43 * looper executor thread. |
| 44 */ |
| 45 public interface TCPChannelEvents { |
| 46 void onTCPConnected(boolean server); |
| 47 void onTCPMessage(String message); |
| 48 void onTCPError(String description); |
| 49 void onTCPClose(); |
| 50 } |
| 51 |
| 52 /** |
| 53 * Initializes the TCPChannelClient. If IP is a local IP address, starts a lis
tening server on |
| 54 * that IP. If not, instead connects to the IP. |
| 55 * |
| 56 * @param eventListener Listener that will receive events from the client. |
| 57 * @param ip IP address to listen on or connect to. |
| 58 * @param port Port to listen on or connect to. |
| 59 */ |
| 60 public TCPChannelClient( |
| 61 LooperExecutor executor, TCPChannelEvents eventListener, String ip, int po
rt) { |
| 62 this.executor = executor; |
| 63 this.eventListener = eventListener; |
| 64 |
| 65 InetAddress address; |
| 66 try { |
| 67 address = InetAddress.getByName(ip); |
| 68 } catch (UnknownHostException e) { |
| 69 reportError("Invalid IP address."); |
| 70 return; |
| 71 } |
| 72 |
| 73 if (address.isAnyLocalAddress()) { |
| 74 socket = new TCPSocketServer(address, port); |
| 75 } else { |
| 76 socket = new TCPSocketClient(address, port); |
| 77 } |
| 78 |
| 79 socket.start(); |
| 80 } |
| 81 |
| 82 /** |
| 83 * Disconnects the client if not already disconnected. This will fire the onTC
PClose event. |
| 84 */ |
| 85 public void disconnect() { |
| 86 checkIfCalledOnValidThread(); |
| 87 |
| 88 socket.disconnect(); |
| 89 } |
| 90 |
| 91 /** |
| 92 * Sends a message on the socket. |
| 93 * |
| 94 * @param message Message to be sent. |
| 95 */ |
| 96 public void send(String message) { |
| 97 checkIfCalledOnValidThread(); |
| 98 |
| 99 socket.send(message); |
| 100 } |
| 101 |
| 102 /** |
| 103 * Helper method for firing onTCPError events. Calls onTCPError on the executo
r thread. |
| 104 */ |
| 105 private void reportError(final String message) { |
| 106 Log.e(TAG, "TCP Error: " + message); |
| 107 executor.execute(new Runnable() { |
| 108 @Override |
| 109 public void run() { |
| 110 eventListener.onTCPError(message); |
| 111 } |
| 112 }); |
| 113 } |
| 114 |
| 115 /** |
| 116 * Helper method for debugging purposes. |
| 117 * Ensures that TCPChannelClient method is called on a looper thread. |
| 118 */ |
| 119 private void checkIfCalledOnValidThread() { |
| 120 if (!executor.checkOnLooperThread()) { |
| 121 throw new IllegalStateException( |
| 122 "TCPChannelClient method is not called on valid thread"); |
| 123 } |
| 124 } |
| 125 |
| 126 |
| 127 /** |
| 128 * Base class for server and client sockets. Contains a listening thread that
will call |
| 129 * eventListener.onTCPMessage on new messages. |
| 130 */ |
| 131 private abstract class TCPSocket extends Thread { |
| 132 // Lock for editing out and rawSocket |
| 133 protected final Object rawSocketLock; |
| 134 private PrintWriter out; |
| 135 private Socket rawSocket; |
| 136 |
| 137 /** |
| 138 * Connect to the peer, potentially a slow operation. |
| 139 * |
| 140 * @return Socket connection, null if connection failed. |
| 141 */ |
| 142 public abstract Socket connect(); |
| 143 /** Returns true if sockets is a server rawSocket. */ |
| 144 public abstract boolean isServer(); |
| 145 |
| 146 TCPSocket() { |
| 147 rawSocketLock = new Object(); |
| 148 } |
| 149 |
| 150 /** |
| 151 * The listening thread. |
| 152 */ |
| 153 @Override |
| 154 public void run() { |
| 155 Log.d(TAG, "Listening thread started..."); |
| 156 |
| 157 // Receive connection to temporary variable first, so we don't block. |
| 158 Socket tempSocket = connect(); |
| 159 BufferedReader in; |
| 160 |
| 161 Log.d(TAG, "TCP connection established."); |
| 162 |
| 163 synchronized (rawSocketLock) { |
| 164 if (rawSocket != null) { |
| 165 Log.e(TAG, "Socket already existed and will be replaced."); |
| 166 } |
| 167 |
| 168 rawSocket = tempSocket; |
| 169 |
| 170 // Connecting failed, error has already been reported, just exit. |
| 171 if (rawSocket == null) { |
| 172 return; |
| 173 } |
| 174 |
| 175 try { |
| 176 out = new PrintWriter(rawSocket.getOutputStream(), true); |
| 177 in = new BufferedReader(new InputStreamReader(rawSocket.getInputStream
())); |
| 178 } catch (IOException e) { |
| 179 reportError("Failed to open IO on rawSocket: " + e.getMessage()); |
| 180 return; |
| 181 } |
| 182 } |
| 183 |
| 184 Log.v(TAG, "Execute onTCPConnected"); |
| 185 executor.execute(new Runnable() { |
| 186 @Override |
| 187 public void run() { |
| 188 Log.v(TAG, "Run onTCPConnected"); |
| 189 eventListener.onTCPConnected(isServer()); |
| 190 } |
| 191 }); |
| 192 |
| 193 while (true) { |
| 194 final String message; |
| 195 try { |
| 196 message = in.readLine(); |
| 197 } catch (IOException e) { |
| 198 synchronized (rawSocketLock) { |
| 199 // If socket was closed, this is expected. |
| 200 if (rawSocket == null) { |
| 201 break; |
| 202 } |
| 203 } |
| 204 |
| 205 reportError("Failed to read from rawSocket: " + e.getMessage()); |
| 206 break; |
| 207 } |
| 208 |
| 209 // No data received, rawSocket probably closed. |
| 210 if (message == null) { |
| 211 break; |
| 212 } |
| 213 |
| 214 executor.execute(new Runnable() { |
| 215 @Override |
| 216 public void run() { |
| 217 Log.v(TAG, "Receive: " + message); |
| 218 eventListener.onTCPMessage(message); |
| 219 } |
| 220 }); |
| 221 } |
| 222 |
| 223 Log.d(TAG, "Receiving thread exiting..."); |
| 224 |
| 225 // Close the rawSocket if it is still open. |
| 226 disconnect(); |
| 227 } |
| 228 |
| 229 /** |
| 230 * Closes the rawSocket if it is still open. Also fires the onTCPClose event
. |
| 231 */ |
| 232 public void disconnect() { |
| 233 try { |
| 234 synchronized (rawSocketLock) { |
| 235 if (rawSocket != null) { |
| 236 rawSocket.close(); |
| 237 rawSocket = null; |
| 238 out = null; |
| 239 |
| 240 executor.execute(new Runnable() { |
| 241 @Override |
| 242 public void run() { |
| 243 eventListener.onTCPClose(); |
| 244 } |
| 245 }); |
| 246 } |
| 247 } |
| 248 } catch (IOException e) { |
| 249 reportError("Failed to close rawSocket: " + e.getMessage()); |
| 250 } |
| 251 } |
| 252 |
| 253 /** |
| 254 * Sends a message on the socket. Should only be called on the executor thre
ad. |
| 255 */ |
| 256 public void send(String message) { |
| 257 Log.v(TAG, "Send: " + message); |
| 258 |
| 259 synchronized (rawSocketLock) { |
| 260 if (out == null) { |
| 261 reportError("Sending data on closed socket."); |
| 262 return; |
| 263 } |
| 264 |
| 265 out.write(message + "\n"); |
| 266 out.flush(); |
| 267 } |
| 268 } |
| 269 } |
| 270 |
| 271 private class TCPSocketServer extends TCPSocket { |
| 272 // Server socket is also guarded by rawSocketLock. |
| 273 private ServerSocket serverSocket; |
| 274 |
| 275 final private InetAddress address; |
| 276 final private int port; |
| 277 |
| 278 public TCPSocketServer(InetAddress address, int port) { |
| 279 this.address = address; |
| 280 this.port = port; |
| 281 } |
| 282 |
| 283 /** Opens a listening socket and waits for a connection. */ |
| 284 @Override |
| 285 public Socket connect() { |
| 286 Log.d(TAG, "Listening on [" + address.getHostAddress() + "]:" + Integer.to
String(port)); |
| 287 |
| 288 final ServerSocket tempSocket; |
| 289 try { |
| 290 tempSocket = new ServerSocket(port, 0, address); |
| 291 } catch (IOException e) { |
| 292 reportError("Failed to create server socket: " + e.getMessage()); |
| 293 return null; |
| 294 } |
| 295 |
| 296 synchronized (rawSocketLock) { |
| 297 if (serverSocket != null) { |
| 298 Log.e(TAG, "Server rawSocket was already listening and new will be ope
ned."); |
| 299 } |
| 300 |
| 301 serverSocket = tempSocket; |
| 302 } |
| 303 |
| 304 try { |
| 305 return tempSocket.accept(); |
| 306 } catch (IOException e) { |
| 307 reportError("Failed to receive connection: " + e.getMessage()); |
| 308 return null; |
| 309 } |
| 310 } |
| 311 |
| 312 /** Closes the listening socket and calls super. */ |
| 313 @Override |
| 314 public void disconnect() { |
| 315 try { |
| 316 synchronized (rawSocketLock) { |
| 317 if (serverSocket != null) { |
| 318 serverSocket.close(); |
| 319 serverSocket = null; |
| 320 } |
| 321 } |
| 322 } catch (IOException e) { |
| 323 reportError("Failed to close server socket: " + e.getMessage()); |
| 324 } |
| 325 |
| 326 super.disconnect(); |
| 327 } |
| 328 |
| 329 @Override |
| 330 public boolean isServer() { |
| 331 return true; |
| 332 } |
| 333 } |
| 334 |
| 335 private class TCPSocketClient extends TCPSocket { |
| 336 final private InetAddress address; |
| 337 final private int port; |
| 338 |
| 339 public TCPSocketClient(InetAddress address, int port) { |
| 340 this.address = address; |
| 341 this.port = port; |
| 342 } |
| 343 |
| 344 /** Connects to the peer. */ |
| 345 @Override |
| 346 public Socket connect() { |
| 347 Log.d(TAG, "Connecting to [" + address.getHostAddress() + "]:" + Integer.t
oString(port)); |
| 348 |
| 349 try { |
| 350 return new Socket(address, port); |
| 351 } catch (IOException e) { |
| 352 reportError("Failed to connect: " + e.getMessage()); |
| 353 return null; |
| 354 } |
| 355 } |
| 356 |
| 357 @Override |
| 358 public boolean isServer() { |
| 359 return false; |
| 360 } |
| 361 } |
| 362 } |
OLD | NEW |