2015-09-20 105 views
1

所以我試圖創建一個客戶端/服務器程序。我想知道我的客戶何時自己斷開連接,所以我設置了心跳系統。每隔6秒我的客戶端發送一個ping到我的服務器,如果客戶端總共不發送ping一次30秒,客戶端被認爲是斷開連接並從當前連接列表(我計劃實現一個GUI)中刪除。或者至少,這是計劃。爲什麼我的布爾值不被更改?

ConnectionManager.java

public class ConnectionManager implements Runnable{ 

static Socket connection; 

private ArrayList<Thread> allConnections; 
private ArrayList<Connection> allConnectionList; 
private ServerSocket server; 
private int id = 0; 

public ConnectionManager() { 
    allConnections = new ArrayList<Thread>(); 
    allConnectionList = new ArrayList<Connection>(); 
} 


@Override 
public void run() { 
    try { 
     server = new ServerSocket(5555); 
     System.out.println("Server is running!"); 
     while(true) { 
      connection = server.accept(); 
      Connection a = new Connection(connection, id); 
      Runnable runnable = a; 
      allConnectionList.add(a); 
      allConnections.add(new Thread(runnable)); 
      allConnections.get(allConnections.size() - 1).start(); 
      id++; 
     } 
    } catch (IOException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
    } 
} 

public void removeConnection(int id) { 
    allConnections.remove(id); 
    allConnectionList.remove(id); 
} 

Connection.java

public class Connection implements Runnable { 

private Socket a; 
public boolean amIActive; 
private int id; 

public Connection(Socket a, int id) { 
    amIActive = true; 
    this.a = a; 
    this.id = id; 
} 

public void onConnect() { 
    try { 
     String TimeStamp = new java.util.Date().toString(); 
     String formattedAddress = a.getInetAddress().toString().replace("/", ""); 
     System.out.println("Received connection from: " + formattedAddress + " at " + TimeStamp); 
     Runnable runnable = new ConnectionListener(this); 
     Thread connectionThread = new Thread(runnable); 
     connectionThread.start(); 
     String returnCode = "Server repsonded to " + a.getInetAddress().toString().replace("/", "") + " at "+ TimeStamp + (char) 13; 
     BufferedOutputStream os = new BufferedOutputStream(a.getOutputStream()); 
     OutputStreamWriter osw = new OutputStreamWriter(os, "US-ASCII"); 
     osw.write(returnCode); 
     osw.flush(); 

    } catch (IOException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
    } 
} 

@Override 
public void run() { 
    onConnect(); 
    System.out.println("We got this far!"); 
    while(amIActive) { 
     whileTrue(); 
    } 
    System.out.println("This code never gets run because we get stuck in the while loop above"); 
    Main.b.removeConnection(id); 
    System.out.println("Connection was closed from " + a.getInetAddress()); 
} 

public void setOffline(boolean state) { 
    this.amIActive = state; 
} 

public void whileTrue() { 
} 

public Socket getSocket() { 
    return a; 
} 

ConnectionListener.java

public class ConnectionListener implements Runnable{ 

public Connection myConnection; 
public boolean receivedHeartbeat; 
public int missedHeartbeats = 0; 

public ConnectionListener(Connection a) { 
    this.myConnection = a; 
} 


@Override 
public void run() { 

    Runnable runnable = new Heartbeat(this); 
    Thread thread = new Thread(runnable); 
    thread.start(); 

    while(myConnection.amIActive) { 
     try { 
      BufferedInputStream is; 
      is = new BufferedInputStream(myConnection.getSocket().getInputStream()); 
      InputStreamReader isr = new InputStreamReader(is); 
      StringBuffer process = new StringBuffer(); 
      int character; 

      while((character = isr.read()) != 13) { //GETTING STUCK HERE BECAUSE STUPID. 
       if(character == -1) { 
        myConnection.setOffline(true); 
       } else { 
        process.append((char)character); 
       } 
      } 
      handleInput(process); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

public void handleInput(StringBuffer process) { 
    String messageSent = process.toString(); 
    if(messageSent.equals("Ping!")) { 
     receivedHeartbeat = true; 
    } 
} 

Heartbeat.java

public class Heartbeat implements Runnable{ 

private ConnectionListener b; 

public Heartbeat(ConnectionListener a) { 
    b = a; 
} 

@Override 
public void run() { 
    while(true) { 
     try { 
      Thread.sleep(1000); 
      if(b.missedHeartbeats > 5) { 
       b.myConnection.amIActive = false; 
       System.out.println("Setting amIActiveToFalse!"); 
      } 
      if(b.receivedHeartbeat) { 
       b.receivedHeartbeat = false; 
      } else { 
       b.missedHeartbeats++; 
      } 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
    } 

} 

我的控制檯被Heartbeat.java上的System.out.println("Setting amIActiveToFalse!");垃圾郵件攻擊。但Connection.java中的while循環繼續運行。我相信這可能與我的線程有關,但我無法弄清楚。

+1

如果標記變量volatile,它會工作嗎? –

+0

@AndyTurner確實有效。謝謝 :)。然而,這很簡單,讓我覺得有一種可怕的缺點,它會在後來叮叮噹噹地咬我,所以我要對它進行一些研究。再次感謝! :0 –

+1

當你得到一個,你永遠不會重置missedHeartbeats。順便說一句:這會讓你的CPU笨拙:while(amIActive)whileTrue(); }'。 – Kenney

回答

1

如果您有一個非易失性變量,則不存在對一個線程中的更改進行可視性檢查的保證。尤其是,如果JVM檢測到線程沒有更改boolean,則它可以內聯它,這意味着您將永遠不會看到值更改。

簡單的解決方案是製作布爾volatile,它不會被內聯,並且一個線程會看到另一個線程何時更改它。

詳情http://vanillajava.blogspot.com/2012/01/demonstrating-when-volatile-is-required.html

1

調試時有你發佈的代碼,我看到一對夫婦的問題,但我能順利拿到心跳功能工作。

  1. 在連接監聽器類,我不認爲if語句與.equals("Ping!")將會匹配,因爲在每行末尾的換行符。
  2. 在Connection Listener類中,我可能會將套接字的輸入流置於循環的頂部而不是循環內部。 (我不認爲這將打破它,但它可能是更好的這種方式)

ConnectionListener更新:

public void run() { 

    Runnable runnable = new Heartbeat(this); 
    Thread thread = new Thread(runnable); 
    thread.start(); 

    BufferedReader br = null; 
    try { 
    //is = new BufferedInputStream(myConnection.getSocket().getInputStream()); 
    br = new BufferedReader(new InputStreamReader(myConnection.getSocket().getInputStream())); 
    } catch (IOException e1) { 
     // TODO Auto-generated catch block 
     e1.printStackTrace(); 
    } 

    while(myConnection.amIActive) { 
    try { 
     String processLine = br.readLine(); 
     System.out.println("handleInput:" + processLine); 
     handleInput(processLine); 
    } catch (Exception e) { 
     System.out.println("Exception!"); 
     e.printStackTrace(); 
    } 
    } 
} 

public void handleInput(String messageSent) { 
    if(messageSent.startsWith("Ping!")) { //Need to use startsWith, or add newline character 
    receivedHeartbeat = true; 
    System.out.println("receivedHeartbeat!"); 
    } 
} 
  • 此外,在你的心跳類確保您重置truemissedHeartbeats計數器爲0:
  • if(b.receivedHeartbeat) { 
        b.receivedHeartbeat = false; 
        b.missedHeartbeats = 0; 
    } else { 
        b.missedHeartbeats++; 
    } 
    
    1

    瑣細回答這個問題是:使變量volatile

    如果沒有這個,允許線程更改值基本上保持它的更新緩存,稍後將它們提交到主存儲器。

    這使得線程代碼運行速度要快得多,因爲它可以保持其變量高速緩存,而不是從主內存獲取。但是,其結果是其他線程看不到更新。

    使得可變揮發性防止這種情況的發生:一個線程始終讀從主內存的值,並寫入被立即提交。

    我說,這是微不足道的答案,因爲它不一定解決所有你的問題。可能還有一個原子性問題:在一個讀取變量的線程和再次寫入變量之間,另一個線程可能會潛入並更改其值,這可能會或可能不會將第一個線程從不變量的角度置於未定義狀態。

    具體來說:

    if(b.receivedHeartbeat) { b.receivedHeartbeat = false; 
    

    可能的是,這個線程它評估爲真之後其他線程可以改變b.receivedHeartbeat爲假,所以該迭代被錯誤地計數爲「非錯過」心跳。

    這可以通過使變量a(非易失性)的AtomicBoolean,其上有一個原子的比較和設置方法,該方法避免了這樣的競爭條件被固定。

    Java併發在實踐中對這些問題有很大的參考,我全心全意地推薦它。尋找「可見性」和「原子性」的主題。

    而且讀了Java內存模型先進的篇章。這讓我開始懷疑自己,但在消化它之後讓我成爲了一個更強大的程序員。