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 }