예제 소스
SGF 기보로 학습하는 바둑 LLM 인공지능 · 파일 169개
tcpvlmclient.h
#pragma once
#include <QAtomicInt>
#include <QByteArray>
#include <QImage>
#include <QObject>
#include <QString>
class QTcpSocket;
/**
* 19×19 바둑판 PNG + 361 바이트 격자 상태를 TCP 로 보내 VLM 서버의 좌표 응답을 받습니다.
* 한 번 연결한 소켓을 게임/앱 수명 동안 유지(persistent)합니다.
*
* 프로토콜 (GO19):
* 요청: "GO19" | u32_be body_len | u8 size | u8 ai_side(1=Black,2=White) |
* u32_be png_len | png bytes | size*size board bytes (0/1/2) |
* u32_be game_epoch (선택, 있으면 대국이 바뀔 때마다 증가 — 서버 온라인 학습 트리거) |
* [선택] u8 n_reject | n×(u8 row, u8 col) — settled/자충으로 거부한 좌표
* 응답: "RPLY" | u8 flag
* flag=1: u8 row, u8 col (row=255 && col=255 → 패스)
* flag=0: u16_be err_len, utf8 err
* PING/PONG: 4바이트 텍스트.
*/
namespace TcpVlmClient {
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
~Worker() override;
bool isBusy() const;
bool isConnected() const;
public slots:
void doPing(QString host, quint16 port, int timeoutMs);
/// 인게임 영구 소켓을 건드리지 않는 일회성 PING.
/// 여러 서버를 독립적으로 검사할 수 있도록 tag(예: "1","2") 로 결과 라우팅.
/// timeoutMs 안에 PING/PONG 이 끝나야 성공.
void doPingProbe(QString tag, QString host, quint16 port, int timeoutMs);
/// 한 수 요청. row=255 && col=255 면 패스 권고.
/// rejectCoords: 서버가 다시 고르지 말 좌표 (최대 64). 빈 배열이면 미전송(구형 호환).
void requestMove(QImage boardImage, QByteArray boardPacked, int boardSize, int aiSide,
QString host, quint16 port, int timeoutMs, quint32 gameEpoch,
QByteArray rejectCoords = QByteArray());
void cancel();
void closeConnection();
/// 앱 종료 시 메인 스레드에서 BlockingQueuedConnection 으로 호출.
/// 소켓 abort 로 readBytes 의 QEventLoop 에서 빠져나와 스레드를 안전히 quit 할 수 있게 함.
void shutdownForAppExit();
signals:
/// row/col == 255,255 일 때 isPass=true.
void moveReady(bool success, int row, int col, bool isPass, QString errorMessage);
void pingReady(bool success, QString errorMessage);
/// doPingProbe 결과. endpoint="host:port", latencyMs 는 성공 시 실측 왕복(접속+PONG).
void pingProbeReady(QString tag, bool success, QString endpoint, int latencyMs,
QString errorMessage);
void connectionStateChanged(bool connected, QString endpoint);
private:
struct Conn {
QTcpSocket *sock = nullptr;
QString host;
quint16 port = 0;
quint64 lastUseSeq = 0; ///< 간단 LRU (연결 2개 유지)
};
/// host:port 에 대한 소켓을 얻고(있으면 재사용), 없으면 생성.
/// 본 클라이언트는 VlmVsVlm 처럼 두 엔드포인트를 번갈아 쓰므로
/// 소켓 2개까지는 동시에 유지한다.
bool ensureConnected(QTcpSocket **outSock, const QString &host, quint16 port, int timeoutMs,
QString *errorOut);
bool readBytes(QTcpSocket *sock, QByteArray *dst, qint64 nbytes, int deadlineMs);
bool writeAll(QTcpSocket *sock, const QByteArray &data, int timeoutMs);
void closeAllSocketsLocked(const QString &reason);
void emitConnState();
Conn m_conns[2];
quint64 m_useSeq = 0;
QAtomicInt m_cancel{0};
QAtomicInt m_busy{0};
};
} // namespace TcpVlmClient