2016-09-06 4 views
0

Я пишу чат-приложение в Scala, проблема связана с клиентами, клиент читает из StdIn (который блокирует) перед отправкой данных на эхо-сервер, поэтому, если подключено несколько клиентов то они не получают данные с сервера до тех пор, пока чтение из StdIn не завершится. Я имею в виду, что местный IO, то есть чтение из STDIN и чтения/записи в сокет должен быть в отдельных потоках, но я не могу придумать способ сделать это, ниже код клиента синглтон:Приложение для чата 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 расширения Runnable:

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() и имеет результат, хранящийся в переменной класса, которую я тогда получил от Клиента singleton, однако это вызвало циклизацию данных, отправляемых от клиента на сервер и обратно. Я немного разбираюсь в многопоточности, сервер использует новый поток каждый раз, когда новый клиент подключается, так что сокет, прослушивающий новые подключения (блокирует) и сокет IO, является отдельным. – user2069328

+0

Я думаю, что знаю, что вы сделали неправильно, из этого описания, но вы должны опубликовать код, где вы это сделали, чтобы я был уверен. Если я понимаю вас, вы в основном просто обновили, что вход StdIn был в другом потоке, но вы неоднократно отправляли его в свой основной поток, независимо от того, было ли оно изменено? – childofsoong

ответ

0

Итак, вы успешно переместили чтение ввода в Runnable, поэтому он будет работать на другом Thread, но теперь, когда мы смотрим на логику на основном потоке, мы видим, что он всегда будет посылать сообщение, если это не null. Есть несколько вопросов к этому:

  • Вы не зацикливание в методе run, так что вы только собираетесь получить одно сообщение, а затем ваш метод run заканчивается - вы хотите, чтобы обернуть это в 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 
    } 
}` 

Затем, в основном потоке, просто иметь дело с получением сообщений и их печати:

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

Смежные вопросы