본문 바로가기

Qt

Qt IPC 성능 비교 실험기 ㅡ QSharedMemory, QLocalSocket, QTcpSocket, QRemoteObject

들어가며

Qt Framework를 사용하는 개발자라면, Qt에서 공식 제공하는 IPC를 활용해 개발하고 싶을 것이다.

 

프로토콜이나 아키텍처를 설계할 때 IPC 선정은 매우 중요한 요소이고,

그중에서도 전송 속도는 항상 주요한 판단 기준이 된다.

 

Qt Framework를 사용하며 IPC별 속도가 궁금해졌고,

직접 테스트 환경을 구성해 IPC별 성능 비교를 진행해 보았다.

 

이 글에서는 다음 네 가지 IPC의 성능을 비교한다.

  • QSharedMemory
  • QLocalSocket
  • QTcpSocket
  • QRemoteObject

테스트 환경 및 방법

테스트는 Windows PC의 Local 환경에서 진행했다.

Qt로 서버를 구현하고, googletest(gtest) 기반의 유닛 테스트를 통해 성능을 측정했다.

 

테스트 시나리오

데이터 전송 크기에 따라 IPC 속도측정에 변화가 생길 것을 고려해

다음 두 가지 케이스로 나누어 테스트했다.

  • 100MB binary data 를 100번 송수신, 평균속도 측정
  • 200Byte binary data 를 1000번 송수신, 평균속도 측정

또한 IPC 방식에 따른 조건 차이를 최소화하기 위해,

메모리를 공유하는 QSharedMemory, QRemoteObject의 경우

서버가 데이터를 수신한 뒤 그대로 한번더 데이터를 전송하도록 구현했다.

테스트 1회 측정 시간

테스트 결과

200 Byte 전송 평균 시간(us)

200 Byte 전송 시간

IPC 평균시간(us)
QtSharedMemory 16.459
QTcpSocket 63.828
QLocalSocket 80.066
QtRemoteObject 177.174

 

100 MB 전송 시간

IPC 평균시간(ms)
QtSharedMemory 41.194
QTcpSocket 186.324
QLocalSocket 238.556
QtRemoteObject 207.681

 

결과는 의외였다. (테스트 제대로 한거 맞겠지..?)

Local전용 IPC라 더 빠를것이라 예상한 QLocalSocket의 경우 QTcpSocket보다 느렸으며,

Qt EventLoop 기반으로 동작해서 느릴 것이라고 생각한 QRemoteObject는 대용량 전송에서 준수한 성능으로 나타났다.

최종 테스트 결과 : 100 MB 전송 속도

  Mbps
QSharedMemory 19420.3
QTcpSocket 4293.6
QRemoteObject 3852.1
QLocalSocket 3353.5

회고

직접 서버와 클라이언트를 구현해 성능을 비교해 보니,

예상과 다른 결과가 나와 인상 깊었다.

 

특히 Qt 6 에서 도입된 QRemoteObject는

단순한 RPC 수준을 넘어 실사용 가능하다는 가능성을 보았고,

 

단지 빠르다고만 알고 있었던 Shared Memory의 성능을 수치로 확인하며,
CS지식을 넓히는 계기가 됐다.

잠깐 찾아본 결과, QTcpSocket이 빠른 이유는, "운영체제 커널의 네트워크 스택 최적화"가 잘 돼있기 때문이라고 한다.
Localhost 통신은 NIC를 거치지 않고, 커널 메모리 내부에서 바로 패킷을 전달하고
오랜 시간 최적화를 통해 OS커널 버퍼를 적극 활용하기 때문에 빠른 결과가 나올 수 있다고 한다.
이번에 처음 써본 QRemoteObject는 Qt EventLoop 기반으로 동작하며
Server와 Client간의 동기화된 QObject를 생성하는 느낌이다.
LocalTest에서는 생각했던 것보다 훨씬 속도가 빨라 Online에서도 적극 사용을 고려해 봐야겠다.

서버 및 테스트 코드

