1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.transport.socket.nio;
21
22 import java.io.IOException;
23 import java.net.ConnectException;
24 import java.net.InetSocketAddress;
25 import java.net.SocketAddress;
26 import java.nio.channels.SelectionKey;
27 import java.nio.channels.Selector;
28 import java.nio.channels.SocketChannel;
29 import java.util.Iterator;
30 import java.util.Set;
31
32 import org.apache.mina.common.ConnectFuture;
33 import org.apache.mina.common.ExceptionMonitor;
34 import org.apache.mina.common.IoConnector;
35 import org.apache.mina.common.IoConnectorConfig;
36 import org.apache.mina.common.IoHandler;
37 import org.apache.mina.common.IoServiceConfig;
38 import org.apache.mina.common.support.AbstractIoFilterChain;
39 import org.apache.mina.common.support.BaseIoConnector;
40 import org.apache.mina.common.support.DefaultConnectFuture;
41 import org.apache.mina.util.Queue;
42 import org.apache.mina.util.NewThreadExecutor;
43 import org.apache.mina.util.NamePreservingRunnable;
44 import edu.emory.mathcs.backport.java.util.concurrent.Executor;
45
46
47
48
49
50
51
52 public class SocketConnector extends BaseIoConnector {
53
54
55
56 private static volatile int nextId = 0;
57
58 private final Object lock = new Object();
59
60 private final int id = nextId++;
61
62 private final String threadName = "SocketConnector-" + id;
63
64 private SocketConnectorConfig defaultConfig = new SocketConnectorConfig();
65
66 private final Queue connectQueue = new Queue();
67
68 private final SocketIoProcessor[] ioProcessors;
69
70 private final int processorCount;
71
72 private final Executor executor;
73
74
75
76
77 private Selector selector;
78
79 private Worker worker;
80
81 private int processorDistributor = 0;
82
83 private int workerTimeout = 60;
84
85
86
87
88 public SocketConnector() {
89 this(1, new NewThreadExecutor());
90 }
91
92
93
94
95
96
97
98 public SocketConnector(int processorCount, Executor executor) {
99 if (processorCount < 1) {
100 throw new IllegalArgumentException(
101 "Must have at least one processor");
102 }
103
104 this.executor = executor;
105 this.processorCount = processorCount;
106 ioProcessors = new SocketIoProcessor[processorCount];
107
108 for (int i = 0; i < processorCount; i++) {
109 ioProcessors[i] = new SocketIoProcessor(
110 "SocketConnectorIoProcessor-" + id + "." + i, executor);
111 }
112 }
113
114
115
116
117
118
119 public int getWorkerTimeout() {
120 return workerTimeout;
121 }
122
123
124
125
126
127
128 public void setWorkerTimeout(int workerTimeout) {
129 if (workerTimeout < 0) {
130 throw new IllegalArgumentException("Must be >= 0");
131 }
132 this.workerTimeout = workerTimeout;
133 }
134
135 public ConnectFuture connect(SocketAddress address, IoHandler handler,
136 IoServiceConfig config) {
137 return connect(address, null, handler, config);
138 }
139
140 public ConnectFuture connect(SocketAddress address,
141 SocketAddress localAddress, IoHandler handler,
142 IoServiceConfig config) {
143 if (address == null)
144 throw new NullPointerException("address");
145 if (handler == null)
146 throw new NullPointerException("handler");
147
148 if (!(address instanceof InetSocketAddress))
149 throw new IllegalArgumentException("Unexpected address type: "
150 + address.getClass());
151
152 if (localAddress != null
153 && !(localAddress instanceof InetSocketAddress))
154 throw new IllegalArgumentException(
155 "Unexpected local address type: " + localAddress.getClass());
156
157 if (config == null) {
158 config = getDefaultConfig();
159 }
160
161 SocketChannel ch = null;
162 boolean success = false;
163 try {
164 ch = SocketChannel.open();
165 ch.socket().setReuseAddress(true);
166 if (localAddress != null) {
167 ch.socket().bind(localAddress);
168 }
169
170 ch.configureBlocking(false);
171
172 if (ch.connect(address)) {
173 DefaultConnectFuture future = new DefaultConnectFuture();
174 newSession(ch, handler, config, future);
175 success = true;
176 return future;
177 }
178
179 success = true;
180 } catch (IOException e) {
181 return DefaultConnectFuture.newFailedFuture(e);
182 } finally {
183 if (!success && ch != null) {
184 try {
185 ch.close();
186 } catch (IOException e) {
187 ExceptionMonitor.getInstance().exceptionCaught(e);
188 }
189 }
190 }
191
192 ConnectionRequest request = new ConnectionRequest(ch, handler, config);
193 synchronized (lock) {
194 try {
195 startupWorker();
196 } catch (IOException e) {
197 try {
198 ch.close();
199 } catch (IOException e2) {
200 ExceptionMonitor.getInstance().exceptionCaught(e2);
201 }
202
203 return DefaultConnectFuture.newFailedFuture(e);
204 }
205
206 synchronized (connectQueue) {
207 connectQueue.push(request);
208 }
209 selector.wakeup();
210 }
211
212 return request;
213 }
214
215 public IoServiceConfig getDefaultConfig() {
216 return defaultConfig;
217 }
218
219
220
221
222
223
224
225 public void setDefaultConfig(SocketConnectorConfig defaultConfig) {
226 if (defaultConfig == null) {
227 throw new NullPointerException("defaultConfig");
228 }
229 this.defaultConfig = defaultConfig;
230 }
231
232 private Selector getSelector() {
233 synchronized (lock) {
234 return this.selector;
235 }
236 }
237
238 private void startupWorker() throws IOException {
239 synchronized (lock) {
240 if (worker == null) {
241 selector = Selector.open();
242 worker = new Worker();
243 executor.execute(new NamePreservingRunnable(worker));
244 }
245 }
246 }
247
248 private void registerNew() {
249 if (connectQueue.isEmpty())
250 return;
251
252 Selector selector = getSelector();
253 for (;;) {
254 ConnectionRequest req;
255 synchronized (connectQueue) {
256 req = (ConnectionRequest) connectQueue.pop();
257 }
258
259 if (req == null)
260 break;
261
262 SocketChannel ch = req.channel;
263 try {
264 ch.register(selector, SelectionKey.OP_CONNECT, req);
265 } catch (IOException e) {
266 req.setException(e);
267 }
268 }
269 }
270
271 private void processSessions(Set keys) {
272 Iterator it = keys.iterator();
273
274 while (it.hasNext()) {
275 SelectionKey key = (SelectionKey) it.next();
276
277 if (!key.isConnectable())
278 continue;
279
280 SocketChannel ch = (SocketChannel) key.channel();
281 ConnectionRequest entry = (ConnectionRequest) key.attachment();
282
283 boolean success = false;
284 try {
285 ch.finishConnect();
286 newSession(ch, entry.handler, entry.config, entry);
287 success = true;
288 } catch (Throwable e) {
289 entry.setException(e);
290 } finally {
291 key.cancel();
292 if (!success) {
293 try {
294 ch.close();
295 } catch (IOException e) {
296 ExceptionMonitor.getInstance().exceptionCaught(e);
297 }
298 }
299 }
300 }
301
302 keys.clear();
303 }
304
305 private void processTimedOutSessions(Set keys) {
306 long currentTime = System.currentTimeMillis();
307 Iterator it = keys.iterator();
308
309 while (it.hasNext()) {
310 SelectionKey key = (SelectionKey) it.next();
311
312 if (!key.isValid())
313 continue;
314
315 ConnectionRequest entry = (ConnectionRequest) key.attachment();
316
317 if (currentTime >= entry.deadline) {
318 entry.setException(new ConnectException());
319 try {
320 key.channel().close();
321 } catch (IOException e) {
322 ExceptionMonitor.getInstance().exceptionCaught(e);
323 } finally {
324 key.cancel();
325 }
326 }
327 }
328 }
329
330 private void newSession(SocketChannel ch, IoHandler handler,
331 IoServiceConfig config, ConnectFuture connectFuture)
332 throws IOException {
333 SocketSessionImpl session = new SocketSessionImpl(this,
334 nextProcessor(), getListeners(), config, ch, handler, ch
335 .socket().getRemoteSocketAddress());
336 try {
337 getFilterChainBuilder().buildFilterChain(session.getFilterChain());
338 config.getFilterChainBuilder().buildFilterChain(
339 session.getFilterChain());
340 config.getThreadModel().buildFilterChain(session.getFilterChain());
341 } catch (Throwable e) {
342 throw (IOException) new IOException("Failed to create a session.")
343 .initCause(e);
344 }
345
346
347
348 session.setAttribute(AbstractIoFilterChain.CONNECT_FUTURE,
349 connectFuture);
350
351
352 session.getIoProcessor().addNew(session);
353 }
354
355 private SocketIoProcessor nextProcessor() {
356 if (this.processorDistributor == Integer.MAX_VALUE) {
357 this.processorDistributor = Integer.MAX_VALUE % this.processorCount;
358 }
359
360 return ioProcessors[processorDistributor++ % processorCount];
361 }
362
363 private class Worker implements Runnable {
364 private long lastActive = System.currentTimeMillis();
365
366 public void run() {
367 Thread.currentThread().setName(SocketConnector.this.threadName);
368
369 Selector selector = getSelector();
370 for (;;) {
371 try {
372 int nKeys = selector.select(1000);
373
374 registerNew();
375
376 if (nKeys > 0) {
377 processSessions(selector.selectedKeys());
378 }
379
380 processTimedOutSessions(selector.keys());
381
382 if (selector.keys().isEmpty()) {
383 if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) {
384 synchronized (lock) {
385 if (selector.keys().isEmpty()
386 && connectQueue.isEmpty()) {
387 worker = null;
388 try {
389 selector.close();
390 } catch (IOException e) {
391 ExceptionMonitor.getInstance()
392 .exceptionCaught(e);
393 } finally {
394 SocketConnector.this.selector = null;
395 }
396 break;
397 }
398 }
399 }
400 } else {
401 lastActive = System.currentTimeMillis();
402 }
403 } catch (IOException e) {
404 ExceptionMonitor.getInstance().exceptionCaught(e);
405
406 try {
407 Thread.sleep(1000);
408 } catch (InterruptedException e1) {
409 ExceptionMonitor.getInstance().exceptionCaught(e1);
410 }
411 }
412 }
413 }
414 }
415
416 private class ConnectionRequest extends DefaultConnectFuture {
417 private final SocketChannel channel;
418
419 private final long deadline;
420
421 private final IoHandler handler;
422
423 private final IoServiceConfig config;
424
425 private ConnectionRequest(SocketChannel channel, IoHandler handler,
426 IoServiceConfig config) {
427 this.channel = channel;
428 long timeout;
429 if (config instanceof IoConnectorConfig) {
430 timeout = ((IoConnectorConfig) config)
431 .getConnectTimeoutMillis();
432 } else {
433 timeout = ((IoConnectorConfig) getDefaultConfig())
434 .getConnectTimeoutMillis();
435 }
436 this.deadline = System.currentTimeMillis() + timeout;
437 this.handler = handler;
438 this.config = config;
439 }
440 }
441 }