View Javadoc
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 }