001 /**
002 * Copyright (C) 2012 FuseSource, Inc.
003 * http://fusesource.com
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.fusesource.hawtdispatch.transport;
019
020 import org.fusesource.hawtdispatch.Dispatch;
021 import org.fusesource.hawtdispatch.DispatchQueue;
022 import org.fusesource.hawtdispatch.DispatchSource;
023
024 import java.io.IOException;
025 import java.net.*;
026 import java.nio.channels.SelectionKey;
027 import java.nio.channels.ServerSocketChannel;
028 import java.nio.channels.SocketChannel;
029
030 /**
031 * A TCP based implementation of {@link TransportServer}
032 *
033 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
034 */
035
036 public class TcpTransportServer implements TransportServer {
037
038 private final String bindScheme;
039 private final InetSocketAddress bindAddress;
040
041 private int backlog = 100;
042
043 private ServerSocketChannel channel;
044 private TransportServerListener listener;
045 private DispatchQueue dispatchQueue;
046 private DispatchSource acceptSource;
047 private int receiveBufferSize = 64*1024;
048
049 public TcpTransportServer(URI location) throws UnknownHostException {
050 bindScheme = location.getScheme();
051 String host = location.getHost();
052 host = (host == null || host.length() == 0) ? "::" : host;
053 bindAddress = new InetSocketAddress(InetAddress.getByName(host), location.getPort());
054 }
055
056 public void setTransportServerListener(TransportServerListener listener) {
057 this.listener = listener;
058 }
059
060 public InetSocketAddress getSocketAddress() {
061 return (InetSocketAddress) channel.socket().getLocalSocketAddress();
062 }
063
064 public DispatchQueue getDispatchQueue() {
065 return dispatchQueue;
066 }
067
068 public void setDispatchQueue(DispatchQueue dispatchQueue) {
069 this.dispatchQueue = dispatchQueue;
070 }
071
072 public void suspend() {
073 acceptSource.suspend();
074 }
075
076 public void resume() {
077 acceptSource.resume();
078 }
079
080 public void start() throws Exception {
081 start(null);
082 }
083 public void start(Runnable onCompleted) throws Exception {
084
085 try {
086 channel = ServerSocketChannel.open();
087 channel.configureBlocking(false);
088 try {
089 channel.socket().setReceiveBufferSize(receiveBufferSize);
090 } catch (SocketException ignore) {
091 }
092 channel.socket().bind(bindAddress, backlog);
093 } catch (IOException e) {
094 throw new IOException("Failed to bind to server socket: " + bindAddress + " due to: " + e);
095 }
096
097 acceptSource = Dispatch.createSource(channel, SelectionKey.OP_ACCEPT, dispatchQueue);
098 acceptSource.setEventHandler(new Runnable() {
099 public void run() {
100 try {
101 SocketChannel client = channel.accept();
102 while( client!=null ) {
103 handleSocket(client);
104 client = channel.accept();
105 }
106 } catch (Exception e) {
107 listener.onAcceptError(e);
108 }
109 }
110 });
111 acceptSource.setCancelHandler(new Runnable() {
112 public void run() {
113 try {
114 channel.close();
115 } catch (IOException e) {
116 }
117 }
118 });
119 acceptSource.resume();
120 if( onCompleted!=null ) {
121 dispatchQueue.execute(onCompleted);
122 }
123 }
124
125 public String getBoundAddress() {
126 try {
127 return new URI(bindScheme, null, bindAddress.getAddress().getHostAddress(), channel.socket().getLocalPort(), null, null, null).toString();
128 } catch (URISyntaxException e) {
129 throw new RuntimeException(e);
130 }
131 }
132
133 public void stop() throws Exception {
134 stop(null);
135 }
136 public void stop(final Runnable onCompleted) throws Exception {
137 if( acceptSource.isCanceled() ) {
138 onCompleted.run();
139 } else {
140 acceptSource.setCancelHandler(new Runnable() {
141 public void run() {
142 try {
143 channel.close();
144 } catch (IOException e) {
145 }
146 onCompleted.run();
147 }
148 });
149 acceptSource.cancel();
150 }
151 }
152
153 public int getBacklog() {
154 return backlog;
155 }
156
157 public void setBacklog(int backlog) {
158 this.backlog = backlog;
159 }
160
161 protected final void handleSocket(SocketChannel socket) throws Exception {
162 TcpTransport transport = createTransport();
163 transport.connected(socket);
164 listener.onAccept(transport);
165 }
166
167 protected TcpTransport createTransport() {
168 return new TcpTransport();
169 }
170
171 /**
172 * @return pretty print of this
173 */
174 public String toString() {
175 return getBoundAddress();
176 }
177
178 public int getReceiveBufferSize() {
179 return receiveBufferSize;
180 }
181
182 public void setReceiveBufferSize(int receiveBufferSize) {
183 this.receiveBufferSize = receiveBufferSize;
184 }
185
186 }