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.ChannelFactory;
24  import org.jboss.netty.channel.ChannelFuture;
25  import org.jboss.netty.channel.ChannelHandler;
26  import org.jboss.netty.channel.ChannelPipeline;
27  import org.jboss.netty.channel.ChannelPipelineException;
28  import org.jboss.netty.channel.ChannelPipelineFactory;
29  import org.jboss.netty.channel.Channels;
30  
31  /**
32   * A helper class which creates a new client-side {@link Channel} and makes a
33   * connection attempt.
34   *
35   * <h3>Configuring a channel</h3>
36   *
37   * {@link #setOption(String, Object) Options} are used to configure a channel:
38   *
39   * <pre>
40   * {@link ClientBootstrap} b = ...;
41   *
42   * // Options for a new channel
43   * b.setOption("remoteAddress", new {@link InetSocketAddress}("example.com", 8080));
44   * b.setOption("tcpNoDelay", true);
45   * b.setOption("receiveBufferSize", 1048576);
46   * </pre>
47   *
48   * For the detailed list of available options, please refer to
49   * {@link ChannelConfig} and its sub-types.
50   *
51   * <h3>Configuring a channel pipeline</h3>
52   *
53   * Every channel has its own {@link ChannelPipeline} and you can configure it
54   * in two ways.
55   *
56   * The recommended approach is to specify a {@link ChannelPipelineFactory} by
57   * calling {@link #setPipelineFactory(ChannelPipelineFactory)}.
58   *
59   * <pre>
60   * {@link ClientBootstrap} b = ...;
61   * b.setPipelineFactory(new MyPipelineFactory());
62   *
63   * public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
64   *   public {@link ChannelPipeline} getPipeline() throws Exception {
65   *     // Create and configure a new pipeline for a new channel.
66   *     {@link ChannelPipeline} p = {@link Channels}.pipeline();
67   *     p.addLast("encoder", new EncodingHandler());
68   *     p.addLast("decoder", new DecodingHandler());
69   *     p.addLast("logic",   new LogicHandler());
70   *     return p;
71   *   }
72   * }
73   * </pre>
74  
75   * <p>
76   * The alternative approach, which works only in a certain situation, is to use
77   * the default pipeline and let the bootstrap to shallow-copy the default
78   * pipeline for each new channel:
79   *
80   * <pre>
81   * {@link ClientBootstrap} b = ...;
82   * {@link ChannelPipeline} p = b.getPipeline();
83   *
84   * // Add handlers to the default pipeline.
85   * p.addLast("encoder", new EncodingHandler());
86   * p.addLast("decoder", new DecodingHandler());
87   * p.addLast("logic",   new LogicHandler());
88   * </pre>
89   *
90   * Please note 'shallow-copy' here means that the added {@link ChannelHandler}s
91   * are not cloned but only their references are added to the new pipeline.
92   * Therefore, you cannot use this approach if you are going to open more than
93   * one {@link Channel}s or run a server that accepts incoming connections to
94   * create its child channels.
95   *
96   * <h3>Applying different settings for different {@link Channel}s</h3>
97   *
98   * {@link ClientBootstrap} is just a helper class.  It neither allocates nor
99   * manages any resources.  What manages the resources is the
100  * {@link ChannelFactory} implementation you specified in the constructor of
101  * {@link ClientBootstrap}.  Therefore, it is OK to create as many
102  * {@link ClientBootstrap} instances as you want with the same
103  * {@link ChannelFactory} to apply different settings for different
104  * {@link Channel}s.
105  *
106  * @apiviz.landmark
107  */
108 public class ClientBootstrap extends Bootstrap {
109 
110     /**
111      * Creates a new instance with no {@link ChannelFactory} set.
112      * {@link #setFactory(ChannelFactory)} must be called before any I/O
113      * operation is requested.
114      */
115     public ClientBootstrap() {
116         super();
117     }
118 
119     /**
120      * Creates a new instance with the specified initial {@link ChannelFactory}.
121      */
122     public ClientBootstrap(ChannelFactory channelFactory) {
123         super(channelFactory);
124     }
125 
126     /**
127      * Attempts a new connection with the current {@code "remoteAddress"} and
128      * {@code "localAddress"} option.  If the {@code "localAddress"} option is
129      * not set, the local address of a new channel is determined automatically.
130      * This method is similar to the following code:
131      *
132      * <pre>
133      * {@link ClientBootstrap} b = ...;
134      * b.connect(b.getOption("remoteAddress"), b.getOption("localAddress"));
135      * </pre>
136      *
137      * @return a future object which notifies when this connection attempt
138      *         succeeds or fails
139      *
140      * @throws IllegalStateException
141      *         if {@code "remoteAddress"} option was not set
142      * @throws ClassCastException
143      *         if {@code "remoteAddress"} or {@code "localAddress"} option's
144      *            value is neither a {@link SocketAddress} nor {@code null}
145      * @throws ChannelPipelineException
146      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
147      *            failed to create a new {@link ChannelPipeline}
148      */
149     public ChannelFuture connect() {
150         SocketAddress remoteAddress = (SocketAddress) getOption("remoteAddress");
151         if (remoteAddress == null) {
152             throw new IllegalStateException("remoteAddress option is not set.");
153         }
154         return connect(remoteAddress);
155     }
156 
157     /**
158      * Attempts a new connection with the specified {@code remoteAddress} and
159      * the current {@code "localAddress"} option. If the {@code "localAddress"}
160      * option is not set, the local address of a new channel is determined
161      * automatically.  This method is identical with the following code:
162      *
163      * <pre>
164      * {@link ClientBootstrap} b = ...;
165      * b.connect(remoteAddress, b.getOption("localAddress"));
166      * </pre>
167      *
168      * @return a future object which notifies when this connection attempt
169      *         succeeds or fails
170      *
171      * @throws ClassCastException
172      *         if {@code "localAddress"} option's value is
173      *            neither a {@link SocketAddress} nor {@code null}
174      * @throws ChannelPipelineException
175      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
176      *            failed to create a new {@link ChannelPipeline}
177      */
178     public ChannelFuture connect(SocketAddress remoteAddress) {
179         if (remoteAddress == null) {
180             throw new NullPointerException("remoteAddress");
181         }
182         SocketAddress localAddress = (SocketAddress) getOption("localAddress");
183         return connect(remoteAddress, localAddress);
184     }
185 
186     /**
187      * Attempts a new connection with the specified {@code remoteAddress} and
188      * the specified {@code localAddress}.  If the specified local address is
189      * {@code null}, the local address of a new channel is determined
190      * automatically.
191      *
192      * @return a future object which notifies when this connection attempt
193      *         succeeds or fails
194      *
195      * @throws ChannelPipelineException
196      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
197      *            failed to create a new {@link ChannelPipeline}
198      */
199     public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
200 
201         if (remoteAddress == null) {
202             throw new NullPointerException("remoteAddress");
203         }
204 
205         ChannelPipeline pipeline;
206         try {
207             pipeline = getPipelineFactory().getPipeline();
208         } catch (Exception e) {
209             throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
210         }
211 
212         // Set the options.
213         Channel ch = getFactory().newChannel(pipeline);
214         boolean success = false;
215         try {
216             ch.getConfig().setOptions(getOptions());
217             success = true;
218         } finally {
219             if (!success) {
220                 ch.close();
221             }
222         }
223 
224         // Bind.
225         if (localAddress != null) {
226             ch.bind(localAddress);
227         }
228 
229         // Connect.
230         return ch.connect(remoteAddress);
231     }
232 
233     /**
234      * Attempts to bind a channel with the specified {@code localAddress}. later the channel can
235      * be connected to a remoteAddress by calling {@link Channel#connect(SocketAddress)}.This method
236      * is useful where bind and connect need to be done in separate steps.
237      *
238      * This can also be useful if you want to set an attachment to the {@link Channel} via
239      * {@link Channel#setAttachment(Object)} so you can use it after the {@link #bind(SocketAddress)}
240      * was done.
241      * <br>
242      * For example:
243      *
244      * <pre>
245      *  ChannelFuture bindFuture = bootstrap.bind(new InetSocketAddress("192.168.0.15", 0));
246      *  Channel channel = bindFuture.getChannel();
247      *  channel.setAttachment(dataObj);
248      *  bootstrap.connect(new InetSocketAddress("192.168.0.30", 8080));
249      * </pre>
250      * <br>
251      *
252      * You can use it then in your handlers like this:
253      *
254      * <pre>
255      *  public class YourHandler extends SimpleChannelUpstreamHandler {
256      *      public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
257      *          Object dataObject = ctx.getChannel().getAttachment();
258      *      }
259      *  }
260      *
261      * </pre>
262      *
263      * @return a future object which notifies when this bind attempt
264      *         succeeds or fails
265      *
266      * @throws ChannelPipelineException
267      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
268      *            failed to create a new {@link ChannelPipeline}
269      */
270     public ChannelFuture bind(final SocketAddress localAddress) {
271 
272         if (localAddress == null) {
273             throw new NullPointerException("localAddress");
274         }
275 
276         ChannelPipeline pipeline;
277         try {
278             pipeline = getPipelineFactory().getPipeline();
279         } catch (Exception e) {
280             throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
281         }
282 
283         // Set the options.
284         Channel ch = getFactory().newChannel(pipeline);
285         boolean success = false;
286         try {
287             ch.getConfig().setOptions(getOptions());
288             success = true;
289         } finally {
290             if (!success) {
291                 ch.close();
292             }
293         }
294 
295         // Bind.
296         return ch.bind(localAddress);
297     }
298 }