'From etoys3.0 of 7 March 2008 [latest update: #2047] on 3 July 2008 at 8:40:19 pm'! "Change Set: etoyP2P-bf Date: 1 July 2008 Author: Bert Freudenberg Make Etoys peer-to-peer networking use multiple ports. This involves changing the wire-format, but I found a backwards-compatible way to sneak in an optional header. Also, add a class comment explaining that weird EToyPeerToPeer class"! !EToyPeerToPeer commentStamp: 'bf 7/3/2008 20:06' prior: 0! Peer-to-peer here means that there is no distinguished server. In fact, every party runs a server (a listening instance of myself). Connections are only used one-way, for sending messages, no return is expected. Instances of this class are used for one of three purposes: sending data, listening for connections, or receiving from a connection. Any particular instance is only used for one purpose. The transmitted data format is very simple: a header, followed by a space character, followed by data bytes. The header typically is just a single decimal number as ASCII string indicating the data size. An optional header may follow the integer, it is used to transmit a non-default port number. Instance variables: If used for sending: ipAddress -- 'addr:port' to send to (:port is optional) socket -- sending socket process -- background process doing the send dataQueue -- data to send If used for listening: connectionQueue -- listening for connections process -- waiting for accepts in queue socket -- accepted connection If used for receiving: socket -- receiving socket process -- reading from socket remoteSocketAddress -- 'addr:port' of remote sender (:port is optional) leftOverData -- buffered data ! !EToyListenerMorph class methodsFor: 'as yet unclassified' stamp: 'bf 7/3/2008 19:13'! listeningPort ^GlobalListener ifNotNil: [GlobalListener listeningPort] ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'bf 7/1/2008 19:55'! doConnectForSend | addr port | addr := NetNameResolver addressForName: (ipAddress copyUpTo: $:). addr ifNil: [ communicatorMorph commResult: {#message -> ('could not find ',ipAddress)}. ^false]. port := (ipAddress copyAfter: $:) asInteger. port ifNil: [port := self class eToyCommunicationsPorts first]. socket connectNonBlockingTo: addr port: port. [socket waitForConnectionFor: 15] on: ConnectionTimedOut do: [:ex | communicatorMorph commResult: {#message -> ('no connection to ',ipAddress,' (', ipAddress,')')}. ^false]. ^true ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'bf 7/3/2008 19:36'! doSendData | totalLength myData allTheData | myData _ dataQueue next ifNil: [socket sendData: '0 '. ^false]. totalLength _ (myData collect: [ :x | x size]) sum. socket sendData: totalLength printString, self makeOptionalHeader, ' '. allTheData _ WriteStream on: (String new: totalLength). myData do: [ :chunk | allTheData nextPutAll: chunk asString]. NebraskaDebug at: #peerBytesSent add: {totalLength}. self sendDataCautiously: allTheData contents. ^true ! ! !EToyPeerToPeer methodsFor: 'sending' stamp: 'bf 7/3/2008 19:53'! makeOptionalHeader "Optional header format is (key:value;key:value) and it must not contain spaces. This is designed to be backwards-compatible with old receivers who receive a header as anything up to a space, but only actually use an initial size integer" | p | p := EToyListenerMorph listeningPort. ^(p == nil or: [p == self class eToyCommunicationsPorts first]) ifTrue: [''] ifFalse: ['(port:', p printString, ')']! ! !EToyPeerToPeer methodsFor: 'listening' stamp: 'bf 7/1/2008 19:51'! awaitDataFor: aCommunicatorMorph Socket initializeNetwork. connectionQueue _ ConnectionQueue portNumber: self class eToyCommunicationsPorts queueLength: 6. communicatorMorph _ aCommunicatorMorph. process _ [self doAwaitData] newProcess. process priority: Processor highIOPriority. process resume. ! ! !EToyPeerToPeer methodsFor: 'listening' stamp: 'bf 7/3/2008 16:40'! listeningPort ^connectionQueue portNumber! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'bf 7/3/2008 19:24'! doReceiveOneMessage | awaitingLength i length answer header | awaitingLength _ true. answer _ WriteStream on: String new. [awaitingLength] whileTrue: [ leftOverData _ leftOverData , socket receiveData. (i _ leftOverData indexOf: $ ) > 0 ifTrue: [ awaitingLength _ false. header _ leftOverData first: i - 1. length _ header asNumber. self parseOptionalHeader: header. answer nextPutAll: (leftOverData allButFirst: i). ]. ]. leftOverData _ ''. [answer size < length] whileTrue: [ answer nextPutAll: socket receiveData. communicatorMorph commResult: {#commFlash -> true}. ]. answer _ answer contents. answer size > length ifTrue: [ leftOverData _ answer allButFirst: length. answer _ answer first: length ]. ^answer ! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'bf 7/3/2008 19:36'! parseOptionalHeader: aString "header used to be just an integer, was extended to have optional parameters (see makeOptionalHeader)" (((aString copyAfter: $() copyUpTo: $)) findTokens: $;) do: [:item | (item beginsWith: 'port:') ifTrue: [self receivedPort: (item copyAfter: $:)]]! ! !EToyPeerToPeer methodsFor: 'receiving' stamp: 'bf 7/3/2008 19:33'! receivedPort: aString (remoteSocketAddress includes: $:) ifFalse: [ remoteSocketAddress := remoteSocketAddress, ':', aString].! ! !EToyPeerToPeer class methodsFor: 'as yet unclassified' stamp: 'bf 7/1/2008 19:51'! eToyCommunicationsPorts ^ 34151 to: 34159! ! EToyPeerToPeer class removeSelector: #eToyCommunicationsPort! !EToyPeerToPeer reorganize! ('sending' doConnectForSend doSendData makeOptionalHeader sendDataCautiously: sendSomeData:to:for: sendSomeData:to:for:multiple:) ('listening' awaitDataFor: doAwaitData listeningPort stopListening) ('receiving' doReceiveData doReceiveOneMessage parseOptionalHeader: receiveDataOn:for: receivedPort:) !