001 /**
002 * Copyright (C) 2010-2011, FuseSource Corp. All rights reserved.
003 *
004 * http://fusesource.com
005 *
006 * The software in this package is published under the terms of the
007 * CDDL license a copy of which has been included with this distribution
008 * in the license.txt file.
009 */
010 package org.fusesource.hawtdispatch.transport;
011
012 import org.fusesource.hawtdispatch.Dispatch;
013 import org.fusesource.hawtdispatch.DispatchQueue;
014 import org.fusesource.hawtdispatch.DispatchSource;
015
016 import java.io.IOException;
017 import java.net.*;
018 import java.nio.channels.SelectionKey;
019 import java.nio.channels.ServerSocketChannel;
020 import java.nio.channels.SocketChannel;
021 import java.util.Map;
022
023 /**
024 * A TCP based implementation of {@link TransportServer}
025 *
026 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
027 */
028
029 public class TcpTransportServer implements TransportServer {
030
031 private final String bindScheme;
032 private final InetSocketAddress bindAddress;
033
034 private int backlog = 100;
035
036 private ServerSocketChannel channel;
037 private TransportServerListener listener;
038 private DispatchQueue dispatchQueue;
039 private DispatchSource acceptSource;
040 private int receive_buffer_size = 64*1024;
041
042 public TcpTransportServer(URI location) throws UnknownHostException {
043 bindScheme = location.getScheme();
044 String host = location.getHost();
045 host = (host == null || host.length() == 0) ? "::" : host;
046 bindAddress = new InetSocketAddress(InetAddress.getByName(host), location.getPort());
047 }
048
049 public void setTransportServerListener(TransportServerListener listener) {
050 this.listener = listener;
051 }
052
053 public InetSocketAddress getSocketAddress() {
054 return (InetSocketAddress) channel.socket().getLocalSocketAddress();
055 }
056
057 public DispatchQueue getDispatchQueue() {
058 return dispatchQueue;
059 }
060
061 public void setDispatchQueue(DispatchQueue dispatchQueue) {
062 this.dispatchQueue = dispatchQueue;
063 }
064
065 public void suspend() {
066 acceptSource.suspend();
067 }
068
069 public void resume() {
070 acceptSource.resume();
071 }
072
073 public void start() throws Exception {
074 start(null);
075 }
076 public void start(Runnable onCompleted) throws Exception {
077
078 try {
079 channel = ServerSocketChannel.open();
080 channel.configureBlocking(false);
081 try {
082 channel.socket().setReceiveBufferSize(receive_buffer_size);
083 } catch (SocketException ignore) {
084 }
085 channel.socket().bind(bindAddress, backlog);
086 } catch (IOException e) {
087 throw new IOException("Failed to bind to server socket: " + bindAddress + " due to: " + e);
088 }
089
090 acceptSource = Dispatch.createSource(channel, SelectionKey.OP_ACCEPT, dispatchQueue);
091 acceptSource.setEventHandler(new Runnable() {
092 public void run() {
093 try {
094 SocketChannel client = channel.accept();
095 while( client!=null ) {
096 handleSocket(client);
097 client = channel.accept();
098 }
099 } catch (Exception e) {
100 listener.onAcceptError(e);
101 }
102 }
103 });
104 acceptSource.setCancelHandler(new Runnable() {
105 public void run() {
106 try {
107 channel.close();
108 } catch (IOException e) {
109 }
110 }
111 });
112 acceptSource.resume();
113 if( onCompleted!=null ) {
114 dispatchQueue.execute(onCompleted);
115 }
116 }
117
118 public String getBoundAddress() {
119 try {
120 return new URI(bindScheme, null, bindAddress.getAddress().getHostAddress(), channel.socket().getLocalPort(), null, null, null).toString();
121 } catch (URISyntaxException e) {
122 throw new RuntimeException(e);
123 }
124 }
125
126 public String getConnectAddress() {
127 try {
128 return new URI(bindScheme, null, resolveHostName(), channel.socket().getLocalPort(), null, null, null).toString();
129 } catch (URISyntaxException e) {
130 throw new RuntimeException(e);
131 }
132 }
133
134
135 protected String resolveHostName() {
136 String result;
137 if (bindAddress.getAddress().isAnyLocalAddress()) {
138 // make it more human readable and useful, an alternative to 0.0.0.0
139 try {
140 result = InetAddress.getLocalHost().getCanonicalHostName();
141 } catch (UnknownHostException e) {
142 result = "localhost";
143 }
144 } else {
145 result = bindAddress.getAddress().getCanonicalHostName();
146 }
147 return result;
148 }
149
150 public void stop() throws Exception {
151 stop(null);
152 }
153 public void stop(final Runnable onCompleted) throws Exception {
154 if( acceptSource.isCanceled() ) {
155 onCompleted.run();
156 } else {
157 acceptSource.setCancelHandler(new Runnable() {
158 public void run() {
159 try {
160 channel.close();
161 } catch (IOException e) {
162 }
163 onCompleted.run();
164 }
165 });
166 acceptSource.cancel();
167 }
168 }
169
170 public int getBacklog() {
171 return backlog;
172 }
173
174 public void setBacklog(int backlog) {
175 this.backlog = backlog;
176 }
177
178 protected final void handleSocket(SocketChannel socket) throws Exception {
179 TcpTransport transport = createTransport();
180 transport.connected(socket);
181 listener.onAccept(transport);
182 }
183
184 protected TcpTransport createTransport() {
185 return new TcpTransport();
186 }
187
188 /**
189 * @return pretty print of this
190 */
191 public String toString() {
192 return getBoundAddress();
193 }
194
195
196 public int getReceive_buffer_size() {
197 return receive_buffer_size;
198 }
199
200 public void setReceive_buffer_size(int receive_buffer_size) {
201 this.receive_buffer_size = receive_buffer_size;
202 }
203
204 }