Multithreading 如何在线程之间共享变量或以其他方式处理此逻辑?

Multithreading 如何在线程之间共享变量或以其他方式处理此逻辑?,multithreading,scala,sockets,concurrency,future,Multithreading,Scala,Sockets,Concurrency,Future,我正在一个服务器上运行快速约会风格的聊天会话 下面是它的工作原理: 用户请求加入会话 当20个用户请求加入会话时,将创建一个新会话 会话运行时,两个用户组在聊天中配对 聊天结束后,这两个用户返回用户池,再次配对 最后,会议结束 我想知道如何处理会话和配对 我不知道如何在线程之间传递套接字并跟踪它们 我在二进制套接字上使用JSON,并使用Slick连接到MySQL数据库 我认为我的线程体系结构是合乎逻辑的,但如果有什么不合理,请告诉我: ChatServer (main app, |

我正在一个服务器上运行快速约会风格的聊天会话

下面是它的工作原理:

  • 用户请求加入会话
  • 当20个用户请求加入会话时,将创建一个新会话
  • 会话运行时,两个用户组在聊天中配对
  • 聊天结束后,这两个用户返回用户池,再次配对
  • 最后,会议结束
  • 我想知道如何处理会话和配对

    我不知道如何在线程之间传递套接字并跟踪它们

    我在二进制套接字上使用JSON,并使用Slick连接到MySQL数据库

    我认为我的线程体系结构是合乎逻辑的,但如果有什么不合理,请告诉我:

    ChatServer (main app, 
    |           starts 1 ServerHandler thread, 
    |           starts 1 SessionWaiter thread, 
    |           then loops waiting for server-side commands)
    ├──ServerHandler (loops waiting for new clients, 
    |  |              starts a new ClientHandler thread for each client)
    |  └──ClientHandler (each thread communicates with 1 client, 
    |                    client can request to join a chat session, 
    |                    then database is updated to show the request)
    └──SessionWaiter (loops checking database every 5 seconds, 
       |              if 20 Users are requesting a session then it creates a new session in the database, 
       |              assigns those 20 Users to that SessionID, 
       |              and creates 1 SessionRunner thread to handle the session, 
       |              passing the 20 client sockets to the SessionRunner - BUT HOW?)
       └──SessionRunner (each thread handles 1 Session (20 Users), pairing Users in Chats, until the Session ends)
    
    application.conf:

    mydb = {
      driver = "com.mysql.cj.jdbc.Driver",
      url = "jdbc:mysql://localhost:3306/chatsession?serverTimezone=UTC&useSSL=false",
      user = "root",
      password = "password",
      connectionPool = disabled
    }
    
    Build.sbt:

    scalaVersion := "2.13.1"
    scalacOptions += "-deprecation"
    libraryDependencies ++= Seq(
      "com.typesafe.slick" %% "slick" % "3.3.2",
      "org.slf4j" % "slf4j-nop" % "1.7.26",
      "com.typesafe.slick" %% "slick-hikaricp" % "3.3.2",
      "mysql" % "mysql-connector-java" % "6.0.6",
      "com.typesafe.play" %% "play-json" % "2.8.0"
    )
    
    Main.scala:

    import java.net.ServerSocket
    import java.io.PrintStream
    import java.io.BufferedReader
    import java.io.InputStreamReader
    import java.io.BufferedWriter
    import java.io.OutputStreamWriter
    import java.net.Socket
    import slick.jdbc.MySQLProfile.api._
    import scala.concurrent.Future
    import scala.concurrent.blocking // needed if using "blocking { }"
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.concurrent.Await
    import scala.concurrent.duration.Duration
    import scala.collection.mutable.ArrayBuffer
    import java.util.concurrent.ConcurrentHashMap
    import scala.jdk.CollectionConverters._
    import scala.util.{Failure, Success}
    import java.util.concurrent.{Executors, ExecutorService} // for threads
    import play.api.libs.json._
    
    class ClientHandler(socket: Socket) extends Runnable {
      def run() : Unit = {
        val inputstream = new BufferedReader(new InputStreamReader(socket.getInputStream()))
        val outputstream = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
        var break = false
        var startTime = System.currentTimeMillis()
        outputstream.write("welcome") // convert to json
        outputstream.newLine()
        outputstream.flush()
        println("client welcomed")
        while(!break){
          if(inputstream.ready()){
            val input = inputstream.readLine() // blocking call
            println("client: " + input)
            val json: JsValue = Json.parse(input)
            val command = (json \ "command").validate[String].getOrElse("unknown command")
            command match {
              case "connect" =>
                val id = (json \ "id").validate[String].getOrElse("unknown command")
                println(id + " connected")
              case "joinsession" =>
                // update user row in database (set WantsToJoin=1, LastActive=CurrentTime)
                // should I store their socket number in the db or can I pass it internally somehow?
                // respond to client and tell them they are in a queue
              case "ping" =>
                // client program sends a ping every 3 seconds
                // if ping or another command is not received for 5 seconds, 
                // then the socket will be closed below
              case _ => // any other input, break and close socket
                println("breaking and closing socket")
                break = true
            }
            startTime = System.currentTimeMillis()
          } else if (System.currentTimeMillis() - startTime >= 5000) {
            break = true
          }
        }
        socket.close()
      }
    }
    
    class ServerHandler(serversocket: ServerSocket) extends Runnable {
      def run() : Unit = {
        while(true) {
          val socket = serversocket.accept // blocking call
          println("client connected")
          (new Thread(new ClientHandler(socket))).start()
        }
      }
    }
    
    // each thread of this class will manage an individual session with 10 users
    class SessionRunner() extends Runnable {
      def run() : Unit = {
        while(true) {
          // have an array of the 10 users (with each socket, userid from database, etc)
          // take over each user's socket connection 
          // how do I get the sockets?
        }
      }
    }
    
    // one thread of this class will be run in a loop
    // every 5 seconds it will check how many users are requesting a session
    // if there are 10 users requesting a session, a new SessionRunner thread will be created
    // -and passed the 10 sockets? of those 10 users so it knows which clients to contact
    // how do I keep track of those sockets and pass them?
    class SessionWaiter() extends Runnable {
      def run() : Unit = {
        while(true) {
          // time out for 5 seconds
          // do a database read
          // if there are 20 users:
          // -who are requesting a session, and
          // -who have been online within the last 30 seconds
          // then create a new thread to handle that session
          // update those user rows to show:
          //  -they are no longer requesting a session, and
          //  -that they are in a session, and update sessionid
          //  -so they can rejoin the session if they lose and regain connection before the session ends
          (new Thread(new SessionRunner())).start()
          // -(how do I pass the 10 users' client sockets to that thread??)
        }
      }
    }
    
    // TDL: server prints to console every 10 seconds:
    // # of active sessions, # of users in sessions, # of users waiting for a session
    object ChatServer extends App {
      val server = new ServerSocket(10000)
      (new Thread(new ServerHandler(server))).start()
    
      var break = false
      while(!break) {
        print(">")
        val input = scala.io.StdIn.readLine() // blocking call
        input match {
          case "quit" =>
            println("\nQUITTING")
            server.close()
            break = true
          case _ =>
            println("\nUnrecognized command:"+input+"<")
        }
      }
    }
    
    导入java.net.ServerSocket
    导入java.io.PrintStream
    导入java.io.BufferedReader
    导入java.io.InputStreamReader
    导入java.io.BufferedWriter
    导入java.io.OutputStreamWriter
    导入java.net.Socket
    导入slick.jdbc.MySQLProfile.api_
    导入scala.concurrent.Future
    导入scala.concurrent.blocking//如果使用“blocking{}”,则需要
    导入scala.concurrent.ExecutionContext.Implicits.global
    导入scala.concurrent.Await
    导入scala.concurrent.duration.duration
    导入scala.collection.mutable.ArrayBuffer
    导入java.util.concurrent.ConcurrentHashMap
    导入scala.jdk.CollectionConverters_
    导入scala.util.{失败,成功}
    为线程导入java.util.concurrent.{Executors,ExecutorService}//
    导入play.api.libs.json_
    类ClientHandler(socket:socket)扩展了Runnable{
    def run():单位={
    val inputstream=new BufferedReader(新的InputStreamReader(socket.getInputStream()))
    val outputstream=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
    变量中断=错误
    var startTime=System.currentTimeMillis()
    outputstream.write(“欢迎”)//转换为json
    outputstream.newLine()
    outputstream.flush()
    println(“欢迎客户”)
    当(!休息){
    if(inputstream.ready()){
    val input=inputstream.readLine()//阻塞调用
    println(“客户端:”+输入)
    val json:JsValue=json.parse(输入)
    val command=(json\“command”).validate[String].getOrElse(“未知命令”)
    命令匹配{
    案例“连接”=>
    val id=(json\“id”).validate[String].getOrElse(“未知命令”)
    println(id+“已连接”)
    案例“joinsession”=>
    //更新数据库中的用户行(设置WantsToJoin=1,LastActive=CurrentTime)
    //我应该在数据库中存储它们的套接字号,还是可以通过某种方式在内部传递?
    //响应客户机并告诉他们他们正在排队
    案例“ping”=>
    //客户端程序每3秒发送一次ping
    //如果在5秒钟内未收到ping或其他命令,
    //然后插座将在下面关闭
    case=>//任何其他输入,断开并关闭套接字
    println(“断开和闭合插座”)
    中断=真
    }
    startTime=System.currentTimeMillis()
    }else if(System.currentTimeMillis()-startTime>=5000){
    中断=真
    }
    }
    socket.close()
    }
    }
    类ServerHandler(serversocket:serversocket)扩展了Runnable{
    def run():单位={
    while(true){
    val socket=serversocket.accept//阻止调用
    println(“客户端连接”)
    (新线程(新ClientHandler(套接字))).start()
    }
    }
    }
    //此类的每个线程将管理一个具有10个用户的单独会话
    类SessionRunner()扩展了Runnable{
    def run():单位={
    while(true){
    //拥有10个用户的数组(每个套接字、数据库中的用户ID等)
    //接管每个用户的套接字连接
    //我怎样才能拿到插座?
    }
    }
    }
    //此类的一个线程将在循环中运行
    //每隔5秒,它将检查有多少用户请求会话
    //如果有10个用户请求会话,将创建一个新的SessionRunner线程
    //-并通过了10个插座?这10个用户中的一个,以便它知道要联系哪些客户
    //我如何跟踪这些插座并传递它们?
    类SessionWater()扩展了Runnable{
    def run():单位={
    while(true){
    //超时5秒
    //进行数据库读取
    //如果有20个用户:
    //-谁在请求会话,以及
    //-谁在过去30秒内在线
    //然后创建一个新线程来处理该会话
    //更新这些用户行以显示:
    //-他们不再请求会话,并且
    //-他们正在会话中,并更新会话ID
    //-因此,如果在会话结束前失去并重新连接,他们可以重新加入会话
    (新线程(新SessionRunner()).start()
    //-(如何将10个用户的客户端套接字传递给该线程??)
    }
    }
    }
    //TDL:服务器每10秒向控制台打印一次:
    //#个活动会话,#个会话中的用户,#个等待会话的用户
    对象服务器扩展应用程序{
    val服务器=新服务器套接字(10000)
    (新线程(新服务器处理程序(服务器))).start()
    变量中断=错误
    当(!休息){
    打印(“>”)
    val input=scala.io.StdIn.readLine()//阻塞调用
    输入匹配{
    案例“退出”=>
    println(“\n匹配”)
    server.close()
    中断=真
    案例=>
    
    println(“\n无法识别的命令:“+input+”免责声明:除此之外,您同时使用非常低级的并发原语(线程、可运行),而您有一系列非常强大的库和解决方案来完成这类任务,特别是在Scala中(Futures、Akka、Akka streams、cats effect、fs2等),我会尽力帮助你,我会用最简单的方法
    type MyConnectionType = Socket
    object RequestingConnectionsHolder {
      @volatile var requestingConnections: List[MyConnectionType] = List()
    }
    
    class SessionWaiter() extends Runnable {
      def run() : Unit = {
        while(true) {
          val collection = RequestingConnectionsHolder.requestingConnections
          val sessionSize = 10
          RequestingConnectionsHolder.synchronized {
            if(collection.size >= sessionSize) {
              val (firsts, lasts) = (collection.take(10), collection.drop(10))
              (new Thread(new SessionRunner(firsts))).start()
              RequestingConnectionsHolder.requestingConnections = lasts
            }
          }
        }
      }
    }
    
    case "joinsession" => RequestingConnectionsHolder.synchronized { RequestingConnectionsHolder.requestingConnections += socket }
    
    class SessionRunner(connections: List[MyConnectionType]) extends Runnable {
      def run() : Unit = {
        while(sessionIsAlive) { }
        RequestingConnectionsHolder.synchronized {
          RequestingConnectionsHolder.requestingConnections += connections
        }
      }
    }