Scala聊天应用程序,阻止问题

时间:2016-09-06 17:23:06

标签: java multithreading scala sockets blocking

我正在Scala中编写聊天应用程序,问题在于客户端,客户端在将数据发送到echo服务器之前从StdIn(阻止)读取,因此如果连接了多个客户端,则他们不会收到来自服务器的数据直到从StdIn读取完成。我认为本地IO,即从StdIn读取和读取/写入套接字应该在不同的线程上,但我想不出办法,下面是客户端单例代码:

import java.net._
import scala.io._
import java.io._
import java.security._

object Client {

  var msgAcc = ""

  def main(args: Array[String]): Unit = {
    val conn = new ClientConnection(InetAddress.getByName(args(0)), args(1).toInt)
    val server = conn.connect()
    println("Enter a username")
    val user = new User(StdIn.readLine())
    println("Welcome to the chat " + user.username)
    sys.addShutdownHook(this.shutdown(conn, server))
    while (true) {
    val txMsg = StdIn.readLine()//should handle with another thread?
    if (txMsg != null) {
      conn.sendMsg(server, user, txMsg)
      val rxMsg = conn.getMsg(server)
      val parser = new JsonParser(rxMsg)
      val formattedMsg = parser.formatMsg(parser.toJson()) 
      println(formattedMsg)
      msgAcc = msgAcc + formattedMsg + "\n"
      }
    }
  }

  def shutdown(conn: ClientConnection, server: Socket): Unit = {
    conn.close(server)
    val fileWriter = new BufferedWriter(new FileWriter(new File("history.txt"), true))
    fileWriter.write(msgAcc) 
    fileWriter.close()
    println("Leaving chat, thanks for using")
  }

}
下面的

是ClientConnection类:

import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.SocketFactory
import java.net.Socket
import java.net.InetAddress
import java.net.InetSocketAddress
import java.security._
import java.io._
import scala.io._
import java.util.GregorianCalendar
import java.util.Calendar
import java.util.Date
import com.sun.net.ssl.internal.ssl.Provider
import scala.util.parsing.json._

class ClientConnection(host: InetAddress, port: Int) {

  def connect(): Socket = {
    Security.addProvider(new Provider())
    val sslFactory = SSLSocketFactory.getDefault()
    val sslSocket = sslFactory.createSocket(host, port).asInstanceOf[SSLSocket]
    sslSocket
   }

  def getMsg(server: Socket): String = new BufferedSource(server.getInputStream()).getLines().next()

  def sendMsg(server: Socket, user: User, msg: String): Unit = {
    val out = new PrintStream(server.getOutputStream())
    out.println(this.toMinifiedJson(user.username, msg))
    out.flush()
  }  

  private def toMinifiedJson(user: String, msg: String): String = {
    s"""{"time":"${this.getTime()}","username":"$user","msg":"$msg"}"""
  }

  private def getTime(): String = {
    val cal = Calendar.getInstance()
    cal.setTime(new Date())
    "(" + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND) + ")"
  }

  def close(server: Socket): Unit = server.close()
}

这是使用Thread从标准输入读取的客户端单例:

import java.net._
import scala.io._
import java.io._
import java.security._
import java.util.NoSuchElementException

object Client {

  var msgAcc = ""

  def main(args: Array[String]): Unit = {
    val conn = new ClientConnection(InetAddress.getByName(args(0)), args(1).toInt)
    val server = conn.connect()
    println("Enter a username")
    val user = new User(StdIn.readLine())
    println("Welcome to the chat " + user.username)
    sys.addShutdownHook(this.shutdown(conn, server))
    new Thread(conn).start()
    while (true) {
    val tx = conn.tx
    if (tx != null) {
      conn.sendMsg(server, user, tx)
      val rxMsg = conn.getMsg(server)
      val parser = new JsonParser(rxMsg)
      val formattedMsg = parser.formatMsg(parser.toJson()) 
      println(formattedMsg)
      msgAcc = msgAcc + formattedMsg + "\n" 
      }
    }
  }

  def shutdown(conn: ClientConnection, server: Socket): Unit = {
    conn.close(server)
    val fileWriter = new BufferedWriter(new FileWriter(new File("history.txt"), true))
    fileWriter.write(msgAcc) 
    fileWriter.close()

这是扩展Runnable的ClientConnection类:

import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.SocketFactory
import java.net.Socket
import java.net.InetAddress
import java.net.InetSocketAddress
import java.security._
import java.io._
import scala.io._
import java.util.GregorianCalendar
import java.util.Calendar
import java.util.Date
import com.sun.net.ssl.internal.ssl.Provider
import scala.util.parsing.json._

class ClientConnection(host: InetAddress, port: Int) extends Runnable {

  var tx: String = null

  override def run(): Unit = {
     tx = StdIn.readLine()
  }

  def connect(): Socket = {
    Security.addProvider(new Provider())
    val sslFactory = SSLSocketFactory.getDefault()
    val sslSocket = sslFactory.createSocket(host, port).asInstanceOf[SSLSocket]
    sslSocket
   }

  def getMsg(server: Socket): String = new BufferedSource(server.getInputStream()).getLines().next()

  def sendMsg(server: Socket, user: User, msg: String): Unit = {
    val out = new PrintStream(server.getOutputStream())
    out.println(this.toMinifiedJson(user.username, msg))
    out.flush()
  }  

  private def toMinifiedJson(user: String, msg: String): String = {
    s"""{"time":"${this.getTime()}","username":"$user","msg":"$msg"}"""
  }

  private def getTime(): String = {
    val cal = Calendar.getInstance()
    cal.setTime(new Date())
    "(" + cal.get(Calendar.HOUR_OF_DAY) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND) + ")"
  }

  def close(server: Socket): Unit = server.close()
}

1 个答案:

答案 0 :(得分:0)

所以你已经成功地将输入的读取移动到Runnable,因此它将在另一个Thread上运行,但现在当我们查看主线程上的逻辑时,我们看到了如果消息不是null,它将始终发送消息。这有几个问题:

  • 您没有在run方法中循环,因此您只会收到一条消息,然后您的run方法终止 - 您希望将其包装在{{1或while(true)所以你不断更新它。
  • 只有在向服务器发送消息后,您仍然会从服务器打印出消息。您应该将其解耦,以便完全在另一个线程上向服务器发送消息。

根据这一点,可能会解决这个问题:

while(<some boolean indicating you're not done>)

然后,在你的主线程中,只需处理获取消息并将其打印出来:

//This is your new run method in your Runnable
override def run(): Unit = {
    while(true) {
        tx = StdIn.readLine()
        conn.sendMsg(server, user, tx) //Note you'll need to pass those references in somehow
    }
}`

这样,这两种行为就在不同的线程上。