Scalaによるソケット通信オセロプログラミング
Scala2.12.7と、scalaFXを使って通信型オセロプログラミングを練習で作っています。
ソケット通信でサーバーが先行(黒)、クライアント側が後攻(白)を担当します。
Scala自体が初心者なため、あまりScalaの特性を生かした実装ができていないことは、承知しております。Scala自体の書き方・設計に関するご指摘ももちろんいただきたいとも思いますが、今回の件はもっと基礎的なソケット通信や、同期処理などの話が自分の中の理解と異なるかと思い、思った動作をしていません。
処理の流れとして、起動後、画面描画処理をしたのち、サーバー側がサーバーソケットを作成、その状態で別プロセスで起動した同一プログラムを起動してクライアントとして、接続します。
ここまでは想定通り動作しています。
その後、サーバー側が必ず先行で、自分の盤面のどこかをクリックするとそこに黒丸を描き、通信した相手の盤面にも即時に黒丸を描きたいですが描画されず、その後白側がクリックした時に、白側の盤面に一つ前の黒が描画されます。つまり、ワンテンポ遅れてお互いの盤面に描画される状態です。
以下に、該当のソースコードを掲載します。
OseloController.scala
package gui
import java.net.Socket
import scalafx.Includes._
import scalafx.scene.input.MouseEvent
import scalafx.scene.paint.Color._
import scalafx.scene.layout.GridPane
import scalafxml.core.macros.sfxml
import javafx.scene.shape.Circle
import scalafx.scene.paint.Color
import network._
import scalafx.scene.control.Button
@sfxml
class OseloController(val board: GridPane, val connecteButton: Button, val serverButton: Button){
val server: Server = new Server(8000, board)
var client: Client = _
var flag: Boolean = _ //白黒の順番判定trueが黒、falseが白
var posX:Int = _
var posY:Int = _
serverButton.onMouseClicked = (_: MouseEvent) =>{
server.createServer
flag = true
}
connecteButton.onMouseClicked = (_: MouseEvent) =>{
val sc: Socket = new Socket("127.0.0.1", 8000)
client = new Client(sc, board)
flag = false
client.createClient()
client.receiveMsg()
}
board.onMouseClicked = (e:MouseEvent) => {
posX = (e.x / 53).asInstanceOf[Int]
posY = (e.y / 53).asInstanceOf[Int]
if(flag) {
server.sendMsg(posX.toString + "," + posY.toString)
server.receiveMsg()
}else{
client.sendMsg(posX.toString + "," + posY.toString)
client.receiveMsg()
}
}
}
Server.scala
package network
import java.io._
import java.net._
import javafx.scene.shape.Circle
import scalafx.scene.layout.GridPane
import scalafx.scene.paint.Color
import scalafx.scene.paint.Color._
class Server(val port: Int, val board: GridPane) extends Thread{
var ss: ServerSocket = _
var sc: Socket = _
var br: BufferedReader = _
var pw: PrintWriter = _
var clientMsg: String = _
var arr: Array[String] = _
def createServer: Unit ={
try{
ss = new ServerSocket(port)
println("Waiting For・・・")
sc = ss.accept()
println("Welcome!!")
//以下、メッセージやり取りのための変数初期化
try {
br = new BufferedReader(new InputStreamReader(sc.getInputStream))
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sc.getOutputStream)))
}catch {
case e: Exception => e.printStackTrace
}
} catch {
case e:Exception => e.printStackTrace
ss.close()
}
}
def createCircle(posX: Int, posY: Int, color: Color): Unit ={
println("Server:" + posX + "," + posY)
board.add(new Circle(0,0,27,color), posX , posY)
}
def sendMsg(msg: String): Unit = {
arr = msg.split(",")
pw.println(msg)
pw.flush()
createCircle(arr(0).toInt, arr(1).toInt, Black)
}
def receiveMsg(): Unit ={
while((clientMsg = br.readLine()) == null){Thread.sleep(100)}
println(clientMsg)
arr = clientMsg.split(",")
createCircle(arr(0).toInt, arr(1).toInt, White)
}
}
Client.scala
package network
import java.io._
import java.net._
import java.util._
import javafx.scene.shape.Circle
import scalafx.scene.paint.Color._
import scalafx.scene.layout.GridPane
import scalafx.scene.paint.Color
class Client(val sc: Socket, val board: GridPane){
var br: BufferedReader = _
var pw: PrintWriter = _
var serverMsg: String = _
var arr: Array[String] = _
def createClient(): Unit ={
try {
br = new BufferedReader(new InputStreamReader(sc.getInputStream))
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sc.getOutputStream)))
}catch {
case e: Exception => e.printStackTrace
}
}
def createCircle(posX: Int, posY: Int, color: Color): Unit ={
println("Client:" + posX + "," + posY)
board.add(new Circle(0,0,27,color), posX , posY)
}
def sendMsg(msg: String): Unit = {
arr = msg.split(",")
pw.println(msg)
pw.flush()
createCircle(arr(0).toInt, arr(1).toInt, White)
}
def receiveMsg(): Unit ={
while((serverMsg = br.readLine()) == null){Thread.sleep(100)}
println(serverMsg)
arr = serverMsg.split(",")
createCircle(arr(0).toInt, arr(1).toInt, Black)
}
}
個人的には、同期・非同期処理もしくは、マルチスレッドなどの話が絡んでくると考えています。
ただ、調べた限りでは、こういったケースでは、非同期処理を使用するのは、サーバーが複数のクライアントを同時に捌くケースかなとも思いました。
createCircle
関数をどこに挟んでも、関数内部に入りprintln
関数は走るのに、実際にboard
にadd
する部分は必ず相手からの応答があったタイミングでしか走りません。
最良の実装をご教授いただければと思います。
よろしくお願いします。