1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package org.jboss.netty.bootstrap;
17
18 import java.net.InetSocketAddress;
19 import java.net.SocketAddress;
20
21 import org.jboss.netty.channel.Channel;
22 import org.jboss.netty.channel.ChannelConfig;
23 import org.jboss.netty.channel.ChannelException;
24 import org.jboss.netty.channel.ChannelFactory;
25 import org.jboss.netty.channel.ChannelFuture;
26 import org.jboss.netty.channel.ChannelHandler;
27 import org.jboss.netty.channel.ChannelPipeline;
28 import org.jboss.netty.channel.ChannelPipelineException;
29 import org.jboss.netty.channel.ChannelPipelineFactory;
30 import org.jboss.netty.channel.Channels;
31
32 /**
33 * A helper class which creates a new server-side {@link Channel} for a
34 * connectionless transport.
35 *
36 * <h3>Only for connectionless transports</h3>
37 *
38 * This bootstrap is for connectionless transports only such as UDP/IP.
39 * Use {@link ServerBootstrap} instead for connection oriented transports.
40 * Do not use this helper if you are using a connection oriented transport such
41 * as TCP/IP and local transport which accepts an incoming connection and lets
42 * the accepted child channels handle received messages.
43 *
44 * <h3>Configuring channels</h3>
45 *
46 * {@link #setOption(String, Object) Options} are used to configure a channel:
47 *
48 * <pre>
49 * {@link ConnectionlessBootstrap} b = ...;
50 *
51 * // Options for a new channel
52 * b.setOption("localAddress", new {@link InetSocketAddress}(8080));
53 * b.setOption("tcpNoDelay", true);
54 * b.setOption("receiveBufferSize", 1048576);
55 * </pre>
56 *
57 * For the detailed list of available options, please refer to
58 * {@link ChannelConfig} and its sub-types.
59 *
60 * <h3>Configuring a channel pipeline</h3>
61 *
62 * Every channel has its own {@link ChannelPipeline} and you can configure it
63 * in two ways.
64 *
65 * The recommended approach is to specify a {@link ChannelPipelineFactory} by
66 * calling {@link #setPipelineFactory(ChannelPipelineFactory)}.
67 *
68 * <pre>
69 * {@link ConnectionlessBootstrap} b = ...;
70 * b.setPipelineFactory(new MyPipelineFactory());
71 *
72 * public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
73 * public {@link ChannelPipeline} getPipeline() throws Exception {
74 * // Create and configure a new pipeline for a new channel.
75 * {@link ChannelPipeline} p = {@link Channels}.pipeline();
76 * p.addLast("encoder", new EncodingHandler());
77 * p.addLast("decoder", new DecodingHandler());
78 * p.addLast("logic", new LogicHandler());
79 * return p;
80 * }
81 * }
82 * </pre>
83
84 * <p>
85 * The alternative approach, which works only in a certain situation, is to use
86 * the default pipeline and let the bootstrap to shallow-copy the default
87 * pipeline for each new channel:
88 *
89 * <pre>
90 * {@link ConnectionlessBootstrap} b = ...;
91 * {@link ChannelPipeline} p = b.getPipeline();
92 *
93 * // Add handlers to the default pipeline.
94 * p.addLast("encoder", new EncodingHandler());
95 * p.addLast("decoder", new DecodingHandler());
96 * p.addLast("logic", new LogicHandler());
97 * </pre>
98 *
99 * Please note 'shallow-copy' here means that the added {@link ChannelHandler}s
100 * are not cloned but only their references are added to the new pipeline.
101 * Therefore, you cannot use this approach if you are going to open more than
102 * one {@link Channel}s or run a server that accepts incoming connections to
103 * create its child channels.
104 *
105 * <h3>Applying different settings for different {@link Channel}s</h3>
106 *
107 * {@link ConnectionlessBootstrap} is just a helper class. It neither
108 * allocates nor manages any resources. What manages the resources is the
109 * {@link ChannelFactory} implementation you specified in the constructor of
110 * {@link ConnectionlessBootstrap}. Therefore, it is OK to create as
111 * many {@link ConnectionlessBootstrap} instances as you want with the same
112 * {@link ChannelFactory} to apply different settings for different
113 * {@link Channel}s.
114 *
115 * @apiviz.landmark
116 */
117 public class ConnectionlessBootstrap extends Bootstrap {
118
119 /**
120 * Creates a new instance with no {@link ChannelFactory} set.
121 * {@link #setFactory(ChannelFactory)} must be called before any I/O
122 * operation is requested.
123 */
124 public ConnectionlessBootstrap() {
125 }
126
127 /**
128 * Creates a new instance with the specified initial {@link ChannelFactory}.
129 */
130 public ConnectionlessBootstrap(ChannelFactory channelFactory) {
131 super(channelFactory);
132 }
133
134 /**
135 * Creates a new channel which is bound to the local address which was
136 * specified in the current {@code "localAddress"} option. This method is
137 * similar to the following code:
138 *
139 * <pre>
140 * {@link ConnectionlessBootstrap} b = ...;
141 * b.bind(b.getOption("localAddress"));
142 * </pre>
143 *
144 * @return a new bound channel which accepts incoming connections
145 *
146 * @throws IllegalStateException
147 * if {@code "localAddress"} option was not set
148 * @throws ClassCastException
149 * if {@code "localAddress"} option's value is
150 * neither a {@link SocketAddress} nor {@code null}
151 * @throws ChannelException
152 * if failed to create a new channel and
153 * bind it to the local address
154 */
155 public Channel bind() {
156 SocketAddress localAddress = (SocketAddress) getOption("localAddress");
157 if (localAddress == null) {
158 throw new IllegalStateException("localAddress option is not set.");
159 }
160 return bind(localAddress);
161 }
162
163 /**
164 * Creates a new channel which is bound to the specified local address.
165 *
166 * @return a new bound channel which accepts incoming connections
167 *
168 * @throws ChannelException
169 * if failed to create a new channel and
170 * bind it to the local address
171 */
172 public Channel bind(final SocketAddress localAddress) {
173 if (localAddress == null) {
174 throw new NullPointerException("localAddress");
175 }
176
177 ChannelPipeline pipeline;
178 try {
179 pipeline = getPipelineFactory().getPipeline();
180 } catch (Exception e) {
181 throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
182 }
183
184 Channel ch = getFactory().newChannel(pipeline);
185
186 // Apply options.
187 boolean success = false;
188 try {
189 ch.getConfig().setOptions(getOptions());
190 success = true;
191 } finally {
192 if (!success) {
193 ch.close();
194 }
195 }
196
197 // Bind
198 ChannelFuture future = ch.bind(localAddress);
199
200 // Wait for the future.
201 future.awaitUninterruptibly();
202 if (!future.isSuccess()) {
203 future.getChannel().close().awaitUninterruptibly();
204 throw new ChannelException("Failed to bind to: " + localAddress, future.getCause());
205 }
206
207 return ch;
208 }
209
210 /**
211 * Creates a new connected channel with the current {@code "remoteAddress"}
212 * and {@code "localAddress"} option. If the {@code "localAddress"} option
213 * is not set, the local address of a new channel is determined
214 * automatically. This method is similar to the following code:
215 *
216 * <pre>
217 * {@link ConnectionlessBootstrap} b = ...;
218 * b.connect(b.getOption("remoteAddress"), b.getOption("localAddress"));
219 * </pre>
220 *
221 * @return a future object which notifies when the creation of the connected
222 * channel succeeds or fails
223 *
224 * @throws IllegalStateException
225 * if {@code "remoteAddress"} option was not set
226 * @throws ClassCastException
227 * if {@code "remoteAddress"} or {@code "localAddress"} option's
228 * value is neither a {@link SocketAddress} nor {@code null}
229 * @throws ChannelPipelineException
230 * if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
231 * failed to create a new {@link ChannelPipeline}
232 */
233 public ChannelFuture connect() {
234 SocketAddress remoteAddress = (SocketAddress) getOption("remoteAddress");
235 if (remoteAddress == null) {
236 throw new IllegalStateException("remoteAddress option is not set.");
237 }
238 return connect(remoteAddress);
239 }
240
241 /**
242 * Creates a new connected channel with the specified
243 * {@code "remoteAddress"} and the current {@code "localAddress"} option.
244 * If the {@code "localAddress"} option is not set, the local address of
245 * a new channel is determined automatically. This method is identical
246 * with the following code:
247 *
248 * <pre>
249 * {@link ConnectionlessBootstrap} b = ...;
250 * b.connect(remoteAddress, b.getOption("localAddress"));
251 * </pre>
252 *
253 * @return a future object which notifies when the creation of the connected
254 * channel succeeds or fails
255 *
256 * @throws ClassCastException
257 * if {@code "localAddress"} option's value is
258 * neither a {@link SocketAddress} nor {@code null}
259 * @throws ChannelPipelineException
260 * if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
261 * failed to create a new {@link ChannelPipeline}
262 */
263 public ChannelFuture connect(SocketAddress remoteAddress) {
264 if (remoteAddress == null) {
265 throw new NullPointerException("remotedAddress");
266 }
267 SocketAddress localAddress = (SocketAddress) getOption("localAddress");
268 return connect(remoteAddress, localAddress);
269 }
270
271 /**
272 * Creates a new connected channel with the specified
273 * {@code "remoteAddress"} and the specified {@code "localAddress"}.
274 * If the specified local address is {@code null}, the local address of a
275 * new channel is determined automatically.
276 *
277 * @return a future object which notifies when the creation of the connected
278 * channel succeeds or fails
279 *
280 * @throws ChannelPipelineException
281 * if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
282 * failed to create a new {@link ChannelPipeline}
283 */
284 public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
285
286 if (remoteAddress == null) {
287 throw new NullPointerException("remoteAddress");
288 }
289
290 ChannelPipeline pipeline;
291 try {
292 pipeline = getPipelineFactory().getPipeline();
293 } catch (Exception e) {
294 throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
295 }
296
297 // Set the options.
298 Channel ch = getFactory().newChannel(pipeline);
299 boolean success = false;
300 try {
301 ch.getConfig().setOptions(getOptions());
302 success = true;
303 } finally {
304 if (!success) {
305 ch.close();
306 }
307 }
308
309 // Bind.
310 if (localAddress != null) {
311 ch.bind(localAddress);
312 }
313
314 // Connect.
315 return ch.connect(remoteAddress);
316 }
317 }