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 }