2010-01-14 17 views
6

Tengo bastantes problemas con actores que contienen operaciones de larga ejecución, en mi caso conexiones de socket persistentes. Aquí hay un código de prueba que funciona bien si creo menos de cuatro instancias de Servidor, pero si creo más instancias, siempre termino con solo tres o a veces cuatro conexiones de socket simultáneas, porque las demás se desconectan. Me pregunto por qué es así y si hay algo obviamente mal con mi código.scala actors: long running io operations

package test 

import actors.Actor 
import actors.Actor._ 
import java.io.{PrintStream, DataOutputStream, DataInputStream} 
import java.net.{Socket, InetAddress} 
import java.text.{SimpleDateFormat} 
import java.util.{Calendar} 

case class SInput(input: String) 
case class SOutput(output: String) 
case class SClose 
case class SRepeat 

import scala.xml._ 

class Config(xml: Node) { 
    var nick: String = (xml \ "nick").text 
    var realName: String = (xml \ "realName").text 
    var server: String = (xml \ "ip").text 
    var port: Int = (xml \ "port").text.toInt 
    var identPass: String = (xml \ "identPass").text 
    var joinChannels: List[String] = List.fromString((xml \ "join").text.trim, ' ') 
} 

object ServerStarter { 
    def main(args: Array[String]): Unit = { 
    var servers = List[Server]() 

    val a = actor { 
     loop { 
     receive { 
      case config: Config => 
      actor { 
       val server = new Server(config) 
       servers = server :: servers 
       server.start 
      } 
     } 
     } 
    } 

    val xml = XML.loadFile("config.xml") 
    (xml \ "server").elements.foreach(config => a ! new Config(config)) 
    } 
} 


class Server(config: Config) extends Actor { 
    private var auth = false 
    private val socket = new Socket(InetAddress.getByName(config.server), config.port) 
    private val out = new PrintStream(new DataOutputStream(socket.getOutputStream())) 
    private val in = new DataInputStream(socket.getInputStream()) 

    def act = { 
    val _self = this 
    _self ! SRepeat 

    while (true) { 
     receive { 
     case SRepeat => 
      try { 
      val input = in.readLine 
      if (input != null) { 
       actor {_self ! SInput(input)} 
      } else { 
       actor {_self ! SClose} 
      } 
      } catch { 
      case e: Exception => 
       println(e) 
       actor {_self ! SClose} 
      } 

     case SClose => 
      println(getDate + " closing: " + config.server + " mail: " + mailboxSize) 
      try { 
      socket.close 
      in.close 
      out.close 
      } catch { 
      case e: Exception => 
       println(e) 
      } 

     case SInput(input: String) => 
      println(getDate + " " + config.server + " IN => " + input + " mail: " + mailboxSize) 
      actor {onServerInput(_self, input)} 
      _self ! SRepeat 

     case SOutput(output: String) => 
      println(getDate + " " + config.server + " OUT => " + output + " mail: " + mailboxSize) 
      actor { 
      out.println(output) 
      out.flush() 
      } 

     case x => 
      println("unmatched: " + x + " mail: " + mailboxSize) 
     } 
    } 
    } 

    private def getDate = { 
    new SimpleDateFormat("hh:mm:ss").format(Calendar.getInstance().getTime()); 
    } 

    def onServerInput(a: Actor, input: String) = { 
    if (!auth) { 
     authenticate(a) 
    } 
    else if (input.contains("MOTD")) { 
     identify(a) 
     join(a) 
    } 
    else if (input.contains("PING")) { 
     pong(a, input) 
    } else { 
    } 
    } 

    def authenticate(a: Actor) = { 
    a ! SOutput("NICK " + config.nick) 
    a ! SOutput("USER " + config.nick + " 0 0 : " + config.realName) 
    auth = true 
    } 

    def pong(a: Actor, input: String) = { 
    a ! SOutput("PONG " + input.split(":").last) 
    } 

    def identify(a: Actor) = { 
    if (config.identPass != "") { 
     a ! SOutput("nickserv :identify " + config.nick + " " + config.identPass) 
    } 
    } 

    def join(a: Actor) = { 
    config.joinChannels.foreach(channel => a ! SOutput("JOIN " + channel)) 
    } 
} 

btw. Estoy usando scala 2.7.6 final.

+0

Hey Max, mucho tiempo no se ve! Me alegro de ver que estás probando Scala. –

Respuesta

6

Aquí hay cosas extrañas. Por ejemplo:

actor { 
    val server = new Server(config) 
    servers = server :: servers 
    server.start 
} 

O también:

actor {_self ! SClose} 

El método actor es una fábrica de Actor. En el primer caso, por ejemplo, está creando un actor que creará otro actor (porque el Servidor es un Actor) y lo iniciará.

Déjeme repetir que: todo entre actor { y } es un actor. Dentro de ese actor, estás haciendo new Server, que crea otro actor. Y eso está dentro de un receive, que, por supuesto, es parte de un actor. Entonces, dentro de un actor, estás creando un actor que creará un actor.

Y en el segundo ejemplo, estás creando un actor solo para enviarte un mensaje. Eso no tiene sentido para mí, pero no tengo mucha experiencia con actores.

+0

Bueno, tengo la idea de envolver los mensajes que tengo la intención de enviar a los actores dentro de un actor desde aquí: http://stackoverflow.com/questions/1549251/scala-actors-worst-practices (segunda respuesta, segundo punto) – maxmc

+0

si i eliminar el actor mencionado {} el problema permanece. más de 2 instancias de Servidor simultáneas no funcionan de manera confiable. – maxmc

+2

El uso de 'Actor.actor' debe hacerse si no estás dentro de un actor. Las situaciones que menciono ocurren dentro de los actores. –