QSharedMemory Echo Server

더보기
#include <QCoreApplication>
#include <QSharedMemory>
#include <QSystemSemaphore>
#include <QDebug>
#include <vector>
#include <QThread>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    while (1)
    {
        // 1. Throughput용 리소스
        QSharedMemory speedMem("SpeedTestMem");
        QSystemSemaphore speedSemClient("SpeedSemClient", 0, QSystemSemaphore::Open);
        QSystemSemaphore speedSemServer("SpeedSemServer", 0, QSystemSemaphore::Open);

        // 2. Latency용 리소스
        QSharedMemory latencyMem("TestSharedMem");
        QSystemSemaphore latSemClient("SemClient", 0, QSystemSemaphore::Open);
        QSystemSemaphore latSemServer("SemServer", 0, QSystemSemaphore::Open);

        qDebug() << "Shared Memory Echo Server (High-Performance Mode) Started...";

        // --- [Throughput 테스트 루프] ---
        qDebug() << "Throughput 테스트 대기 중...";
        while (!speedMem.attach()) { QThread::usleep(100); } // 최초 연결 시에만 짧게 대기

        const qint64 sSize = speedMem.size();
        std::vector<char> sBuffer(sSize);

        for (int i = 0; i < 100; ++i) {
            // 여기서 acquire()는 클라이언트가 write를 마칠 때까지 커널 레벨에서 대기(Block)하며,
            // 신호가 오자마자 즉시 깨어납니다 (msleep 필요 없음).
            if (!speedSemServer.acquire()) break;

            speedMem.lock();
            memcpy(sBuffer.data(), speedMem.constData(), sSize);
            memcpy(speedMem.data(), sBuffer.data(), sSize);
            speedMem.unlock();

            speedSemClient.release();
        }
        qDebug() << "Throughput 테스트 완료.";
        speedMem.detach();


        // --- [Latency 테스트 루프] ---
        qDebug() << "Latency 테스트 대기 중...";
        while (!latencyMem.attach()) { QThread::usleep(100); }

        const qint64 lSize = latencyMem.size();
        std::vector<char> lBuffer(lSize);

        for (int i = 0; i < 1000; ++i) {
            if (!latSemServer.acquire()) break;

            latencyMem.lock();
            memcpy(lBuffer.data(), latencyMem.constData(), lSize);
            memcpy(latencyMem.data(), lBuffer.data(), lSize);
            latencyMem.unlock();

            latSemClient.release();
        }
        qDebug() << "Latency 테스트 완료. 모든 작업 종료.";
    }

    return 0;
}

QSharedMemory Client

더보기
#include <gtest/gtest.h>
#include <QSharedMemory>
#include <QSystemSemaphore>
#include <QFile>
#include <QDebug>
#include <chrono>

