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
120
121 public int getWorkerTimeout() {
122 return workerTimeout;
123 }
124
125
126
127
128
129
130
131
132
133 public void setWorkerTimeout(int workerTimeout) {
134 if (workerTimeout < 0) {
135 throw new IllegalArgumentException("Must be >= 0");
136 }
137 this.workerTimeout = workerTimeout;
138 }
139
140 public ConnectFuture connect(SocketAddress address, IoHandler handler,
141 IoServiceConfig config) {
142 return connect(address, null, handler, config);
143 }
144
145 public ConnectFuture connect(SocketAddress address,
146 SocketAddress localAddress, IoHandler handler,
147 IoServiceConfig config) {
148 if (address == null)
149 throw new NullPointerException("address");
150 if (handler == null)
151 throw new NullPointerException("handler");
152
153 if (!(address instanceof InetSocketAddress))
154 throw new IllegalArgumentException("Unexpected address type: "
155 + address.getClass());
156
157 if (localAddress != null
158 && !(localAddress instanceof InetSocketAddress))
159 throw new IllegalArgumentException(
160 "Unexpected local address type: " + localAddress.getClass());
161
162 if (config == null) {
163 config = getDefaultConfig();
164 }
165
166 SocketChannel ch = null;
167 boolean success = false;
168 try {
169 ch = SocketChannel.open();
170 ch.socket().setReuseAddress(true);
171 if (localAddress != null) {
172 ch.socket().bind(localAddress);
173 }
174
175 ch.configureBlocking(false);
176
177 if (ch.connect(address)) {
178 DefaultConnectFuture future = new DefaultConnectFuture();
179 newSession(ch, handler, config, future);
180 success = true;
181 return future;
182 }
183
184 success = true;
185 } catch (IOException e) {
186 return DefaultConnectFuture.newFailedFuture(e);
187 } finally {
188 if (!success && ch != null) {
189 try {
190 ch.close();
191 } catch (IOException e) {
192 ExceptionMonitor.getInstance().exceptionCaught(e);
193 }
194 }
195 }
196
197 ConnectionRequest request = new ConnectionRequest(ch, handler, config);
198 synchronized (lock) {
199 try {
200 startupWorker();
201 } catch (IOException e) {
202 try {
203 ch.close();
204 } catch (IOException e2) {
205 ExceptionMonitor.getInstance().exceptionCaught(e2);
206 }
207
208 return DefaultConnectFuture.newFailedFuture(e);
209 }
210
211 synchronized (connectQueue) {
212 connectQueue.push(request);
213 }
214 selector.wakeup();
215 }
216
217 return request;
218 }
219
220 public IoServiceConfig getDefaultConfig() {
221 return defaultConfig;
222 }
223
224
225
226
227
228
229
230 public void setDefaultConfig(SocketConnectorConfig defaultConfig) {
231 if (defaultConfig == null) {
232 throw new NullPointerException("defaultConfig");
233 }
234 this.defaultConfig = defaultConfig;
235 }
236
237 private Selector getSelector() {
238 synchronized (lock) {
239 return this.selector;
240 }
241 }
242
243 private void startupWorker() throws IOException {
244 synchronized (lock) {
245 if (worker == null) {
246 selector = Selector.open();
247 worker = new Worker();
248 executor.execute(new NamePreservingRunnable(worker, threadName));
249 }
250 }
251 }
252
253 private void registerNew() {
254 if (connectQueue.isEmpty())
255 return;
256
257 Selector selector = getSelector();
258 for (;;) {
259 ConnectionRequest req;
260 synchronized (connectQueue) {
261 req = (ConnectionRequest) connectQueue.pop();
262 }
263
264 if (req == null)
265 break;
266
267 SocketChannel ch = req.channel;
268 try {
269 ch.register(selector, SelectionKey.OP_CONNECT, req);
270 } catch (IOException e) {
271 req.setException(e);
272 try {
273 ch.close();
274 } catch (IOException e2) {
275 ExceptionMonitor.getInstance().exceptionCaught(e2);
276 }
277 }
278 }
279 }
280
281 private void processSessions(Set keys) {
282 Iterator it = keys.iterator();
283
284 while (it.hasNext()) {
285 SelectionKey key = (SelectionKey) it.next();
286
287 if (!key.isConnectable())
288 continue;
289
290 SocketChannel ch = (SocketChannel) key.channel();
291 ConnectionRequest entry = (ConnectionRequest) key.attachment();
292
293 boolean success = false;
294 try {
295 ch.finishConnect();
296 newSession(ch, entry.handler, entry.config, entry);
297 success = true;
298 } catch (Throwable e) {
299 entry.setException(e);
300 } finally {
301 key.cancel();
302 if (!success) {
303 try {
304 ch.close();
305 } catch (IOException e) {
306 ExceptionMonitor.getInstance().exceptionCaught(e);
307 }
308 }
309 }
310 }
311
312 keys.clear();
313 }
314
315 private void processTimedOutSessions(Set keys) {
316 long currentTime = System.currentTimeMillis();
317 Iterator it = keys.iterator();
318
319 while (it.hasNext()) {
320 SelectionKey key = (SelectionKey) it.next();
321
322 if (!key.isValid())
323 continue;
324
325 ConnectionRequest entry = (ConnectionRequest) key.attachment();
326
327 if (currentTime >= entry.deadline) {
328 entry.setException(new ConnectException());
329 try {
330 key.channel().close();
331 } catch (IOException e) {
332 ExceptionMonitor.getInstance().exceptionCaught(e);
333 } finally {
334 key.cancel();
335 }
336 }
337 }
338 }
339
340 private void newSession(SocketChannel ch, IoHandler handler,
341 IoServiceConfig config, ConnectFuture connectFuture)
342 throws IOException {
343 SocketSessionImpl session = new SocketSessionImpl(this,
344 nextProcessor(), getListeners(), config, ch, handler, ch
345 .socket().getRemoteSocketAddress());
346 try {
347 getFilterChainBuilder().buildFilterChain(session.getFilterChain());
348 config.getFilterChainBuilder().buildFilterChain(
349 session.getFilterChain());
350 config.getThreadModel().buildFilterChain(session.getFilterChain());
351 } catch (Throwable e) {
352 throw (IOException) new IOException("Failed to create a session.")
353 .initCause(e);
354 }
355
356
357
358 session.setAttribute(AbstractIoFilterChain.CONNECT_FUTURE,
359 connectFuture);
360
361
362 session.getIoProcessor().addNew(session);
363 }
364
365 private SocketIoProcessor nextProcessor() {
366 if (this.processorDistributor == Integer.MAX_VALUE) {
367 this.processorDistributor = Integer.MAX_VALUE % this.processorCount;
368 }
369
370 return ioProcessors[processorDistributor++ % processorCount];
371 }
372
373 private class Worker implements Runnable {
374 private long lastActive = System.currentTimeMillis();
375
376 public void run() {
377 Selector selector = getSelector();
378 for (;;) {
379 try {
380 int nKeys = selector.select(1000);
381
382 registerNew();
383
384 if (nKeys > 0) {
385 processSessions(selector.selectedKeys());
386 }
387
388 processTimedOutSessions(selector.keys());
389
390 if (selector.keys().isEmpty()) {
391 if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) {
392 synchronized (lock) {
393 if (selector.keys().isEmpty()
394 && connectQueue.isEmpty()) {
395 worker = null;
396 try {
397 selector.close();
398 } catch (IOException e) {
399 ExceptionMonitor.getInstance()
400 .exceptionCaught(e);
401 } finally {
402 SocketConnector.this.selector = null;
403 }
404 break;
405 }
406 }
407 }
408 } else {
409 lastActive = System.currentTimeMillis();
410 }
411 } catch (IOException e) {
412 ExceptionMonitor.getInstance().exceptionCaught(e);
413
414 try {
415 Thread.sleep(1000);
416 } catch (InterruptedException e1) {
417 ExceptionMonitor.getInstance().exceptionCaught(e1);
418 }
419 }
420 }
421 }
422 }
423
424 private class ConnectionRequest extends DefaultConnectFuture {
425 private final SocketChannel channel;
426
427 private final long deadline;
428
429 private final IoHandler handler;
430
431 private final IoServiceConfig config;
432
433 private ConnectionRequest(SocketChannel channel, IoHandler handler,
434 IoServiceConfig config) {
435 this.channel = channel;
436 long timeout;
437 if (config instanceof IoConnectorConfig) {
438 timeout = ((IoConnectorConfig) config)
439 .getConnectTimeoutMillis();
440 } else {
441 timeout = ((IoConnectorConfig) getDefaultConfig())
442 .getConnectTimeoutMillis();
443 }
444 this.deadline = System.currentTimeMillis() + timeout;
445 this.handler = handler;
446 this.config = config;
447 }
448 }
449 }