[102] JUCE Diary #12 : 答客問:InterprocessConnectionServer

[102] JUCE Diary #12 : 答客問:InterprocessConnectionServer

網友在這篇文章問到 InterprocessConnectionServerInterprocessConnection 的使用方式。本文以一簡易程式來說明如何使用這兩個類別。範例程式做的事情如下:

  1. 啟動一個 Server 程式監聽網路 Port 52713
  2. 啟動一個 Client 程式連接到本機(localhost) Port 52713
  3. 連線成功後,Client 會傳送資料到 Server
  4. Server 收到資料進行處理

Client 與 Server 皆為 JUCE Console Project,文章內的程式碼皆為片段,完整的程式碼請見文末的 Gist。由於只是示範,故省略許多錯誤判斷,請多留意。

Server-side: InterprocessConnectionServer

先來看 InterprocessConnectionServer 的介面:

class InterprocessConnectionServer : private Thread { // ①
public:
    bool beginWaitingForSocket (int portNumber);
    void stop();

protected:
    virtual InterprocessConnection* createConnectionObject() = 0; // ②

private:
    ScopedPointer<StreamingSocket> socket;
    void run() override;
};

① 以 private 繼承自 Thread 類別,使得 Thread 原本公開的成員函式變得無法被外界存取。此舉將 InterprocessConnectionServer 對外(public)介面變成只有兩個:

  • beginWaitingForSocket
  • stop

InterprocessConnectionServer 最重要的是名為 createConnectionObject「Pure Virtual Function(純虛擬函式)」,因為她,所以無法直接將 InterprocessConnectionServer 實體化(具現化?),用者必須繼承它並實作 createConnectionObject 成員函式才可以使用 InterprocessConnectionServer 提供的服務。底下的 IPCServer 為示範:

class IPCServer : public InterprocessConnectionServer
{
public:
	IPCServer()
	: connection_(nullptr) {}

	~IPCServer()
	{
		delete connection_;
	}

protected:
	InterprocessConnection* createConnectionObject() override
	{
		connection_ = new Connection();
		return connection_;
	}

	Connection* connection_;
};

createConnectionObject 只是簡單建立一個 Connection 物件,將其存於成員變數(於解構式中將其釋放),然後回傳。可是,Connection 類別哪來的?繼續看下去。

Server-side: InterprocessConnection

InterprocessConnection 類別也有**「純虛擬函式」**,所以也不能直接具現化。先看看她的介面(刪去不重要的部分):

class InterprocessConnection {
public:
    InterprocessConnection (bool callbacksOnMessageThread = true,
                            uint32 magicMessageHeaderNumber = 0xf2b49e2c);

    bool connectToSocket (const String& hostName,
                          int portNumber,
                          int timeOutMillisecs);

    virtual void connectionMade() = 0;
    virtual void connectionLost() = 0;
    virtual void messageReceived (const MemoryBlock& message) = 0;
};

要覆寫的「純虛擬函式」有三個: connectionMade, connectionLost, messageReceived。這三個函式皆為 CALLBACK 函式,會在特定時機被呼叫,由名稱可猜出她們被呼叫的時機點:

  • connectionMade:連線成功時呼叫
  • connectionLost:斷線時呼叫
  • messageReceived:接收到資料時呼叫

底下是我的 Connection 類別實作:

class Connection : public InterprocessConnection {
public:
    Connection()
      : InterprocessConnection(false, 15) {} // ①

    void connectionMade() override { printf("Connection made\n"); }
    void connectionLost() override { printf("Connection lost\n"); }

    void messageReceived(const MemoryBlock& msg) override { // ②
        printf("From client: %s\n", msg.toString().toRawUTF8());
    }
};
  • Connection 的建構式(ctor)呼叫 InterprocessConnection 建構式並傳入相應的參數值。
  • messageReceived 假設傳來的資料為字串,收到後直接將其印出。

Client-side: InterprocessConnection

在客戶端(Client-side)這邊同樣需要實作 InterprocessConnection ,作法同 Server-side InterprocessConnection,故不在此重覆。(完整範例見文末連結)

指揮艇,組合!

萬事俱備,可以開始實作功能了。

① 啟動一個 Server Process 監聽網路 Port 52713

先在 Server-side 建立一個 IPCServer 物件,並呼叫 beginWaitingForSocket 成員函式:

IPCServer server;
if (server.beginWaitingForSocket(52713)) {
    printf("Waiting for client...\n");
}

server.beginWaitingForSocket 若成功表示已經開始監聽 Port 52713

② 啟動一個 Client Process 連接到本機(localhost)Port 52713

在 Client-side 建立 Connection 物件,並呼叫其 connectToSocket 成員函式:

std::unique_ptr<Connection> client (new Connection());
client->connectionToSocket("localhost", 52713, 5000);

呼叫 connectionToSocket 會觸發 Server-side IPCServer 使其呼叫 createConnectionObject 用以建立 InterprocessConnection 物件,成功建立後會呼叫其 connectionMade

③ Client 傳送資料給 Server

Client 在確認連線成功後利用 sendMessage 傳送字串,送完後自動斷線(disconnect)。

if (client->isConnected()) {
    printf("Connected\n");

    MemoryBlock mb;
    String msg("713");
    mb.append(msg.toRawUTF8(), msg.length());
    client->sendMessage(mb);
    client->disconnect();
}

④ Server 收到 Client 傳來的並資料進行處理

當 Client-side 呼叫 sendMessage,Server-side IPCServer 裡的 messageReceived 會被呼叫:

void messageReceived(const MemoryBlock& msg) override {
	const auto str = msg.toString();
	printf("From client: %s\n", str.toRawUTF8());

	if (str.contains("713")) {
		// stop server
	}
}

JUCE 使用 MemoryBlock 來包裝傳送與接收的資料,MemoryBlock 提供許多成員函式,其中 toString 將資料轉成字串,很方便。

底下展示 Server(左邊)與 Client(右邊)的執行實況:

執行 server 程式後出現的警告訊窗為 OS X 的安全機制。

完整範例

其實 JUCE 隨附的 JuceDemo 專案(examples/Demo)有大量的範例可參考,不過 IPC 這主題 JuceDemo 示範的是 ChildProcessMaster/ChildProcessSlave 類別,剛好沒有 InterprocessConnectionServer

接下來

本文以程式碼示範例 InterprocessConnectionServer 的使用方式,下一篇打算探討其設計應用了哪些 SOLID 原則。🔚