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 }
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 *
237 * This can also be useful if you want to set an attachment to the {@link Channel} via
238 * {@link Channel#setAttachment(Object)} so you can use it after the {@link #bind(SocketAddress)}
239 * was done.
240 * <br>
241 * For example:
242 *
243 * <pre>
244 * ChannelFuture bindFuture = bootstrap.bind(new InetSocketAddress("192.168.0.15", 0));
245 * Channel channel = bindFuture.getChannel();
246 * channel.setAttachment(dataObj);
247 * channel.connect(new InetSocketAddress("192.168.0.30", 8080));
248 * </pre>
249 * <br>
250 *
251 * You can use it then in your handlers like this:
252 *
253 * <pre>
254 * public class YourHandler extends SimpleChannelUpstreamHandler {
255 * public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
256 * Object dataObject = ctx.getChannel().getAttachment();
257 * }
258 * }
259 *
260 * </pre>
261 *
262 * @return a future object which notifies when this bind attempt
263 * succeeds or fails
264 *
265 * @throws ChannelPipelineException
266 * if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory}
267 * failed to create a new {@link ChannelPipeline}
268 */
269 public ChannelFuture bind(final SocketAddress localAddress) {
270
271 if (localAddress == null) {
272 throw new NullPointerException("localAddress");
273 }
274
275 ChannelPipeline pipeline;
276 try {
277 pipeline = getPipelineFactory().getPipeline();
278 } catch (Exception e) {
279 throw new ChannelPipelineException("Failed to initialize a pipeline.", e);
280 }
281
282 // Set the options.
283 Channel ch = getFactory().newChannel(pipeline);
284 boolean success = false;
285 try {
286 ch.getConfig().setOptions(getOptions());
287 success = true;
288 } finally {
289 if (!success) {
290 ch.close();
291 }
292 }
293
294 // Bind.
295 return ch.bind(localAddress);
296 }
297 }