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    }