6

我是 Scala 的新手,所以这个问题可能很简单,尽管我花了一些时间试图解决它。我有一个简单的 Scala TCP 服务器(没有演员,单线程):

import java.io._
import java.net._

object Application {
  def readSocket(socket: Socket): String = {
    val bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream))
    var request = ""
    var line = ""
    do {
      line = bufferedReader.readLine()
      if (line == null) {
        println("Stream terminated")
        return request
      }
      request += line + "\n"
    } while (line != "")
    request
  }

  def writeSocket(socket: Socket, string: String) {
    val out: PrintWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream))
    out.println(string)
    out.flush()
  }

  def main(args: Array[String]) {
    val port = 8000
    val serverSocket = new ServerSocket(port)
    while (true) {
      val socket = serverSocket.accept()
      readSocket(socket)
      writeSocket(socket, "HTTP/1.1 200 OK\r\n\r\nOK")
      socket.close()
    }
  }
}

服务器侦听localhost:8000传入的请求并OK在正文中发送带有单个单词的 HTTP 响应。然后我像这样运行 Apache Benchmark:

ab -c 1000 -n 10000 http://localhost:8000/

这是第一次很好地工作。我第二次启动ab它时挂起,在中产生以下输出netstat -a | grep 8000

....
tcp        0      0 localhost.localdo:43709 localhost.localdom:8000 FIN_WAIT2  
tcp        0      0 localhost.localdo:43711 localhost.localdom:8000 FIN_WAIT2  
tcp        0      0 localhost.localdo:43717 localhost.localdom:8000 FIN_WAIT2  
tcp        0      0 localhost.localdo:43777 localhost.localdom:8000 FIN_WAIT2  
tcp        0      0 localhost.localdo:43722 localhost.localdom:8000 FIN_WAIT2  
tcp        0      0 localhost.localdo:43725 localhost.localdom:8000 FIN_WAIT2  
tcp6       0      0 [::]:8000               [::]:*                  LISTEN     
tcp6      83      0 localhost.localdom:8000 localhost.localdo:43724 CLOSE_WAIT 
tcp6      83      0 localhost.localdom:8000 localhost.localdo:43786 CLOSE_WAIT 
tcp6       1      0 localhost.localdom:8000 localhost.localdo:43679 CLOSE_WAIT 
tcp6      83      0 localhost.localdom:8000 localhost.localdo:43735 CLOSE_WAIT 
tcp6      83      0 localhost.localdom:8000 localhost.localdo:43757 CLOSE_WAIT 
tcp6      83      0 localhost.localdom:8000 localhost.localdo:43754 CLOSE_WAIT 
tcp6      83      0 localhost.localdom:8000 localhost.localdo:43723 CLOSE_WAIT
....

由于服务器不再处理请求。更多细节:ab具有相同参数的相同脚本可以在同一台机器上顺利测试简单的 Node.js 服务器。所以这个问题与我设置为可重用的许多打开的 TCP 连接无关

sudo sysctl -w net.ipv4.tcp_tw_recycle=1
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

谁能给我一个关于我缺少什么的线索?

编辑:流处理的终止已添加到上面的代码中:

    if (line == null) {
      println("Stream terminated")
      return request
    }
4

2 回答 2

2

我正在为那些有一天会偶然发现同一问题的人发布我自己问题的(部分)答案。首先,问题的本质不在于源代码,而在于限制了众多连接的系统本身。问题是socket传递给readSocket函数在某些情况下似乎已损坏,即它无法读取并且在第一次调用bufferedReader.readLine()时返回null或无限期挂起。以下两个步骤使代码在某些机器上工作:

  1. 增加与套接字的并发连接数

    sysctl -w net.core.somaxconn=65535
    
  2. 向构造函数提供第二个参数ServerSocket,它将显式设置连接队列的长度:

    val maxQueue = 50000
    val serverSocket = new ServerSocket(port, maxQueue)
    

上述步骤解决了 EC2 m1.large 实例上的问题,但是我的本地机器上仍然出现问题。更好的方法是使用Akka来处理这类东西:

import akka.actor._
import java.net.InetSocketAddress
import akka.util.ByteString

class TCPServer(port: Int) extends Actor {

  override def preStart {
    IOManager(context.system).listen(new InetSocketAddress(port))
  }

  def receive = {
    case IO.NewClient(server) =>
      server.accept()
    case IO.Read(rHandle, bytes) => {
      val byteString = ByteString("HTTP/1.1 200 OK\r\n\r\nOK")
      rHandle.asSocket.write(byteString)
      rHandle.close()
    }
  }
}

object Application {
  def main(args: Array[String]) {
    val port = 8000
    ActorSystem().actorOf(Props(new TCPServer(port)))
  }
}
于 2012-05-30T06:07:38.130 回答
0

首先,我建议在没有ab. 您可以执行以下操作:

echo "I'm\nHappy\n" | nc -vv localhost 8000

其次,我建议处理流尾。这是BufferedReader.readLine()返回 null 的地方。上面的代码只检查空字符串。解决这个问题后,我会再试一次。然后用 ab 测试,一切看起来都很好。让我们知道问题是否仍然存在。

于 2012-05-26T23:31:36.533 回答