TEST(IpcSpeedTest, QSharedMemory_100MB_100Times) {
    const QString fileName = "../large_test_data.bin";
    const int iterations = 100;
    std::vector<double> durations_s;
    durations_s.reserve(iterations);

    // 1. 파일 데이터 로드
    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray largeData = file.readAll();
    file.close();
    
    const qint64 dataSize = largeData.size();
    const double dataSizeMB = static_cast<double>(dataSize) / (1024.0 * 1024.0);

    // 2. 공유 메모리 및 세마포어 준비
    QSharedMemory sharedMem("SpeedTestMem");
    if (sharedMem.attach()) sharedMem.detach();
    // 데이터 크기만큼 공유 메모리 생성
    ASSERT_TRUE(sharedMem.create(dataSize));

    QSystemSemaphore semClient("SpeedSemClient", 0, QSystemSemaphore::Create);
    QSystemSemaphore semServer("SpeedSemServer", 0, QSystemSemaphore::Create);

    QByteArray echoed;
    echoed.resize(int(dataSize));

    char *dest = static_cast<char*>(sharedMem.data());

    for (int i = 0; i < iterations; ++i) {
        auto start = std::chrono::high_resolution_clock::now();

        // [송신] 공유 메모리에 데이터 복사
        sharedMem.lock();
        memcpy(dest, largeData.constData(), dataSize);
        sharedMem.unlock();

        // 서버에게 읽으라고 알림
        semServer.release();

        // 서버가 다 읽었다는 응답 대기
        ASSERT_TRUE(semClient.acquire());

        // [검증] 서버가 에코로 써준 데이터를 다시 읽어서 비교
        sharedMem.lock();
        memcpy(echoed.data(), sharedMem.constData(), dataSize);
        sharedMem.unlock();

        ASSERT_EQ(0, memcmp(echoed.constData(), largeData.constData(), dataSize))
            << "Echo data mismatch at iteration " << i;

        auto end = std::chrono::high_resolution_clock::now();
        durations_s.push_back(std::chrono::duration<double>(end - start).count());

        if ((i + 1) % 10 == 0) {
            qDebug() << QString("SharedMemory Throughput 진행률: %1/%2 완료").arg(i + 1).arg(iterations);
        }
    }

    double avg_s = std::accumulate(durations_s.begin(), durations_s.end(), 0.0) / iterations;
    double avg_mbps = (dataSizeMB / avg_s) * 8.0;

    qDebug() << "==== QSharedMemory Throughput Result ====";
    qDebug() << "Test Data Size :" << dataSizeMB << "MB";
    qDebug() << "Repeat Count   :" << iterations;
    qDebug() << "Avg Time (Raw) :" << avg_s << "s";
    qDebug() << "Avg Speed      :" << avg_mbps << "Mbps";
    qDebug() << "========================================";

    sharedMem.detach();
}

