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