2016-09-06 49 views
0

我正在Scala中編寫聊天應用程序,問題與客戶端有關,客戶端在將數據發送到回顯服務器之前從StdIn(哪些塊)讀取數據,因此如果連接了多個客戶端那麼在StdIn完成讀取之前,他們不會從服務器接收數據。我在想,本地IO,即從標準輸入和讀取讀取/寫入插座應是在單獨的線程,但我不能想辦法做到這一點,下面是客戶端單碼:Scala聊天應用程序阻塞問題

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() 
} 

這是使用一個線程從標準輸入到讀出的客戶機單:

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() 

這是可運行延伸的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() 
} 
+0

那麼你到目前爲止嘗試過什麼?你知道Java中的多線程嗎? – childofsoong

+0

我試過讓ClientConnection類擴展Runnable(在scala中擴展而不是實現),並讓run()方法執行StdIn.readLine()並將結果存儲在一個類變量中,然後通過客戶端訪問它單身人士,但是這導致了從客戶端發送到服務器並返回的數據的循環。我對多線程有一點了解,每次新客戶端連接時服務器都會使用一個新線程,以便偵聽新連接(哪些塊)和套接字IO的套接字是分開的。 – user2069328

+0

我想我知道你做錯了什麼,但是你應該把代碼發佈在你所做的事情上,所以我可以肯定。如果我理解你,你基本上只是更新了StdIn輸入在另一個線程中的內容,但是你在主線程中多次發送它,無論它是否被更改? – childofsoong

回答

0

所以,你已經成功地移動輸入到Runnable的閱讀,所以它會在另一個Thread運行,但現在當我們看在你的主線程的邏輯,我們可以看到,它總是會發該消息如果不是null。有幾個問題是:

  • 你不是在run方法循環,​​所以你只會收到一個消息,然後你run方法終止 - 你想在一個包裝這個while(true)或​​讓你不斷更新它。
  • 只有在向服務器發送消息之後,您仍然在打印出來自服務器的消息。你應該解耦,這樣發送消息到服務器完全在另一個線程上完成。

東西沿着這一線路可能會解決這個問題:

//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 
    } 
}` 

然後,在你的主線程,只是處理得到的消息,並打印出來:

new Thread(conn).start() 
while (true) { 
    //note the lack of sending messages in here 
    val rxMsg = conn.getMsg(server) 
    val parser = new JsonParser(rxMsg) 
    val formattedMsg = parser.formatMsg(parser.toJson()) 
    println(formattedMsg) 
    msgAcc = msgAcc + formattedMsg + "\n" 
} 

這樣,這兩種行爲在不同的線程上。

+0

感謝解決了這個問題,現在服務器迴應數據的方式有問題。它保存連接的客戶端列表並用該列表實例化新的線程。所以在第一次連接時,Thread只有一個客戶端的引用,在第二次連接時,一個新的Thread對兩個客戶端都有引用,所以第二個客戶端可以向自己和另一個客戶端發送數據,但第一個客戶端只能發送數據本身作爲第一個線程有一個參考列表,只有第一個客戶端。 – user2069328

+0

@ user2069328聽起來好像應該在一個單獨的問題 – childofsoong

+0

好吧,歡呼你的幫助 – user2069328