两天前我开始学习scala actor框架。为了使我的想法具体化,我决定实现一个基于TCP的echo服务器,它可以处理多个同时连接。
以下是echo服务器的代码(不包括错误处理):
class EchoServer extends Actor {
private var connections = 0
def act() {
val serverSocket = new ServerSocket(6789)
val echoServer = self
actor { while (true) echoServer ! ("Connected", serverSocket.accept) }
while (true) {
receive {
case ("Connected", connectionSocket: Socket) =>
connections += 1
(new ConnectionHandler(this, connectionSocket)).start
case "Disconnected" =>
connections -= 1
}
}
}
}
基本上,服务器是处理“已连接”和“已断开连接”消息的Actor。它委托监听匿名actor的连接,该匿名actor在 serverSocket 上调用 accept()方法(阻塞操作)。当连接到达时,它通过“已连接”消息通知服务器并将其传递给套接字以用于与新连接的客户端进行通信。 ConnectionHandler 类的一个实例处理与客户端的实际通信。
以下是连接处理程序的代码(包括一些错误处理):
class ConnectionHandler(server: EchoServer, connectionSocket: Socket)
extends Actor {
def act() {
for (input <- getInputStream; output <- getOutputStream) {
val handler = self
actor {
var continue = true
while (continue) {
try {
val req = input.readLine
if (req != null) handler ! ("Request", req)
else continue = false
} catch {
case e: IOException => continue = false
}
}
handler ! "Disconnected"
}
var connected = true
while (connected) {
receive {
case ("Request", req: String) =>
try {
output.writeBytes(req + "\n")
} catch {
case e: IOException => connected = false
}
case "Disconnected" =>
connected = false
}
}
}
close()
server ! "Disconnected"
}
// code for getInputStream(), getOutputStream() and close() methods
}
连接处理程序使用匿名actor,它通过调用套接字输入流上的 readLine()方法(阻塞操作)来等待发送到套接字的请求。当收到请求时,向处理程序发送“请求”消息,然后简单地将请求回送给客户端。如果处理程序或匿名actor遇到底层套接字问题,则套接字关闭,并向echo服务器发送一条“Disconnect”消息,指示客户端已与服务器断开连接。
因此,我可以启动echo服务器并让它等待连接。然后我可以打开一个新终端并通过telnet连接到服务器。我可以发送请求并正确响应。现在,如果我打开另一个终端并连接到服务器,则服务器注册连接但无法启动此新连接的连接处理程序。当我通过任何现有连接发送消息时,我没有得到立即响应。这是有趣的部分。当我终止除现有客户端连接之外的所有客户端连接并使客户端X保持打开状态时,将返回通过客户端X发送的请求的所有响应。我做了一些测试并得出结论,即使我在创建连接处理程序时调用 start()方法,也没有在后续客户端连接上调用 act()方法
我想我在连接处理程序中错误地处理了阻塞操作。由于先前的连接是由连接处理程序处理的,该连接处理程序阻塞了等待请求的匿名actor,我认为这个被阻止的actor阻止其他actor(连接处理程序)启动。
使用scala actor时如何处理阻塞操作?
非常感谢任何帮助。
答案 0 :(得分:4)
来自the scaladoc for scala.actors.Actor:
注意:在调用Actor特征或其伴随对象(例如
receive
)提供的方法之外的线程阻塞方法时必须小心。阻止actor内部的底层线程可能导致其他actor的饥饿。这也适用于在调用receive
/react
之间长时间占用线程的演员。如果actor使用阻塞操作(例如,阻止I / O的方法),则有几个选项:
- 可以将运行时系统配置为使用更大的线程池大小(例如,通过设置
actors.corePoolSize
JVM属性)。- 可以重写
scheduler
特征的Actor
方法以返回ResizableThreadPoolScheduler
,其调整其线程池的大小以避免由调用任意阻塞方法的actor引起的饥饿。actors.enableForkJoin
JVM属性可以设置为false,在这种情况下,默认情况下会使用ResizableThreadPoolScheduler
来执行actor。