OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * Copyright 2015 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, in t port) { | |
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 executor.execute(new Runnable() { | |
107 @Override | |
108 public void run() { | |
109 eventListener.onTCPError(message); | |
110 } | |
111 }); | |
112 } | |
113 | |
114 /** | |
115 * Helper method for debugging purposes. | |
116 * Ensures that TCPChannelClient method is called on a looper thread. | |
117 */ | |
118 private void checkIfCalledOnValidThread() { | |
119 if (!executor.checkOnLooperThread()) { | |
120 throw new IllegalStateException( | |
121 "TCPChannelClient method is not called on valid thread"); | |
122 } | |
123 } | |
124 | |
125 | |
126 /** | |
127 * Base class for server and client sockets. Contains a listening thread that will call | |
128 * eventListener.onTCPMessage on new messages. | |
129 */ | |
130 private abstract class TCPSocket extends Thread { | |
131 // Lock for editing out and rawSocket | |
132 protected final Object rawSocketLock; | |
133 private PrintWriter out; | |
134 private Socket rawSocket; | |
135 | |
136 /** | |
137 * Connect to the peer, potentially a slow operation. | |
138 * | |
139 * @return Socket connection, null if connection failed. | |
140 */ | |
141 public abstract Socket connect(); | |
142 /** Returns true if sockets is a server rawSocket. */ | |
143 public abstract boolean isServer(); | |
144 | |
145 TCPSocket() { | |
146 rawSocketLock = new Object(); | |
147 } | |
148 | |
149 /** | |
150 * The listening thread. | |
151 */ | |
152 @Override | |
153 public void run() { | |
154 // Receive connection to temporary variable first, so we don't block | |
155 Socket tempSocket = connect(); | |
156 BufferedReader in; | |
157 | |
158 synchronized (rawSocketLock) { | |
159 if (rawSocket != null) { | |
160 Log.e(TAG, "Socket already existed and will be replaced."); | |
161 } | |
162 | |
163 rawSocket = tempSocket; | |
164 | |
165 // Connecting failed, error has already been reported, just exit | |
166 if (rawSocket == null) { | |
167 return; | |
168 } | |
169 | |
170 try { | |
171 out = new PrintWriter(rawSocket.getOutputStream(), true); | |
172 in = new BufferedReader(new InputStreamReader(rawSocket.getInputStream ())); | |
173 } catch (IOException e) { | |
174 reportError("Failed to open IO on rawSocket: " + e.getMessage()); | |
175 return; | |
176 } | |
177 } | |
178 | |
179 executor.execute(new Runnable() { | |
180 @Override | |
181 public void run() { | |
182 eventListener.onTCPConnected(isServer()); | |
183 } | |
184 }); | |
185 | |
186 String message; | |
187 while (true) { | |
188 try { | |
189 message = in.readLine(); | |
190 } catch (IOException e) { | |
191 reportError("Failed to read from rawSocket: " + e.getMessage()); | |
192 return; | |
magjed_webrtc
2016/05/10 14:50:02
break instead of return?
sakal
2016/05/11 08:38:50
Yep, you're right.
| |
193 } | |
194 | |
195 // No data received, rawSocket probably closed | |
196 if (message == null) { | |
197 break; | |
198 } | |
199 | |
200 // We need final variable for the Runnable | |
201 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.
| |
202 executor.execute(new Runnable() { | |
203 @Override | |
204 public void run() { | |
205 Log.v(TAG, "Receive: " + executorMessage); | |
206 eventListener.onTCPMessage(executorMessage); | |
207 } | |
208 }); | |
209 } | |
210 | |
211 Log.d(TAG, "Receiving thread exiting..."); | |
212 | |
213 // Close the rawSocket if it is still open | |
214 disconnect(); | |
215 } | |
216 | |
217 /** | |
218 * Closes the rawSocket if it is still open. Also fires the onTCPClose event . | |
219 */ | |
220 public void disconnect() { | |
221 try { | |
222 synchronized (rawSocketLock) { | |
223 if (rawSocket != null) { | |
224 rawSocket.close(); | |
225 rawSocket = null; | |
226 out = null; | |
227 | |
228 eventListener.onTCPClose(); | |
229 } | |
230 } | |
231 } 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.
| |
232 reportError("Failed to close rawSocket: " + e.getMessage()); | |
233 } | |
234 } | |
235 | |
236 /** | |
237 * Sends a message on the socket. Should only be called on the executor thre ad. | |
238 * | |
239 * @param message | |
240 */ | |
241 public void send(String message) { | |
242 Log.v(TAG, "Send: " + message); | |
243 | |
244 synchronized (rawSocketLock) { | |
245 if (out == null) { | |
246 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
| |
247 } | |
248 | |
249 out.write(message + "\n"); | |
250 out.flush(); | |
251 } | |
252 } | |
253 } | |
254 | |
255 private class TCPSocketServer extends TCPSocket { | |
256 // Server socket also guarded by rawSocketLock | |
257 private ServerSocket serverSocket; | |
258 | |
259 private InetAddress address; | |
magjed_webrtc
2016/05/10 14:50:03
Make these variables final.
sakal
2016/05/11 08:38:50
Done.
| |
260 private int port; | |
261 | |
262 public TCPSocketServer(InetAddress address, int port) { | |
263 this.address = address; | |
264 this.port = port; | |
265 } | |
266 | |
267 /** Opens a listening socket and waits for a connection. */ | |
268 public Socket connect() { | |
269 Log.d(TAG, "Listening on " + address.getHostAddress() + ":" + Integer.toSt ring(port)); | |
270 | |
271 synchronized (rawSocketLock) { | |
272 if (serverSocket != null) { | |
273 Log.e(TAG, "Server rawSocket was already listening and new will be ope ned."); | |
274 } | |
275 | |
276 try { | |
277 serverSocket = new ServerSocket(port, 0, address); | |
278 return serverSocket.accept(); | |
279 } catch (IOException e) { | |
280 reportError("Failed to receive connection: " + e.getMessage()); | |
281 return null; | |
282 } | |
283 } | |
284 } | |
285 | |
286 /** Closes the listening socket and calls super. */ | |
287 @Override | |
288 public void disconnect() { | |
289 try { | |
290 synchronized (rawSocketLock) { | |
291 if (serverSocket != null) { | |
292 serverSocket.close(); | |
293 serverSocket = null; | |
294 } | |
295 } | |
296 } catch (IOException e) { | |
297 reportError("Failed to close server socket: " + e.getMessage()); | |
298 } | |
299 | |
300 super.disconnect(); | |
301 } | |
302 | |
303 public boolean isServer() { | |
magjed_webrtc
2016/05/10 14:50:03
add @Override?
sakal
2016/05/11 08:38:50
Done.
| |
304 return true; | |
305 } | |
306 } | |
307 | |
308 private class TCPSocketClient extends TCPSocket { | |
309 private InetAddress address; | |
magjed_webrtc
2016/05/10 14:50:02
Make these variables final.
sakal
2016/05/11 08:38:50
Done.
| |
310 private int port; | |
311 | |
312 public TCPSocketClient(InetAddress address, int port) { | |
313 this.address = address; | |
314 this.port = port; | |
315 } | |
316 | |
317 /** Connects to the peer. */ | |
318 public Socket connect() { | |
magjed_webrtc
2016/05/10 14:50:03
add @Override?
sakal
2016/05/11 08:38:50
Done.
| |
319 Log.d(TAG, "Connecting to [" + address.getHostAddress() + "]:" + Integer.t oString(port)); | |
320 | |
321 try { | |
322 return new Socket(address, port); | |
323 } catch (IOException e) { | |
324 reportError("Failed to connect: " + e.getMessage()); | |
325 return null; | |
326 } | |
327 } | |
328 | |
329 public boolean isServer() { | |
magjed_webrtc
2016/05/10 14:50:03
add @Override?
sakal
2016/05/11 08:38:50
Done.
| |
330 return false; | |
331 } | |
332 } | |
333 } | |
OLD | NEW |