(********************************************************************) (* *) (* listener.s7i Support for inet listener *) (* Copyright (C) 2007 - 2009, 2011 Thomas Mertes *) (* *) (* This file is part of the Seed7 Runtime Library. *) (* *) (* The Seed7 Runtime Library is free software; you can *) (* redistribute it and/or modify it under the terms of the GNU *) (* Lesser General Public License as published by the Free Software *) (* Foundation; either version 2.1 of the License, or (at your *) (* option) any later version. *) (* *) (* The Seed7 Runtime Library is distributed in the hope that it *) (* will be useful, but WITHOUT ANY WARRANTY; without even the *) (* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *) (* PURPOSE. See the GNU Lesser General Public License for more *) (* details. *) (* *) (* You should have received a copy of the GNU Lesser General *) (* Public License along with this program; if not, write to the *) (* Free Software Foundation, Inc., 51 Franklin Street, *) (* Fifth Floor, Boston, MA 02110-1301, USA. *) (* *) (********************************************************************) include "sockbase.s7i"; include "socket.s7i"; include "poll.s7i"; const type: inetListener is sub baseListener struct var PRIMITIVE_SOCKET: sock is PRIMITIVE_NULL_SOCKET; var socketAddress: addr is socketAddress.value; var string: service is ""; var pollData: checkedSocks is pollData.value; var file: existingConnection is STD_NULL; var file: newConnection is STD_NULL; end struct; type_implements_interface(inetListener, listener); (** * Create a bound internet listener for a port at localhost. * The listerner is responsible for incoming connections of the * specified port. The listener also manages its accepted sockets. * Processing requests from port 1080 can be done with: * aListener := openInetListener(1080); * listen(aListener, 10); * while TRUE do * sock := accept(aListener); * # Read and process the request from sock. * close(sock); * end while; * The example above manages requests from * different clients sequentially. The function * [[#waitForRequest(inout_listener,inout_file,inout_file)|waitForRequest]] * can be used to process interleaved requests from several clients. * @return the bound internet listener. * @exception FILE_ERROR A system function returns an error. * @exception RANGE_ERROR The port is not in the range 0 to 65535. * @exception MEMORY_ERROR An out of memory situation occurred. *) const func listener: openInetListener (in integer: portNumber) is func result var listener: newListener is listener.value; local var socketAddress: address is socketAddress.value; var PRIMITIVE_SOCKET: open_socket is PRIMITIVE_NULL_SOCKET; var inetListener: new_listener is inetListener.value; begin address := inetListenerAddress(portNumber); open_socket := PRIMITIVE_SOCKET(addrFamily(address), SOCK_STREAM, 0); if open_socket <> PRIMITIVE_NULL_SOCKET then new_listener.addr := address; new_listener.service := service(address); setSockOpt(open_socket, SO_REUSEADDR, TRUE); bind(open_socket, new_listener.addr); new_listener.sock := open_socket; addCheck(new_listener.checkedSocks, open_socket, POLLIN, STD_NULL); newListener := toInterface(new_listener); end if; end func; (** * Close the listener ''aListener''. * A listener manages accepted sockets (its existing connections). * When the listener is closed all references to the listener * are removed from the accepted sockets. *) const proc: close (inout inetListener: aListener) is func local var file: aFile is STD_NULL; begin close(aListener.sock); iterChecks(aListener.checkedSocks, POLLINOUT); for aFile range aListener.checkedSocks do if aFile <> STD_NULL then release(aFile); end if; end for; clear(aListener.checkedSocks); end func; const proc: signOn (inout inetListener: aListener, in socket: sock) is func begin addCheck(aListener.checkedSocks, sock, POLLIN); end func; const proc: signOff (inout inetListener: aListener, in socket: sock) is func begin removeCheck(aListener.checkedSocks, sock, POLLIN); end func; (** * Listen for [[socket]] connections and limit the incoming queue. * The ''backlog'' argument defines the maximum length to which * the queue of pending connections for ''aListener'' may grow. * @exception FILE_ERROR A system function returns an error. *) const proc: listen (in inetListener: aListener, in integer: backlog) is func begin listen(aListener.sock, backlog); end func; (** * Create a new accepted connection [[socket]] for ''aListener''. * The function waits until at least one connection request is * in the listeners queue of pending connections. Then it extracts * the first connection request from the listeners queue. This * request is accepted and a connection [[socket]] is created for it. * A listener manages accepted sockets (its existing connections). * When an accepted [[socket]] is closed it is signed off from the * listener. * @return the accepted connection [[socket]]. * @exception FILE_ERROR A system function returns an error. * @exception MEMORY_ERROR An out of memory situation occurred. *) const func file: accept (inout inetListener: aListener) is func result var file: newFile is STD_NULL; local var PRIMITIVE_SOCKET: accepted_socket is PRIMITIVE_NULL_SOCKET; var socket: new_socket is socket.value; begin accepted_socket := accept(aListener.sock, new_socket.addr); if accepted_socket <> PRIMITIVE_NULL_SOCKET then new_socket.sock := accepted_socket; new_socket.service := aListener.service; new_socket.acceptedFrom := aListener; newFile := toInterface(new_socket); addCheck(aListener.checkedSocks, newFile, POLLIN); end if; end func; const proc: waitForRequest (inout inetListener: aListener) is func begin if hasNext(aListener.checkedSocks) then aListener.newConnection := STD_NULL; aListener.existingConnection := nextFile(aListener.checkedSocks); if aListener.existingConnection = STD_NULL then # Skip the listener, which returns STD_NULL, if it is ready. aListener.existingConnection := nextFile(aListener.checkedSocks); end if; else poll(aListener.checkedSocks); if getFinding(aListener.checkedSocks, aListener.sock) = POLLIN then aListener.newConnection := accept(aListener); # writeln("accepted"); else aListener.newConnection := STD_NULL; end if; iterFindings(aListener.checkedSocks, POLLIN); aListener.existingConnection := nextFile(aListener.checkedSocks); if aListener.existingConnection = STD_NULL then # Skip the listener, which returns STD_NULL, if it is ready. aListener.existingConnection := nextFile(aListener.checkedSocks); end if; end if; end func; const func file: getExistingConnection (in inetListener: aListener) is return aListener.existingConnection; const func file: getNewConnection (in inetListener: aListener) is return aListener.newConnection; (** * Wait until a request can be read or an incoming connection is accepted. * The function ''waitForRequest'' can be used to process interleaved * requests from several clients. A listener manages accepted sockets * (its existing connections). This function checks the accepted * sockets for available input (it is possible to read without * blocking). The port of the listener is also checked for incoming * connections. The function returns when input is available for an * existing connection or when a new incoming connection was accepted. * Processing requests from port 2021 can be done with: * aListener := openInetListener(2021); * listen(aListener, 10); * while TRUE do * waitForRequest(aListener, existingConnection, newConnection); * if existingConnection <> STD_NULL then * # Read and process the request from existingConnection. * end if; * if newConnection <> STD_NULL then * # Send welcome message to newConnection. * end if; * end while; * @param existingConnection A random existing connection, were * a read will not block, is assigned. If no existing * connection has available input, [[null_file#STD_NULL|STD_NULL]] * is assigned. * @param newConnection A new accepted connection is assigned. * If no incoming connection is present, * [[null_file#STD_NULL|STD_NULL]] is assigned. *) const proc: waitForRequest (inout listener: aListener, inout file: existingConnection, inout file: newConnection) is func begin waitForRequest(aListener); existingConnection := getExistingConnection(aListener); newConnection := getNewConnection(aListener); end func;