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 }