2013-06-12 130 views
2

我正在實現一個使用Java的服務器/客戶機系統。服務器監聽來自客戶端的連接和客戶端連接後,服務器創建一個新的套接字並將其傳遞給一個新的線程,這將只用於接收數據:爲什麼Java發送需要Thread.Sleep

while (true){ 

     clientSocket=serverSocket.accept(); 
     new ClientReceiver(clientSocket,this.clientsManager).start(); 
    } 

的clientReceiver類如下:

public class ClientReceiver extends Thread { 

private Socket clientSocket=null; 
private Client client=null; 
private ClientsManager clientsManager; 

private ClientActionParser clientActionParser=new ClientActionParser(); 

ClientHandlerState clientHandlerState; 

PrintWriter outputStream=null; 
BufferedReader inputStream=null; 

public ClientReceiver(Socket clientSocket, ClientsManager clientsManager){ 

    this.clientSocket=clientSocket; 
    this.clientsManager=clientsManager; 
    this.setClientHandlerState(ClientHandlerState.Connected); 
} 

public void run(){ 

    String actionString; 
    try{ 
     //define output and input stream to client 
     outputStream =new PrintWriter(clientSocket.getOutputStream(),true); 
     inputStream = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 



     //while client is connected read input to actionString 
     while((actionString=inputStream.readLine()) != null){ 

       AbstractClientAction clientAction= this.clientActionParser.parse(actionString); 

       if(this.getClientHandlerState()==ClientHandlerState.Connected){ 

        if(clientAction instanceof ClientLoginAction){ 

         ClientLoginAction clientLoginAction=(ClientLoginAction) clientAction; 
         if(this.authenticate(clientLoginAction)){ 


         } 
         else{ 
           throw new AuthenticationException(); 
         } 

        } 
        else{ 

         throw new AuthenticationException(); 
        } 
       } 

      } 
      if(this.getClientHandlerState()==ClientHandlerState.Authorized){ 

       //receive other client actions: transfer barge .... 
      } 
      try { 
       Thread.sleep(400); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
    } 
    catch(IOException e){ 

    } 
    catch (AuthenticationException e) { 
     // TODO: handle exception 
    } 

    //clean up the resources 
    try{ 
     outputStream.close(); 
     inputStream.close(); 
     clientSocket.close(); 
    } 
    catch(Exception e){ 

    } 
} 

private boolean authenticate(ClientLoginAction clientLoginAction){ 
    //perform authentication. If authentication successful: 
    this.client=this.clientsManager.authenticateClient(clientLoginAction.getUsername(), clientLoginAction.getPassword()); 
    if(this.client==null){ 
     return false; 
    } 
    else{ 
     ClientSender clientSender=new ClientSender(this.outputStream, this.client); 
     this.clientsManager.addClientSender(clientSender); 
     this.setClientHandlerState(ClientHandlerState.Authorized); 
     clientSender.start(); 
     return true; 
    } 
} 

public ClientHandlerState getClientHandlerState(){ 

    return this.clientHandlerState; 
} 

public void setClientHandlerState(ClientHandlerState clientHandlerState){ 

    this.clientHandlerState=clientHandlerState; 
} 

在接收線程成功驗證之後,將數據發送到客戶端和套接字的OutputStream傳遞給新的線程創建一個新的線程。 clientSender類包含一個隊列作爲包含應發送給客戶端的數據的緩衝區。這裏是類clientSender:

public class ClientSender extends Thread { 

private Client client=null; 
private final Log logger = LogFactory.getLog(getClass()); 
PrintWriter outputStream=null; 
private Queue<String> clientEventsQueue= new LinkedList<String>(); 

public ClientSender(PrintWriter outputStream, Client client){ 
    this.outputStream=outputStream; 
    this.client=client; 
} 

public void run(){ 

    //System.out.println("ClientSender run method called."); 

    while(true){ 

     try { 
      Thread.sleep(10); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 


     if(!this.clientEventsQueue.isEmpty()){ 

      this.outputStream.println(this.clientEventsQueue.remove()); 
     } 
    } 
} 

public Client getClient(){ 

    return this.client; 
} 

public void insertClientEvent(String clientEvent){ 

    this.clientEventsQueue.add(clientEvent); 
} 

每當我想送點東西給客戶使用:

clientSender.insertClientEvent("some text"); 

的問題是,如果我刪除了Thread.sleep(10)我將不會收到任何東西客戶端。由於TCP套接字阻塞,我認爲這不應該發生。這是正常的還是我做錯了什麼?

編輯: 發件人線程沒有「終止」。無論何時從其他系統收到事件,服務器都應向所有客戶端發送適當的信息。所以我認爲最好的方案是在沒有數據要發送時停止線程,並在任何時候啓動它。所以我在clientSender類中試過這個:

public void run(){ 

    while(true){ 

     if(this.clientEventsQueue.isEmpty()){ 
      break; 
     } 
     else{ 
      try { 
       this.outputStream.println(this.clientEventsQueue.take()); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

但現在問題是何時啓動線程?我試圖啓動它,每當我想發送數據,但如我所料不正常工作,並且只發送拳包:

clientSender.insertClientEvent(clientEvent.getEventString()); 
clientSender.start(); 

EDIT2 我想出了這個主意。這非常簡單,我認爲它消耗的CPU時間少得多。

while(true){ 

     while(this.clientEventsQueue.isEmpty()){ 

      try { 
       Thread.sleep(300); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 

     try { 
      this.outputStream.println(this.clientEventsQueue.take()); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

    } 

就像我測試它,它工作得很好。你怎麼看待這件事?

+0

您正在使用哪種操作系統? –

+0

可能是種族危害。不安全的發佈到線程? – Raedwald

+1

用你發佈的內容很難猜測你的代碼有什麼問題。你顯然有某種地方的競爭條件。或者其他的東西。 –

回答

4

我看到您正在使用LinkedList作爲多線程訪問的隊列,並且您正在ClientSender中忙於等待它。
此實現不是線程安全的,並可能導致像clientEvents貼在它的問題不可見的ClientSender線程,CPU被浪費等

你可以使用一個BlockingQueue代替,並呼籲take()上對阻止排隊等待事件發佈。

我也看到你依靠sleep(400)來等待溝通。這也會導致問題。使用套接字資源的線程在完成時可以關閉它,而不是這個。

編輯:
有一些技術來處理終止線程。在這種情況下,我認爲一個「毒丸」可以很好地工作。基本上你:

String stuff = queue.take(); 
if (stuff == null) break; 

,並在隊列後null當你想終止它(不必爲空,可以是任何東西,如"terminate"

EDIT2:
你終止方式將不起作用,因爲它會在任何人發佈事件之前立即終止,理論上你可能會產卵,然後立即殺死線程。最簡單的方法是使用特殊的消息(又名「毒丸」)終止條件。

至於有一個線程只有當有一個事件,在這一點上,我建議使用線程池。您可以將事件發送封裝爲Runnable,並將套接字保存在地圖中。然而,這實現起來相當複雜,並且需要對多線程的良好理解以使其正確。多線程很難,並且在做錯時會引入嚴重的頭痛。 Tbh我不建議在不學習更多關於多線程編程的情況下嘗試這樣做。

EDIT3: @ user2355734:輪詢隊列的時間間隔與您所做的一樣,是由許多人完成的,但不鼓勵。 take()方法實際上是「睡眠」,只有在隊列中有某些東西時纔會醒來,所以理論上通過刪除「睡眠」循環,您可以獲得更低的CPU使用率和更短的延遲。一般來說,你應該儘量避免在多線程代碼中完全使用「睡眠」。很少有人真正需要它,而且這通常是破壞/不理想代碼的標誌。至於測試,雖然它們很有用,但通過測試確保多線程代碼的正確性很難甚至幾乎不可能。你的代碼可能在你的測試中運行良好,但在生產中,在高負載下,在不同的環境下等都會失敗。因此,重新審查代碼並確保其在理論上是正確的。

+0

打敗我吧。另外,對於阻塞行爲,使用'put'而不是'add'(如果緩衝區已滿,可能會拋出'IllegalStateException')。 [阻塞隊列Javadocs](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html)有一個很好的表格,根據所需的行爲解釋使用什麼方法。 –

+0

謝謝。我將clientEventsQueue數據結構更改爲ArrayBlockingQueue ,並使用put()和take()並解決了問題。但我仍然擁有while(true)循環,其中調用take()函數。我從你的答案理解,使用blockingQueue可以刪除。我對麼?我怎樣才能做到這一點? – user2355734

+0

@ user2355734:如果您打算讓雙方在連接時繼續發送消息,則該循環可能應該保留。 – cHao

相關問題