2012-05-23 13 views
5

Jestem nowy w Scali, więc pytanie może być dość proste, chociaż poświęciłem trochę czasu na rozwiązanie problemu. Mam prosty serwer Scala TCP (nie aktorów, single thread):Problemy z gniazdami w prostym serwerze 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() 
    } 
    } 
} 

Serwer nasłuchuje na localhost:8000 żądań przychodzących i wysyła odpowiedź HTTP z pojedynczego OK słowo w organizmie. Następnie uruchamiam Apache Benchmark w ten sposób:

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

który działa ładnie po raz pierwszy. Za drugim razem zacznę ab zawiesza produkcję następujący wynik w 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 
.... 

Ponieważ że nie więcej żądań są obsługiwane przez serwer. Jeszcze jeden szczegół: ten sam skrypt ab z tymi samymi parametrami działa sprawnie testując prosty serwer Node.js na tym samym komputerze. Więc ten problem nie jest związany z liczbą otwartych połączeń TCP, który postawiłem jako wielokrotnego użytku z

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

Czy ktoś może mi dać wskazówkę na temat tego, co mi brakuje?

Edit: Zakończenie postępowania strumień został dodany do powyższego kodu:

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

CLOSE_WAIT oznacza, że ​​TCP czeka dla tej aplikacji, aby zamknąć swoje gniazdo. Innym problemem jest to, że nie wysyłasz poprawnych terminatorów linii dla HTTP. Są one określone jako \ r \ n, a nie \ n. – EJP

+0

Zaktualizowałem kod do '\ r \ n' chociaż' curl' i 'ab' oba wydają się działać dobrze z' \ n'. Co do CLOSE_WAIT - to nie wydaje się być źródłem problemu. Dziękuję za komentowanie. – nab

+0

CLOSE_WAIT jest symptomem problemu, że aplikacja nie zamknęła gniazda. – EJP

Odpowiedz

2

Zamieszczam (częściową) odpowiedź na moje własne pytanie dla tych, którzy pewnego dnia natkną się na ten sam problem. Po pierwsze, natura problemu leży nie w kodzie źródłowym, ale w samym systemie, który ogranicza liczbę połączeń. Problem polega na tym, że funkcja socket przekazana do funkcji readSocket wydaje się uszkodzona w pewnych warunkach, tj. Nie może być odczytana i zwraca się po raz pierwszy lub zawiesza się w nieskończoność. Poniższe dwa kroki uczynić kod działa na niektórych maszynach:

  1. Zwiększenie liczby jednoczesnych połączeń do gniazdka z

    sysctl -w net.core.somaxconn=65535 
    
  2. Podaj drugi parametr do ServerSocket konstruktora, który będzie wyraźnie określoną długość kolejki połączenie:

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

Powyższe kroki rozwiązują problem występujący w instancjach EC2 m1.large, jednak nadal występują problemy na moim komputerze lokalnym.Lepszym rozwiązaniem byłoby wykorzystanie Akka dla rzeczy tego rodzaju:

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))) 
    } 
} 
0

pierwsze, sugeruję próbują to bez ab. Możesz zrobić coś takiego:

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

Po drugie, proponuję obsłużyć koniec strumienia. To tutaj BufferedReader.readLine() zwraca wartość null. Powyższy kod sprawdza tylko pusty ciąg. Po naprawieniu tego spróbuję ponownie. Następnie sprawdź za pomocą ab, gdy wszystko wygląda dobrze. Daj nam znać, jeśli problem nadal występuje.

+0

Po pierwsze, kod działa w przeglądarce, curl, telnet itp. Po drugie, obsługa 'null' nie jest główną przyczyną. W rzeczywistości w mojej sytuacji powinno się to zdarzyć tylko wtedy, gdy klient zamknie połączenie, co nie ma miejsca. Zaktualizowałem pytanie, aby było jasne. Dzięki za sugestie. – nab

Powiązane problemy