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