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 org.jboss.netty.channel.Channel;
19  import org.jboss.netty.channel.ChannelConfig;
20  import org.jboss.netty.channel.ChannelFactory;
21  import org.jboss.netty.channel.ChannelFuture;
22  import org.jboss.netty.channel.ChannelHandler;
23  import org.jboss.netty.channel.ChannelPipeline;
24  import org.jboss.netty.channel.ChannelPipelineException;
25  import org.jboss.netty.channel.ChannelPipelineFactory;
26  import org.jboss.netty.channel.Channels;
27  
28  import java.net.InetSocketAddress;
29  import java.net.SocketAddress;
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     }
117 
118     /**
119      * Creates a new instance with the specified initial {@link ChannelFactory}.
120      */
121     public ClientBootstrap(ChannelFactory channelFactory) {
122         super(channelFactory);
123     }
124 
125     /**
126      * Attempts a new connection with the current {@code "remoteAddress"} and
127      * {@code "localAddress"} option.  If the {@code "localAddress"} option is
128      * not set, the local address of a new channel is determined automatically.
129      * This method is similar to the following code:
130      *
131      * <pre>
132      * {@link ClientBootstrap} b = ...;
133      * b.connect(b.getOption("remoteAddress"), b.getOption("localAddress"));
134      * </pre>
135      *
136      * @return a future object which notifies when this connection attempt
137      *         succeeds or fails
138      *
139      * @throws IllegalStateException
140      *         if {@code "remoteAddress"} option was not set
141      * @throws ClassCastException
142      *         if {@code "remoteAddress"} or {@code "localAddress"} option's
143      *            value is neither a {@link SocketAddress} nor {@code null}
144      * @throws ChannelPipelineException
145      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
146      *            failed to create a new {@link ChannelPipeline}
147      */
148     public ChannelFuture connect() {
149         SocketAddress remoteAddress = (SocketAddress) getOption("remoteAddress");
150         if (remoteAddress == null) {
151             throw new IllegalStateException("remoteAddress option is not set.");
152         }
153         return connect(remoteAddress);
154     }
155 
156     /**
157      * Attempts a new connection with the specified {@code remoteAddress} and
158      * the current {@code "localAddress"} option. If the {@code "localAddress"}
159      * option is not set, the local address of a new channel is determined
160      * automatically.  This method is identical with the following code:
161      *
162      * <pre>
163      * {@link ClientBootstrap} b = ...;
164      * b.connect(remoteAddress, b.getOption("localAddress"));
165      * </pre>
166      *
167      * @return a future object which notifies when this connection attempt
168      *         succeeds or fails
169      *
170      * @throws ClassCastException
171      *         if {@code "localAddress"} option's value is
172      *            neither a {@link SocketAddress} nor {@code null}
173      * @throws ChannelPipelineException
174      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
175      *            failed to create a new {@link ChannelPipeline}
176      */
177     public ChannelFuture connect(SocketAddress remoteAddress) {
178         if (remoteAddress == null) {
179             throw new NullPointerException("remoteAddress");
180         }
181         SocketAddress localAddress = (SocketAddress) getOption("localAddress");
182         return connect(remoteAddress, localAddress);
183     }
184 
185     /**
186      * Attempts a new connection with the specified {@code remoteAddress} and
187      * the specified {@code localAddress}.  If the specified local address is
188      * {@code null}, the local address of a new channel is determined
189      * automatically.
190      *
191      * @return a future object which notifies when this connection attempt
192      *         succeeds or fails
193      *
194      * @throws ChannelPipelineException
195      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
196      *            failed to create a new {@link ChannelPipeline}
197      */
198     public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
199 
200         if (remoteAddress == null) {
201             throw new NullPointerException("remoteAddress");
202         }
203 
204         ChannelPipeline pipeline;
205         try {
206             pipeline = getPipelineFactory().getPipeline();
207         } catch (Exception e) {
208             throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
209         }
210 
211         // Set the options.
212         Channel ch = getFactory().newChannel(pipeline);
213         boolean success = false;
214         try {
215             ch.getConfig().setOptions(getOptions());
216             success = true;
217         } finally {
218             if (!success) {
219                 ch.close();
220             }
221         }
222 
223         // Bind.
224         if (localAddress != null) {
225             ch.bind(localAddress);
226         }
227 
228         // Connect.
229         return ch.connect(remoteAddress);
230     }
231 
232     /**
233      * Attempts to bind a channel with the specified {@code localAddress}. later the channel can
234      * be connected to a remoteAddress by calling {@link Channel#connect(SocketAddress)}.This method
235      * is useful where bind and connect need to be done in separate steps.
236      * <p>
237      * For an instance, a user can set an attachment to the {@link Channel} via
238      * {@link Channel#setAttachment(Object)} before beginning a connection attempt so that the user can access
239      * the attachment once the connection is established:
240      *
241      * <pre>
242      *  ChannelFuture bindFuture = bootstrap.bind(new InetSocketAddress("192.168.0.15", 0));
243      *  Channel channel = bindFuture.getChannel();
244      *  channel.setAttachment(dataObj);
245      *  channel.connect(new InetSocketAddress("192.168.0.30", 8080));
246      * </pre>
247      *
248      * The attachment can be accessed then in the handler like the following:
249      *
250      * <pre>
251      *  public class YourHandler extends SimpleChannelUpstreamHandler {
252      *      public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
253      *          Object dataObject = ctx.getChannel().getAttachment();
254      *      }
255      *  }
256      *
257      * </pre>
258      *
259      * @return a future object which notifies when this bind attempt
260      *         succeeds or fails
261      *
262      * @throws ChannelPipelineException
263      *         if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
264      *            failed to create a new {@link ChannelPipeline}
265      */
266     public ChannelFuture bind(final SocketAddress localAddress) {
267 
268         if (localAddress == null) {
269             throw new NullPointerException("localAddress");
270         }
271 
272         ChannelPipeline pipeline;
273         try {
274             pipeline = getPipelineFactory().getPipeline();
275         } catch (Exception e) {
276             throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
277         }
278 
279         // Set the options.
280         Channel ch = getFactory().newChannel(pipeline);
281         boolean success = false;
282         try {
283             ch.getConfig().setOptions(getOptions());
284             success = true;
285         } finally {
286             if (!success) {
287                 ch.close();
288             }
289         }
290 
291         // Bind.
292         return ch.bind(localAddress);
293     }
294 }