TEST(IpcLatencyTest, QSharedMemory_200B_1000Times) {
    const QString fileName = "../large_test_data.bin";
    const int pingPongCount = 1000;
    const qint64 packetSize = 200;
    
    // 1. 파일에서 200바이트 데이터 로드
    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray testData = file.read(packetSize);
    file.close();

    // 2. 공유 메모리 및 세마포어 설정
    // 서버측에서도 동일한 키(key)를 사용해야 합니다.
    QSharedMemory sharedMem("TestSharedMem");
    if (sharedMem.attach()) sharedMem.detach();
    ASSERT_TRUE(sharedMem.create(packetSize));

    // 동기화를 위한 시스템 세마포어 (두 개를 사용하여 핑퐁 구현)
    QSystemSemaphore semClient("SemClient", 0, QSystemSemaphore::Create);
    QSystemSemaphore semServer("SemServer", 0, QSystemSemaphore::Create);

    QByteArray echoed;
    echoed.resize(int(packetSize));

    // 3. 테스트 시작
    auto start = std::chrono::high_resolution_clock::now();

    char *dest = static_cast<char*>(sharedMem.data());

    for (int j = 0; j < pingPongCount; ++j) {
        // [송신 단계]
        sharedMem.lock();
        memcpy(dest, testData.constData(), packetSize);
        sharedMem.unlock();
        
        semServer.release(); // 서버에게 "읽어가라" 신호

        // [수신 대기 단계]
        ASSERT_TRUE(semClient.acquire()); // 서버가 "응답했다"는 신호 대기

        // [검증]
        sharedMem.lock();
        memcpy(echoed.data(), sharedMem.constData(), packetSize);
        sharedMem.unlock();

        ASSERT_EQ(0, memcmp(echoed.constData(), testData.constData(), packetSize))
            << "Echo data mismatch at ping " << j;
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto total_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
    double avgLatency_us = static_cast<double>(total_us) / pingPongCount;

    qDebug() << "==== QSharedMemory Latency Result ====";
    qDebug() << "Test Data Size :" << packetSize << "Byte";
    qDebug() << "Total Swaps    :" << pingPongCount << "Times";
    qDebug() << "Total Time     :" << total_us << "us";
    qDebug() << "Avg Time (1회) :" << avgLatency_us << "us";
    qDebug() << "======================================";

    sharedMem.detach();
}

QTcpSocket Echo Server

더보기
#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>

class TcpEchoServer : public QObject {
    Q_OBJECT
public:
    TcpEchoServer(quint16 port, QObject *parent = nullptr) : QObject(parent) {
        m_server = new QTcpServer(this);

        if (!m_server->listen(QHostAddress::Any, port)) {
            qCritical() << "TCP 서버 시작 실패:" << m_server->errorString();
            return;
        }

        qDebug() << "TCP 에코 서버 대기 중... 포트:" << port;
        connect(m_server, &QTcpServer::newConnection, this, &TcpEchoServer::onNewConnection);
    }

private slots:
    void onNewConnection() {
        while (m_server->hasPendingConnections()) {
            QTcpSocket *clientSocket = m_server->nextPendingConnection();

            // 스트리밍 에코 로직
            connect(clientSocket, &QTcpSocket::readyRead, [clientSocket]() {
                while (clientSocket->bytesAvailable() > 0) {
                    QByteArray data = clientSocket->readAll();
                    clientSocket->write(data);
                }
                clientSocket->flush();
            });

            connect(clientSocket, &QTcpSocket::disconnected, [clientSocket]() {
                clientSocket->deleteLater();
            });
        }
    }

private:
    QTcpServer *m_server;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    TcpEchoServer server(12345); // 테스트용 포트 번호
    return a.exec();
}

#include "main.moc"

QTcpSocket Client

더보기
#include <gtest/gtest.h>
#include <QTcpSocket>
#include <QHostAddress>
#include <QFile>
#include <QDebug>
#include <vector>
#include <numeric>
#include <chrono>

// 1. TCP 대용량(100MB) 전송 테스트 - 100회 반복
TEST(IpcSpeedTest, QTcpSocket_100MB_100Times) {
    const QString fileName = "../large_test_data.bin";
    const int iterations = 100; // 100회 반복으로 수정
    const quint16 port = 12345;

    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray largeData = file.readAll();
    file.close();
    
    const double dataSizeMB = static_cast<double>(largeData.size()) / (1024.0 * 1024.0);

    QTcpSocket socket;
    socket.setSocketOption(QAbstractSocket::LowDelayOption, 1);
    socket.connectToHost("127.0.0.1", port);
    ASSERT_TRUE(socket.waitForConnected(3000));

    std::vector<double> durations_s;
    durations_s.reserve(iterations);

    for (int i = 0; i < iterations; ++i) {
        auto start = std::chrono::high_resolution_clock::now();

        // [송신]
        qint64 totalSent = 0;
        while (totalSent < largeData.size()) {
            qint64 written = socket.write(largeData.constData() + totalSent, largeData.size() - totalSent);
            if (written == -1) break;
            totalSent += written;
            socket.waitForBytesWritten(3000); 
        }

        // [수신]
        QByteArray receivedData;
        receivedData.reserve(largeData.size());
        while (receivedData.size() < largeData.size()) {
            if (socket.waitForReadyRead(5000)) {
                receivedData.append(socket.readAll());
            } else {
                FAIL() << "회차 " << i + 1 << ": 수신 시간 초과";
                break;
            }
        }

        auto end = std::chrono::high_resolution_clock::now();
        EXPECT_EQ(largeData.size(), receivedData.size());
        
        // 초 단위 소요 시간 저장
        double seconds = std::chrono::duration<double>(end - start).count();
        durations_s.push_back(seconds);

        // 진행률 로그
        if ((i + 1) % 10 == 0) {
            qDebug() << QString("TCP Throughput 진행률: %1/%2 완료").arg(i + 1).arg(iterations);
        }
    }

    socket.disconnectFromHost();
    if (socket.state() != QAbstractSocket::UnconnectedState) {
        socket.waitForDisconnected(1000);
    }

    // 통계 계산
    double avg_s = std::accumulate(durations_s.begin(), durations_s.end(), 0.0) / iterations;
    double avg_mbps = (dataSizeMB / avg_s) * 8.0;

    qDebug() << "==== QTcpSocket Throughput Result ====";
    qDebug() << "Test Data Size :" << dataSizeMB << "MB";
    qDebug() << "Repeat Count   :" << iterations;
    qDebug() << "Avg Time (Raw) :" << avg_s << "s";
    qDebug() << "Avg Speed      :" << avg_mbps << "Mbps";
    qDebug() << "========================================";
}

// 2. TCP 소용량(200B) 왕복 지연 시간 테스트 - 1000회 반복 (형식 통일)
TEST(IpcLatencyTest, QTcpSocket_200B_1000Times_SingleRun) {
    const QString fileName = "../large_test_data.bin";
    const int pingPongCount = 1000;
    const qint64 packetSize = 200;
    const quint16 port = 12345;
    
    // 1. 파일에서 200바이트만 읽어오기
    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray testData = file.read(packetSize); // 정확히 200바이트만 로드
    file.close();

    // 데이터가 충분하지 않을 경우를 대비한 안전장치
    ASSERT_EQ(testData.size(), packetSize) << "파일에 읽을 수 있는 데이터가 부족합니다.";

    QTcpSocket socket;
    socket.setSocketOption(QAbstractSocket::LowDelayOption, 1); 
    
    socket.connectToHost("127.0.0.1", port);
    ASSERT_TRUE(socket.waitForConnected(3000));

    // 시간 측정 시작
    auto start = std::chrono::high_resolution_clock::now();

    for (int j = 0; j < pingPongCount; ++j) {
        // 송신
        if (socket.write(testData) == -1) {
            FAIL() << "데이터 송신 실패";
            break;
        }
        socket.flush();

        // 수신 (에코 서버로부터 200바이트 대기)
        while (socket.bytesAvailable() < packetSize) {
            if (!socket.waitForReadyRead(1000)) {
                FAIL() << "수신 타임아웃 발생 (회차: " << j << ")";
                break;
            }
        }
        socket.readAll(); // 버퍼 비우기
    }

    auto end = std::chrono::high_resolution_clock::now();
    
    // 결과 계산
    auto total_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
    double avgLatency_us = static_cast<double>(total_us) / pingPongCount;

    qDebug() << "==== QTcpSocket Latency Result ====";
    qDebug() << "Source File    :" << fileName;
    qDebug() << "Test Data Size :" << packetSize << "Byte";
    qDebug() << "Total Swaps    :" << pingPongCount << "Times";
    qDebug() << "Total Time     :" << total_us << "us";
    qDebug() << "Avg Time (1회) :" << avgLatency_us << "us";
    qDebug() << "====================================";

    socket.disconnectFromHost();
    socket.waitForDisconnected(1000);
}

QRemoteObject Echo Server

더보기
// main.cpp
#include <QCoreApplication>
#include <QRemoteObjectHost>
#include <QObject>
#include "rep_RoInterface_source.h"

class RemoteObjectControl : public RemoteObjectControlSimpleSource {
    Q_OBJECT
public:
    RemoteObjectControl(QObject *parent = nullptr) {};
    ~RemoteObjectControl() {}

    void sendRawData(QByteArray data) override {
        m_data = data;
        emit dataEchoed(m_data);
    };

private:
    QByteArray m_data;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QRemoteObjectHost srcNode(QUrl(QStringLiteral("local:ro_test")));

    RemoteObjectControl remoteObjectService;
    if (!srcNode.enableRemoting(&remoteObjectService)) {
        qCritical() << "Failed to enable remoting!";
        return -1;
    }

    qDebug() << "QtRO Echo Server is running...";
    return a.exec();
}

#include "main.moc"

+ RemoteObject에 사용되는 RoInterface.rep 파일

// RoInterface.rep
class RemoteObjectControl {
    // [Throughput용] 클라이언트가 대용량 데이터를 서버로 던질 때 사용
    SLOT(void sendRawData(QByteArray data));

    // [Latency용] 서버가 데이터를 받았음을 즉시 알릴 때 사용 (에코 응답)
    SIGNAL(dataEchoed(QByteArray data));
};

QRemoteObject Client

더보기
#include <gtest/gtest.h>
#include <QRemoteObjectNode>
#include <QEventLoop>
#include <QCoreApplication>
#include <QFile>
#include "build/rep_RoInterface_replica.h"


// 1. QtRO Throughput 테스트 (100MB 100회)
TEST(IpcSpeedTest, QtRO_100MB_100Times) {
    int argc = 0;
    QCoreApplication app(argc, nullptr);

    const QString fileName = "../large_test_data.bin";
    const int iterations = 100; // 100회 반복으로 수정
    const quint16 port = 12345;
    std::vector<double> durations_s;

    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray largeData = file.readAll();
    file.close();
    
    const double dataSizeMB = static_cast<double>(largeData.size()) / (1024.0 * 1024.0);
    
    QRemoteObjectNode repNode;
    repNode.connectToNode(QUrl(QStringLiteral("local:ro_test")));
    QSharedPointer<RemoteObjectControlReplica> replica(repNode.acquire<RemoteObjectControlReplica>());
    ASSERT_TRUE(replica->waitForSource(5000));

    for (int i = 0; i < iterations; ++i) {
        auto start = std::chrono::high_resolution_clock::now();
        
        QEventLoop loop;
        QObject::connect(replica.data(), &RemoteObjectControlReplica::dataEchoed, &loop, &QEventLoop::quit);
        
        replica->sendRawData(largeData);
        loop.exec();

        auto end = std::chrono::high_resolution_clock::now();

        double seconds = std::chrono::duration<double>(end - start).count();
        durations_s.push_back(seconds);

        // 진행률 로그
        if ((i + 1) % 10 == 0) {
            qDebug() << QString("Qt_RO Throughput 진행률: %1/%2 완료").arg(i + 1).arg(iterations);
        }
    }
    
    double avg_s = std::accumulate(durations_s.begin(), durations_s.end(), 0.0) / iterations;
    double avg_mbps = (dataSizeMB / avg_s) * 8.0;

    qDebug() << "==== QtRO Throughput Result ====";
    qDebug() << "Test Data Size :" << dataSizeMB << "MB";
    qDebug() << "Repeat Count   :" << iterations;
    qDebug() << "Avg Time (Raw) :" << avg_s << "s";
    qDebug() << "Avg Speed      :" << avg_mbps << "Mbps";
    qDebug() << "========================================";
}

// 2. QtRO Latency 테스트 (200B 1000회)
TEST(IpcLatencyTest, QtRO_200B_1000Times) {
    int argc = 0;
    QCoreApplication app(argc, nullptr);

    const QString fileName = "../large_test_data.bin";
    const int pingPongCount = 1000;
    const qint64 packetSize = 200;
    
    // 1. 파일에서 200바이트만 읽어오기
    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray testData = file.read(packetSize); 
    file.close();

    // QtRO 연결 설정
    QRemoteObjectNode repNode;
    repNode.connectToNode(QUrl(QStringLiteral("local:ro_test")));
    
    QSharedPointer<RemoteObjectControlReplica> replica;
    replica.reset(repNode.acquire<RemoteObjectControlReplica>());

    // 연결 대기 (서버가 켜져 있어야 함)
    ASSERT_TRUE(replica->waitForSource(5000));

    auto start = std::chrono::high_resolution_clock::now();

    for (int j = 0; j < pingPongCount; ++j) {
        QEventLoop loop;
        // 서버에서 응답 시그널이 오면 루프 탈출
        QObject::connect(replica.data(), &RemoteObjectControlReplica::dataEchoed, &loop, &QEventLoop::quit);
        
        replica->sendRawData(testData); // 서버 슬롯 호출
        loop.exec(); // 응답 대기
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto total_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

    qDebug() << "==== QtRO Latency Result ====";
    qDebug() << "Test Data Size :" << packetSize << "Byte";
    qDebug() << "Total Swaps    :" << pingPongCount << "Times";
    qDebug() << "Total Time     :" << total_us << "us";
    qDebug() << "Avg Time (1회) :" << (double)total_us / pingPongCount << "us";
}

QLocalSocket Echo Server

더보기
#include <QCoreApplication>
#include <QLocalServer>
#include <QLocalSocket>
#include <QDebug>

class IpcServer : public QObject {
    Q_OBJECT
public:
    IpcServer(QString serverName, QObject *parent = nullptr) : QObject(parent) {
        m_server = new QLocalServer(this);
        QLocalServer::removeServer(serverName); // 이전 실행에서 남아있을 수 있는 서버 파일(네임드 파이프) 제거

        if (!m_server->listen(serverName)) {
            qCritical() << "서버를 시작할 수 없습니다:" << m_server->errorString();
            return;
        }
        qDebug() << "IPC 서버 대기 중... 서버명:" << serverName;

        connect(m_server, &QLocalServer::newConnection, this, &IpcServer::onNewConnection);
    }

private slots:
    void onNewConnection() {
        QLocalSocket *clientSocket = m_server->nextPendingConnection();

        // 데이터가 들어오면 다시 Echo (보내기)
        connect(clientSocket, &QLocalSocket::readyRead, [clientSocket]() {
            while (clientSocket->bytesAvailable() > 0) {
                QByteArray chunk = clientSocket->readAll();
                clientSocket->write(chunk);
            }
            clientSocket->flush();
        });

        // 연결 종료 시 정리
        connect(clientSocket, &QLocalSocket::disconnected, [clientSocket]() {
            clientSocket->deleteLater();
        });
    }

private:
    QLocalServer *m_server;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    IpcServer server("QLocalEchoServer");

    return a.exec();
}

#include "main.moc"

QLocalSocket Client

더보기
#include <gtest/gtest.h>
#include "mytestlib.h"
#include <QLocalSocket>
#include <QDebug>
#include <QFile>
#include <vector>
#include <numeric>
#include <chrono>

// 1. 대용량(100MB) 전송 테스트 - 100회 반복으로 수정
TEST(IpcSpeedTest, QLocalSocket_100MB_100Times) {
    const QString fileName = "../large_test_data.bin";
    const int iterations = 100; // 100회 반복

    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray largeData = file.readAll();
    file.close();
    
    const double dataSizeMB = static_cast<double>(largeData.size()) / (1024.0 * 1024.0);

    QLocalSocket socket;
    socket.connectToServer("QLocalEchoServer");
    ASSERT_TRUE(socket.waitForConnected(3000));

    std::vector<double> durations_s;
    durations_s.reserve(iterations);

    for (int i = 0; i < iterations; ++i) {
        auto start = std::chrono::high_resolution_clock::now();

        // [송신]
        qint64 totalSent = 0;
        while (totalSent < largeData.size()) {
            qint64 written = socket.write(largeData.constData() + totalSent, largeData.size() - totalSent);
            if (written == -1) break;
            totalSent += written;
            socket.waitForBytesWritten(1000);
        }

        // [수신]
        QByteArray receivedData;
        receivedData.reserve(largeData.size());
        while (receivedData.size() < largeData.size()) {
            if (socket.waitForReadyRead(5000)) {
                receivedData.append(socket.readAll());
            } else {
                FAIL() << "회차 " << i + 1 << ": 수신 시간 초과";
                break;
            }
        }

        auto end = std::chrono::high_resolution_clock::now();
        EXPECT_EQ(largeData.size(), receivedData.size());
        
        // 초 단위 소요 시간 저장
        double seconds = std::chrono::duration<double>(end - start).count();
        durations_s.push_back(seconds);

        // 회차별 로그 (너무 많으면 생략 가능)
        if ((i + 1) % 10 == 0) {
            qDebug() << QString("진행률: %1/%2 완료").arg(i + 1).arg(iterations);
        }
    }

    socket.disconnectFromServer();
    if (socket.state() != QLocalSocket::UnconnectedState) {
        socket.waitForDisconnected(1000);
    }

    // 통계 계산
    double avg_s = std::accumulate(durations_s.begin(), durations_s.end(), 0.0) / iterations;
    double avg_mbps = (dataSizeMB / avg_s) * 8.0;

    qDebug() << "==== QLocalSocket Throughput Result ====";
    qDebug() << "Test Data Size :" << dataSizeMB << "MB";
    qDebug() << "Repeat Count   :" << iterations;
    qDebug() << "Avg Time (Raw) :" << avg_s << "s";
    qDebug() << "Avg Speed      :" << avg_mbps << "Mbps";
    qDebug() << "========================================";
}

TEST(IpcLatencyTest, QLocalSocket_200B_1000Times) {
    const QString fileName = "../large_test_data.bin";
    const int pingPongCount = 1000; // 왕복 횟수
    const qint64 packetSize = 200;
    
    // 1. 파일에서 200바이트만 읽어오기
    QFile file(fileName);
    ASSERT_TRUE(file.open(QIODevice::ReadOnly));
    QByteArray testData = file.read(packetSize); 
    file.close();

    QLocalSocket socket;
    socket.connectToServer("QLocalEchoServer");
    ASSERT_TRUE(socket.waitForConnected(3000));

    // 시간 측정 시작 (1,000번 왕복 전체 시간)
    auto start = std::chrono::high_resolution_clock::now();

    for (int j = 0; j < pingPongCount; ++j) {
        // 송신
        if (socket.write(testData) == -1) {
            FAIL() << "데이터 송신 실패 (회차: " << j << ")";
            break;
        }
        socket.flush(); 

        // 수신 (200바이트를 다 받을 때까지 대기)
        while (socket.bytesAvailable() < packetSize) {
            if (!socket.waitForReadyRead(1000)) {
                FAIL() << "수신 타임아웃 발생 (회차: " << j << ")";
                break;
            }
        }
        socket.readAll(); // 버퍼 비우기
    }

    auto end = std::chrono::high_resolution_clock::now();
    
    // 전체 소요 시간 및 평균 계산
    auto total_us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
    double avgLatency_us = static_cast<double>(total_us) / pingPongCount;

    qDebug() << "==== QLocalSocket Latency Result ====";
    qDebug() << "Source File    :" << fileName;
    qDebug() << "Test Data Size :" << packetSize << "Byte";
    qDebug() << "Total Swaps    :" << pingPongCount << "Times";
    qDebug() << "Total Time     :" << total_us << "us";
    qDebug() << "Avg Time (1회) :" << avgLatency_us << "us";
    qDebug() << "======================================";

    socket.disconnectFromServer();
    if (socket.state() != QLocalSocket::UnconnectedState) {
        socket.waitForDisconnected(1000);
    }
}

CMakeLists.txt : googleTest 빌드 하기 위한 CMake 설정파일

더보기
cmake_minimum_required(VERSION 3.14)
project(unittests)

set(CMAKE_AUTOMOC ON)

# Dependencies
find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects)
add_subdirectory(C:/libs/googletest googletest-build)

# Imported Library
add_library(MyTestLib SHARED IMPORTED)
set(LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../build/Desktop_Qt_6_11_0_MinGW_64_bit-Release/release")
set_target_properties(MyTestLib PROPERTIES
    IMPORTED_IMPLIB "${LIB_PATH}/libMyTestLib.a"
    IMPORTED_LOCATION "${LIB_PATH}/MyTestLib.dll"
)

# Executable
file(GLOB TEST_FILES CONFIGURE_DEPENDS "*_test.cpp")

add_executable(unittests ${TEST_FILES})

target_include_directories(unittests PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/.."
)
target_link_libraries(unittests PRIVATE
    Qt6::Core Qt6::Network Qt6::RemoteObjects
    gtest gtest_main
    MyTestLib
)

qt6_add_repc_replicas(unittests
    RoInterface.rep
)

enable_testing()
add_test(NAME unittests COMMAND unittests)