[102] JUCE Diary #12 : 答客問:InterprocessConnectionServer
            網友在這篇文章問到 InterprocessConnectionServer 與 InterprocessConnection 的使用方式。本文以一簡易程式來說明如何使用這兩個類別。範例程式做的事情如下:
- 啟動一個 Server 程式監聽網路 Port 52713
 - 啟動一個 Client 程式連接到本機(localhost) Port 52713
 - 連線成功後,Client 會傳送資料到 Server
 - 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 的安全機制。
完整範例
- Server: https://gist.github.com/mht/e07604190015d0f909aa17cea5db864b
 - Client: https://gist.github.com/mht/f98fd3deeffed226fb4e8b2479e87834
 
其實 JUCE 隨附的 JuceDemo 專案(examples/Demo)有大量的範例可參考,不過 IPC 這主題 JuceDemo 示範的是 ChildProcessMaster/ChildProcessSlave 類別,剛好沒有 InterprocessConnectionServer。
接下來
本文以程式碼示範例 InterprocessConnectionServer 的使用方式,下一篇打算探討其設計應用了哪些 SOLID 原則。🔚