'From etoys3.0 of 7 March 2008 [latest update: #2047] on 3 July 2008 at 8:38:06 pm'! "Change Set: connQPorts-bf Date: 1 July 2008 Author: Bert Freudenberg Make ConnectionQueue work with a collection of port numbers to try listening on"! !ConnectionQueue commentStamp: '' prior: 0! A ConnectionQueue listens on a given port number and collects a queue of client connections. In order to handle state changes quickly, a ConnectionQueue has its own process that: (a) tries to keep a socket listening on the port whenever the queue isn't already full of connections and (b) prunes stale connections out of the queue to make room for fresh ones. The portNumber can be initialized to a collection, in which case all ports will be probed to create a listening socket. Once successfull, the portNumber is set to the chosen port.! !ConnectionQueue methodsFor: 'public' stamp: 'bf 7/1/2008 18:27'! portNumber ^ portNumber isCollection ifTrue: [portNumber first] ifFalse: [portNumber]! ! !ConnectionQueue methodsFor: 'public' stamp: 'bf 7/1/2008 18:27'! portNumbers ^ portNumber isCollection ifTrue: [portNumber] ifFalse: [{portNumber}]! ! !ConnectionQueue methodsFor: 'private' stamp: 'bf 7/1/2008 19:32'! createListeningSocketWithBacklog: aNumber | addressInfo sock | self portNumbers do: [:trialportNumber | addressInfo := SocketAddressInformation forHost: '' service: trialportNumber asString flags: SocketAddressInformation passiveFlag addressFamily: SocketAddressInformation addressFamilyINET4 socketType: SocketAddressInformation socketTypeStream protocol: SocketAddressInformation protocolTCP. sock := [addressInfo first listenWithBacklog: aNumber] ifError: [nil]. sock ifNotNil: [ portNumber := trialportNumber. ^ sock]]. ^ nil! ! !ConnectionQueue methodsFor: 'private' stamp: 'bf 7/1/2008 19:32'! listenLoop "Private!! This loop is run in a separate process. It will establish up to maxQueueLength connections on the given port." "Details: When out of sockets or queue is full, retry more frequently, since a socket may become available, space may open in the queue, or a previously queued connection may be aborted by the client, making it available for a fresh connection." "Note: If the machine is disconnected from the network while the server is running, the currently waiting socket will go from 'isWaitingForConnection' to 'unconnected', and attempts to create new sockets will fail. When this happens, delete the broken socket and keep trying to create a socket in case the network connection is re-established. Connecting and disconnecting was tested under PPP on Mac system 8.1. It is not if this will work on other platforms." | newConnection | NetNameResolver useOldNetwork ifTrue: [^self oldListenLoop]. "We'll accept four simultanous connections at the same time" socket := self createListeningSocketWithBacklog: 4. "If the listener is not valid then the we cannot use the BSD style accept() mechanism." socket isValid ifFalse: [^self oldStyleListenLoop]. [true] whileTrue: [ socket isValid ifFalse: [ "socket has stopped listening for some reason" socket destroy. (Delay forMilliseconds: 10) wait. ^self listenLoop ]. newConnection _ socket waitForAcceptFor: 10. (newConnection notNil and:[newConnection isConnected]) ifTrue: [accessSema critical: [connections addLast: newConnection]. newConnection _ nil]. self pruneStaleConnections]. ! ! !ConnectionQueue methodsFor: 'private' stamp: 'bf 7/1/2008 19:33'! oldListenLoop "Private!! This loop is run in a separate process. It will establish up to maxQueueLength connections on the given port." "Details: When out of sockets or queue is full, retry more frequently, since a socket may become available, space may open in the queue, or a previously queued connection may be aborted by the client, making it available for a fresh connection." "Note: If the machine is disconnected from the network while the server is running, the currently waiting socket will go from 'isWaitingForConnection' to 'unconnected', and attempts to create new sockets will fail. When this happens, delete the broken socket and keep trying to create a socket in case the network connection is re-established. Connecting and disconnecting was tested under PPP on Mac system 8.1. It is not if this will work on other platforms." | newConnection | socket _ Socket newTCP. "We'll accept four simultanous connections at the same time" socket listenOn: self portNumber backlogSize: 4. "If the listener is not valid then the we cannot use the BSD style accept() mechanism." socket isValid ifFalse: [^self oldStyleListenLoop]. [true] whileTrue: [ socket isValid ifFalse: [ "socket has stopped listening for some reason" socket destroy. (Delay forMilliseconds: 10) wait. ^self oldListenLoop ]. newConnection _ socket waitForAcceptFor: 10. (newConnection notNil and:[newConnection isConnected]) ifTrue: [accessSema critical: [connections addLast: newConnection]. newConnection _ nil]. self pruneStaleConnections]. ! ! !ConnectionQueue methodsFor: 'private' stamp: 'bf 7/1/2008 19:34'! oldStyleListenLoop "Private!! This loop is run in a separate process. It will establish up to maxQueueLength connections on the given port." "Details: When out of sockets or queue is full, retry more frequently, since a socket may become available, space may open in the queue, or a previously queued connection may be aborted by the client, making it available for a fresh connection." "Note: If the machine is disconnected from the network while the server is running, the currently waiting socket will go from 'isWaitingForConnection' to 'unconnected', and attempts to create new sockets will fail. When this happens, delete the broken socket and keep trying to create a socket in case the network connection is re-established. Connecting and disconnecting was tested under PPP on Mac system 8.1. It is not if this will work on other platforms." [true] whileTrue: [ ((socket == nil) and: [connections size < maxQueueLength]) ifTrue: [ "try to create a new socket for listening" socket _ Socket createIfFail: [nil]]. socket == nil ifTrue: [(Delay forMilliseconds: 100) wait] ifFalse: [ socket isUnconnected ifTrue: [socket listenOn: self portNumber]. [socket waitForConnectionFor: 10] on: ConnectionTimedOut do: [:ex | socket isConnected ifTrue: [ "connection established" accessSema critical: [connections addLast: socket]. socket _ nil] ifFalse: [ socket isWaitingForConnection ifFalse: [socket destroy. socket _ nil]]]]. "broken socket; start over" self pruneStaleConnections]. ! !