1 /***
2 *
3 * Copyright 2004 Protique Ltd
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 **/
18 package org.codehaus.activemq.transport.tcp;
19
20 import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
21 import EDU.oswego.cs.dl.util.concurrent.BoundedChannel;
22 import EDU.oswego.cs.dl.util.concurrent.BoundedLinkedQueue;
23 import EDU.oswego.cs.dl.util.concurrent.Executor;
24 import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
25 import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.codehaus.activemq.message.Packet;
29 import org.codehaus.activemq.message.WireFormat;
30 import org.codehaus.activemq.transport.AbstractTransportChannel;
31 import org.codehaus.activemq.util.JMSExceptionHelper;
32
33 import javax.jms.JMSException;
34 import java.io.BufferedInputStream;
35 import java.io.DataInputStream;
36 import java.io.DataOutputStream;
37 import java.io.IOException;
38 import java.io.InterruptedIOException;
39 import java.net.InetAddress;
40 import java.net.Socket;
41 import java.net.SocketTimeoutException;
42 import java.net.URI;
43 import java.net.UnknownHostException;
44
45 /***
46 * A tcp implementation of a TransportChannel
47 *
48 * @version $Revision: 1.38 $
49 */
50 public class TcpTransportChannel extends AbstractTransportChannel implements Runnable {
51 private static final int SOCKET_BUFFER_SIZE = 64 * 1024;
52 private static final int SO_TIMEOUT = 5000;
53 private static final Log log = LogFactory.getLog(TcpTransportChannel.class);
54
55 protected Socket socket;
56 private WireFormat wireFormat;
57 private DataOutputStream dataOut;
58 private DataInputStream dataIn;
59 private SynchronizedBoolean closed;
60 private SynchronizedBoolean started;
61 private Object outboundLock;
62 private Executor executor;
63 private Thread thread;
64 private boolean useAsyncSend = false;
65 private boolean serverSide = false;
66 private BoundedChannel exceptionsList;
67
68 /***
69 * Construct basic helpers
70 */
71 protected TcpTransportChannel(WireFormat wireFormat) {
72 this.wireFormat = wireFormat;
73 closed = new SynchronizedBoolean(false);
74 started = new SynchronizedBoolean(false);
75
76 exceptionsList = new BoundedLinkedQueue(10);
77 outboundLock = new Object();
78 if (useAsyncSend) {
79 executor = new PooledExecutor(new BoundedBuffer(1000), 1);
80 }
81 }
82
83 /***
84 * Connect to a remote Node - e.g. a Broker
85 *
86 * @param remoteLocation
87 * @throws JMSException
88 */
89 public TcpTransportChannel(WireFormat wireFormat, URI remoteLocation) throws JMSException {
90 this(wireFormat);
91 try {
92 this.socket = createSocket(remoteLocation);
93 socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
94 socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
95 BufferedInputStream buffIn = new BufferedInputStream(socket.getInputStream());
96 this.dataIn = new DataInputStream(buffIn);
97 TcpBufferedOutputStream buffOut = new TcpBufferedOutputStream(socket.getOutputStream());
98 this.dataOut = new DataOutputStream(buffOut);
99 }
100 catch (Exception ioe) {
101 throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
102 }
103 }
104
105 /***
106 * Connect to a remote Node - e.g. a Broker
107 *
108 * @param remoteLocation
109 * @param localLocation - e.g. local InetAddress and local port
110 * @throws JMSException
111 */
112 public TcpTransportChannel(WireFormat wireFormat, URI remoteLocation, URI localLocation) throws JMSException {
113 this(wireFormat);
114 try {
115 this.socket = createSocket(remoteLocation, localLocation);
116 socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
117 socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
118 BufferedInputStream buffIn = new BufferedInputStream(socket.getInputStream());
119 this.dataIn = new DataInputStream(buffIn);
120 TcpBufferedOutputStream buffOut = new TcpBufferedOutputStream(socket.getOutputStream());
121 this.dataOut = new DataOutputStream(buffOut);
122 }
123 catch (Exception ioe) {
124 throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
125 }
126 }
127
128 /***
129 * @param socket
130 * @throws JMSException
131 */
132 public TcpTransportChannel(WireFormat wireFormat, Socket socket, Executor executor) throws JMSException {
133 this(wireFormat);
134 this.socket = socket;
135 this.executor = executor;
136 this.serverSide = true;
137 try {
138 socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
139 socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
140 TcpBufferedOutputStream buffOut = new TcpBufferedOutputStream(socket.getOutputStream());
141 this.dataOut = new DataOutputStream(buffOut);
142 BufferedInputStream buffIn = new BufferedInputStream(socket.getInputStream());
143 this.dataIn = new DataInputStream(buffIn);
144 }
145 catch (IOException ioe) {
146 throw JMSExceptionHelper.newJMSException("Initialization of TcpTransportChannel failed: " + ioe, ioe);
147 }
148 }
149
150 /***
151 * close the channel
152 */
153 public void stop() {
154 if (closed.commit(false, true)) {
155 super.stop();
156 try {
157 stopExecutor(executor);
158 dataOut.close();
159 dataIn.close();
160 socket.close();
161
162
163
164 }
165 catch (Exception e) {
166 log.warn("Caught while closing: " + e + ". Now Closed", e);
167 }
168
169 }
170 }
171
172 /***
173 * start listeneing for events
174 *
175 * @throws JMSException if an error occurs
176 */
177 public void start() throws JMSException {
178 if (started.commit(false, true)) {
179 thread = new Thread(this, "Thread:" + toString());
180 thread.setDaemon(true);
181 if (!serverSide) {
182 thread.setPriority(Thread.NORM_PRIORITY + 2);
183 }
184 thread.start();
185 }
186 }
187
188
189 /***
190 * Asynchronously send a Packet
191 *
192 * @param packet
193 * @throws JMSException
194 */
195 public void asyncSend(final Packet packet) throws JMSException {
196 if (executor != null) {
197 try {
198 executor.execute(new Runnable() {
199 public void run() {
200 try {
201 if (!closed.get()) {
202 doAsyncSend(packet);
203 }
204 }
205 catch (JMSException e) {
206 try {
207 exceptionsList.put(e);
208 }
209 catch (InterruptedException e1) {
210 log.warn("Failed to add element to exception list: " + e1);
211 }
212 }
213 }
214
215 });
216 }
217 catch (InterruptedException e) {
218 log.info("Caught: " + e, e);
219 }
220 try {
221 JMSException e = (JMSException) exceptionsList.poll(0);
222 if (e != null) {
223 throw e;
224 }
225 }
226 catch (InterruptedException e1) {
227 log.warn("Failed to remove element to exception list: " + e1);
228 }
229 }
230 else {
231 doAsyncSend(packet);
232 }
233 }
234
235 /***
236 * @return false
237 */
238 public boolean isMulticast() {
239 return false;
240 }
241
242 /***
243 * reads packets from a Socket
244 */
245 public void run() {
246 log.trace("TCP consumer thread starting");
247 int count = 0;
248 while (!closed.get()) {
249 if (serverSide && ++count > 500) {
250 count = 0;
251 Thread.yield();
252 }
253 int type = 0;
254 try {
255 socket.setSoTimeout(SO_TIMEOUT);
256 while ((type = dataIn.read()) == 0) {
257 }
258 if (type == -1) {
259 log.trace("The socket peer is now closed");
260
261 stop();
262 }
263 else {
264 socket.setSoTimeout(0);
265
266 Packet packet = wireFormat.readPacket(type, dataIn);
267 if (packet != null) {
268 doConsumePacket(packet);
269 }
270 }
271 }
272 catch (SocketTimeoutException ste) {
273
274 }
275 catch (InterruptedIOException ioe) {
276
277
278
279
280
281
282 }
283 catch (IOException e) {
284 doClose(e);
285 }
286 }
287 }
288
289 /***
290 * pretty print for object
291 *
292 * @return String representation of this object
293 */
294 public String toString() {
295 return "TcpTransportChannel: " + socket;
296 }
297
298 /***
299 * Actually performs the async send of a packet
300 *
301 * @param packet
302 * @throws JMSException
303 */
304 protected void doAsyncSend(Packet packet) throws JMSException {
305 try {
306 synchronized (outboundLock) {
307 wireFormat.writePacket(packet, dataOut);
308 dataOut.flush();
309 }
310 }
311 catch (IOException e) {
312 throw JMSExceptionHelper.newJMSException("asyncSend failed: " + e, e);
313 }
314 }
315
316
317 private void doClose(Exception ex) {
318 if (!closed.get()) {
319 onAsyncException(JMSExceptionHelper.newJMSException("Error reading socket: " + ex, ex));
320 stop();
321 }
322 }
323
324 /***
325 * Factory method to create a new socket
326 *
327 * @param remoteLocation the URI to connect to
328 * @return the newly created socket
329 * @throws UnknownHostException
330 * @throws IOException
331 */
332 protected Socket createSocket(URI remoteLocation) throws UnknownHostException, IOException {
333 return new Socket(remoteLocation.getHost(), remoteLocation.getPort());
334 }
335
336 /***
337 * Factory method to create a new socket
338 *
339 * @param remoteLocation
340 * @param localLocation
341 * @return @throws IOException
342 * @throws IOException
343 * @throws UnknownHostException
344 */
345 protected Socket createSocket(URI remoteLocation, URI localLocation) throws IOException, UnknownHostException {
346 return new Socket(remoteLocation.getHost(), remoteLocation.getPort(), InetAddress.getByName(localLocation
347 .getHost()), localLocation.getPort());
348 }
349
350 }