簡體   English   中英

在服務於Socket連接的兩個線程之間共享公共數據

[英]Share common data between two threads serving a Socket connection

我在SO上看到了很多類似的問題,但幾乎沒有任何人在圖片中有Socket 所以請花些時間閱讀這個問題。

我有服務器應用程序(使用ServerSocket )偵聽請求,當客戶端嘗試連接時,創建新線程以服務客戶端(並且服務器返回到新請求的偵聽模式)。 現在,我需要根據其他客戶端發送到服務器的內容來響應一個客戶端。

例:

  • ServerSocket偵聽傳入連接。
  • 客戶端A連接,創建新線程以服務A.
  • 客戶端B連接,創建新線程以服務B.
  • A向服務器發送消息“Hello from A”。
  • 將此消息作為對客戶B的響應發送。

我對這整個“線程間通信”的事情不熟悉。 顯然,上面提到的情況聽起來很簡單,但我正在描述這一點以獲得提示,因為我將在客戶端之間交換大量數據,將服務器保持為中間狀態。

另外,如果我想將共享對象限制為10個特定客戶端,該怎么辦? 這樣,當第11個客戶端連接到服務器時,我創建了新的共享對象,該對象將用於在第11,第12,第13 ......到第20個客戶端之間交換數據。 等等每一組10個客戶端。

我嘗試了什么:(我猜是愚蠢的)

  • 我有一個public類,該對象應該作為public static共享,以便我可以將其用作全局而不實例化它,如MyGlobalClass.SharedMsg
  • 這不起作用,我無法將一個線程中收到的數據發送給另一個線程。

我知道存在明顯的鎖定問題,因為如果一個線程正在寫入一個對象,則其他線程在第一個線程完成寫入之前無法訪問它。

那么這個問題的理想方法是什么呢?

更新

由於我創建線程來提供傳入連接請求的方式,我無法理解如何在線程之間共享相同的對象,因為如上所述使用Global對象不起作用。

以下是我如何監聽傳入連接並動態創建服務線程。

// Method of server class
public void startServer()
{
    if (!isRunning)
    {
        try
        {
            isRunning = true;
            while (isRunning)
            {
                try
                {
                    new ClientHandler(mysocketserver.accept()).start();
                }
                catch (SocketTimeoutException ex)
                {
                    //nothing to perform here, go back again to listening.
                }
                catch (SocketException ex)
                {
                    //Not to handle, since I'll stop the server using SocketServer's close() method, and its going to throw SocketException anyway.
                }
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
    else
        System.out.println("Server Already Started!");
}

ClientHandler類。

public class ClientHandler extends Thread
{
    private Socket client = null;
    private ObjectInputStream in = null;
    private ObjectOutputStream out = null;

    public ClientHandler(Socket client)
    {
        super("ClientHandler");
        this.client = client;
    }

    //This run() is common for every Client that connects, and that's where the problem is.
    public void run()
    {
        try
        {
            in = new ObjectInputStream(client.getInputStream());
            out = new ObjectOutputStream(client.getOutputStream());

            //Message received from this thread.
            String msg = in.readObject().toString();
            System.out.println("Client @ "+ client.getInetAddress().getHostAddress() +" Says : "+msg);


            //Response to this client.
            out.writeObject("Message Received");

            out.close();
            in.close();
            client.close();
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}

我相信我正在創建動態線程來為每個連接的客戶端提供服務,使用Global對象共享相同的數據源是不可能的,因為上面的run()主體對於每個連接的客戶端都是完全相同的,因此消費者和生產者都是同樣的方法。 我應該做什么修復,以便我可以為每個連接創建動態線程,並仍然共享相同的對象。

您可能需要一個隊列來在每個客戶端之間進行通信 每個隊列將是從一個客戶端推送到另一個客戶端的數據的“管道”。

您會像這樣使用它(偽代碼):

Thread 1:
Receive request from Client A, with message for Client B
Put message on back of concurrent Queue A2B
Respond to Client A.

Thread 2:
Receive request from Client B.
Pop message from front of Queue A2B
Respond to Client B with message.

您可能還希望它是通用的,因此您擁有許多客戶端(以及許多線程)可以寫入的AllToB隊列。

注意事項: ConcurrentLinkedQueueArrayBlockingQueue

如果要限制消息數,則ArrayBlockingQueue及其容量構造函數允許您執行此操作。 如果您不需要阻止功能,則可以使用方法offerpoll而不是puttake

我不擔心共享隊列,這會使問題變得更加復雜。 如果您知道需要解決的內存使用問題,請執行此操作。

編輯:根據您的更新:

如果需要在所有動態創建的實例之間共享單個實例,您可以:

  1. 創建一個靜態實例。
  2. 將它傳遞給構造函數。

示例1:

public class ClientHandler extends Thread
{
  public static final Map<ClientHandler, BlockingQueue<String>> messageQueues 
    = new ConcurrentHashMap<>();

  <snip>

  public ClientHandler(Socket client)
  {
    super("ClientHandler");
    this.client = client;
    // Note: Bad practice to reference 'this' in a constructor.
    // This can throw an error based on what the put method does.
    // As such, if you are to do this, put it at the end of the method.
    messageQueues.put(this, new ArrayBlockingQueue<>());
  }

  // You can now access this in the run() method like so:
  // Get messages for the current client.
  // messageQueues.get(this).poll();
  // Send messages to the thread for another client.
  // messageQueues.get(someClient).offer(message);

幾個筆記:

  • messageQueues對象應該包含客戶端的某種標識符,而不是短暫的對象引用。
  • 更可測試的設計會將messageQueues對象傳遞給構造函數以允許模擬。
  • 我可能會建議為地圖使用包裝類,因此您可以使用2個參數調用offer,而不必擔心映射語義。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM