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