웹소켓 API

Oasis는 웹소켓 서버와 클라이언트를 지원합니다.

웹소켓 서버는 createHttpWsService API를 이용하여 HTTP 서버의 특정 URL에 붙혀서도 사용 가능합니다.

헤더 파일

OasisNet.h

WsEventDelegate 인터페이스

Oasis 웹소켓 서비스는 사용자가 WsEventDelegate 인터페이스로 부터 유도된 객체를 통하여 웹소켓 이벤트를 처리합니다.


class WsEventDelegate : public std::enable_shared_from_this<WsEventDelegate>
{
public:
    WsEventDelegate();
    virtual ~WsEventDelegate();

public: 
    virtual void onOpen(const WsEventRef &evt);
    virtual void onMessage(const WsEventRef &evt);
    virtual void onError(const WsEventRef &evt);
    virtual void onClose(const WsEventRef &evt);
};
void onOpen ( const WsEventRef & evt )
OasisNet.h
웹소켓이 열렸을 때 호출됩니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
void onMessage ( const WsEventRef & evt )
OasisNet.h
메세지를 수신하였을 때 호출됩니다.
매개변수
evt  웹소켓 이벤트 객체입니다.

아래는 Echo 서버의 경우 onMessage 처리 예입니다.

void EchoWsEventDelegate::onMessage(const WsEventRef &evt)  
{
  if(wsEventMessageType(evt) == kWsMessageText) {
    std::vector<uint8_t> data;
    if(wsEventData(evt, data) > 0) {
      std::string str = "Echo: ";
      str += std::string(data.data(), data.data()+data.size());
      wsSendText(evt, str.c_str());
    }
  }
}
void onError ( const WsEventRef & evt )
OasisNet.h
에러가 발생하였을 때 호출됩니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
void onClose ( const WsEventRef & evt )
OasisNet.h
웹소켓 연결이 닫혔을 때 호출됩니다.
매개변수
evt  웹소켓 이벤트 객체입니다.

WsEvent

ws_message_t wsEventMessageType ( const WsEventRef & evt )
OasisNet.h
메세지 타입을 얻습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
리턴값
메세지 타입을 리턴합니다.

메세지 타입을 아래와 같습니다.

ws_message_t 설명
kWsMessageUnknown 0 모르는 타입입니다.
kWsMessageText 1 텍스트 타입입니다.
kWsMessageBinary 2 이진 타입입니다.
kWsMessagePing 3 Ping 타입입니다.
kWsMessagePong 4 Pong (Ping에 대한 응답) 타입입니다.
uint16_t wsEventStatusCode ( const WsEventRef & evt )
OasisNet.h
매개변수
evt  웹소켓 이벤트 객체입니다.
리턴값
상태 코드를 리턴합니다.

상태 코드는 아래와 같습니다.

kWsStatusCode_상태코드
설명
NormalClosure 1000 정상 종료입니다.
EndPointGoingAway 1001 엔트 포인트 연결 오류 입니다. 서버가 닫히거나 브라우저 창이 닫힌 경우 발생합니다.
EndPointTerminatingConnectionDueToProtocolError 1002 프로토콜 오류로 연결 종료입니다.
EndPointTerminatingConnectionDueToUnknownOpcode 1003 모르는 Opcode로 인하여 데이터 처리 불가로 인한 연결 종료입니다.
NoStatusCodePresent 1005 닫기 프레임에 상태코드가 없습니다.
ConnectionClosedAbnormally 1006 종료 프레임이 없이 발생한 비정상적으로 연결이 종료되었습니다.
EndPointTerminatingConnectionDueToInconsistentData 1007 메세지 타입과 다른 데이터 수신으로 인한 연결 종료입니다. UTF-8 타입 메세지인데 UTF-8이 아닌 데이터를 수신한 경우 등입니다.
EndPointTerminatingConnectionDueToPolicy 1008 보안 등 일반상 이유로 인한 연결 종료입니다.
EndPointTerminatingConnectionDueToTooBigData 1009 처리하기 어려운 큰 데이터를 수신할 경우 발생하는 연결 종료입니다.
ClientTerminatingConnectionDueToLackOfExtensions 1010 서버단에서 확장성 확인 진행 과정에서 처리할 수 없어서 발생하는 클라이언트가 연결을 종료합니다.
ServerTerminatingConnectionDueToUnexpectedCondition 1011 요청을 처리할 수 없어서 서버가 연결을 종료합니다.
TlsHandshakeFailure 1015 TLS handshake 오류로 인한 연결 종료입니다.
ssize_t wsEventDataLength ( const WsEventRef & evt )
OasisNet.h
메시지 데이터 길이를 얻습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
리턴값
메세지 데이터 길이를 리턴합니다.
ssize_t wsEventText ( const WsEventRef & evt , std::string & str )
OasisNet.h
텍스트 메세지를 얻습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
str  OUT 텍스트 메시지를 리턴합니다.
리턴값
텍스트 길이입니다. 텍스트 유형이 아닐 경우 -1을 리턴합니다.
const uint8_t * wsEventData ( const WsEventRef & evt )
OasisNet.h
메세지 데이터를 얻습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
리턴값
메세지 데이터 포인터를 리턴합니다.
ssize_t wsEventData ( const WsEventRef & evt , std::vector<uint8_t> & data )
OasisNet.h
일반 메세지 데이터를 얻습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
data  OUT 데이터를 벡터에 저장 후 리턴합니다.
리턴값
데이터 크기를 리턴합니다.
WsRemoteRef wsEventGetRemote ( const WsEventRef & evt )
OasisNet.h
웹소켓에 연결된 원격 객체를 얻습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
리턴값
웹소켓 원격 객체를 리턴합니다.
ssize_t wsSendText ( const WsEventRef & evt , const char * text )
OasisNet.h
텍스트 메세지를 송신입니다. 바로 리턴이 되며 송신은 백그라운드에서 이루어집니다. 오류가 발생하면 onError 이벤트가 호출됩니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
text  전송할 문자열 포인터입니다.
리턴값
성공한 경우, 텍스트 메세지 길이를 리턴합니다. 실제 전송 결과는 이벤트 호출로 알 수 있습니다. 실패하면 -1을 리턴합니다.
ssize_t wsSendData ( const WsEventRef & evt , const uint8_t * data , size_t length )
OasisNet.h
데이터를 전송합니다. 바로 리턴이 되며 송신은 백그라운드에서 이루어집니다. 오류가 발생하면 onError 이벤트가 호출됩니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
data  전송할 데이터 포인터 입니다.
length  데이터 길이 입니다. 바이트 단위입니다.
리턴값
성공한 경우, 데이터 길이를 리턴합니다. 실제 전송 결과는 이벤트 호출로 알 수 있습니다. 실패하면 -1을 리턴합니다.
ssize_t wsPing ( const WsEventRef & evt , const uint8_t * data , size_t length )
OasisNet.h
PING 메세지를 전송합니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
data  PING 메세지에 추가될 사용자 정의 데이터 포인터입니다.
length  데이터 길이입니다. 바이트 단위입니다.
리턴값
성공하면 PING 데이터 크기를 리턴합니다. 실패하면 -1을 리턴합니다.
int32_t wsClose ( const WsEventRef & evt , uint16_t status_code = kWsStatusCode_NormalClosure , const uint8_t * reason_data = nullptr , size_t reason_data_length = 0 )
OasisNet.h
웹소켓 연결을 닫습니다.
매개변수
evt  웹소켓 이벤트 객체입니다.
status_code  연결 닫기에 사용될 상태코드입니다.
reason_data  설명 데이터입니다.
reason_data_length  설명 데이터 길이입니다.
리턴값
  • 0: 성공
  • -1: 실패

서버 함수

WsServerRef createWsServer ( key_value_map_t & parameters , const std::shared_ptr<WsEventDelegate> & delegate )
OasisNet.h
웹소켓 서버를 생성합니다.
매개변수
parameters  웹소켓 서버 생성에 필요한 key-value map입니다.
delegate  웹소켓에서 발생한 이벤트 처리를 위해 WsEventDelegate로 부터 유도된 사용자 정의 객체입니다.
리턴값
성공하면 웹소켓 서버 객체를 리턴합니다. 실패하면 nullptr을 리턴합니다.

웹소켓 서버 생성에 필요한 key-value map은 아래와 같습니다.

기본값
필수
설명
websocket-protocols
 
사용자가 정의한 프로토콜 이름입니다. 웹소켓 연결이 이 필드가 비어지 않으면 이 필드 값과 매치가 되어야 연결이 이루어집니다.
websocket-fragment-size-max
65535
 
최대 fragment 크기입니다.

TLS를 이용한 보안 웹소켓 서버를 생성할 경우, 아래 key-value map이 추가로 필요합니다.

기본값
필수
설명
tls-enabled
0
TLS 활성화 여부를 지정합니다. "1"이면 활성화되며, "0"이면 TLS를 사용하지 않습니다. 사용할 경우 인증서 경로등 키값이 유효해야 합니다.
tls-version
1.2
TLS 버전입니다. "1.2"를 입력합니다.
tls-client-verify
0
 
접속 클라이언트의 인증서를 검증할 지 여부를 지정합니다. "1" 이면 tls-certificate-request-authority-names 키에 등록된 이름만 연결 허용합니다.
tls-certificate-request-authority-names
 
\0xa 로 구분된 기관명(CN) 목록입니다. 일례로, "MediaStek-Root-CA\x0aMediaStek Inc." 와 같은 형식입니다.
ca-certs-file-path
 
\0xa 로 구분된 PEM 양식의 ROOT CA 인증서 파일 경로입니다. 일례로, "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem" 와 같은 형식입니다.
cert-file-path
RSA, ECDSA, DSS 등의 PEM 양식의 공개 인증서 파일 경로입니다.
pkey-file-path
PEM 양식의 개인키 파일 경로입니다.
int32_t startWsServer ( const WsServerRef & ws_server )
OasisNet.h
웹소켓 서버를 시작합니다.
매개변수
ws_server  웹소켓 서버 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t stopWsServer ( const WsServerRef & ws_server )
OasisNet.h
웹소켓 서버를 중지합니다.
매개변수
ws_server  웹소켓 서버 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t destroyWsServer ( WsServerRef & ws_server )
OasisNet.h
웹소켓 서버를 해제합니다.
매개변수
ws_server  IN OUT 웹소켓 서버 객체입니다. 해제 후 nullptr를 리턴합니다.
리턴값
  • 0: 성공
  • -1: 실패

클라이언트 함수

WsConnectionRef wsOpenConnection ( const char * url , key_value_map_t & parameters , const std::shared_ptr<WsEventDelegate> & delegate , int32_t timeout )
OasisNet.h
웹소켓 클라이언트 객체를 생성하고 웹소켓 서버에 연결합니다.
매개변수
url  웹소켓 서버 URL입니다. ws://<서버주소>[:<포트번호>] 또는 wss://<서버주소>[:<포트번호>] 양식입니다. wss는 TLS를 사용하는 연결입니다. 예를 들어, wss://echo.websocket.org 와 같은 양식입니다.
parameters  웹소켓 클라이언트 연결에 필요한 key-value map입니다.
delegate  웹소켓에서 발생한 이벤트 처리를 위해 WsEventDelegate로 부터 유도된 사용자 정의 객체입니다.
timeout  연결될 때까지 기다리는 최대 대기 시간입니다. 밀리초단위입니다. -1인 경우, 서버 연결 때까지 무한정 기다리지만 웹소켓 프로토콜 협상 과정 완료까지 기다리지 않습니다. 0인 경우, 서버 연결될 때과 웹소켓 연결이 완료될 때까지 기다립니다. delegate의 onOpen이나 onError 이벤트 콜백이 호출됩니다. 0보다 큰 값인 경우, 서버 연결될 때가지 지정한 시간만큼 대기합니다. 이후 웹소켓 연결이 완료될 때까지 지정한 시간 만큼 대기합니다. 시간이 경과되면 errno에 타임아웃(ETIMEDOUT) 오류를 리턴합니다. 연결 결과에 따라 delegate의 onOpen이나 onError 이벤트 콜백이 호출됩니다.
리턴값
성공할 경우, 웹소켓 클라이언트 객체를 리턴합니다. 실패할 경우 nullptr을 리턴합니다.

wsOpenConnection 에 필요한 key-value map 은 아래와 같습니다.

기본값
필수
설명
tls-disable-certificate-validation
0
 
서버의 인증서를 검증하지 않습니다.
tls-use-sni
0
 
서버 연결시 서버의 FQDN을 Hello Handshake에 포함시킵니다.
int32_t wsCloseConnection ( WsConnectionRef & connection , uint16_t status_code = kWsStatusCode_NormalClosure , const uint8_t * reason_data = nullptr , size_t reason_data_length = 0 )
OasisNet.h
웹소켓 연결을 닫습니다.
매개변수
connection  웹소켓 클라이언트 객체입니다.
status_code  연결 닫기에 사용될 상태코드입니다.
reason_data  설명 데이터입니다.
reason_data_length  설명 데이터 길이입니다.
리턴값
  • 0: 성공
  • -1: 실패
ssize_t wsSendText ( const WsConnectionRef & connection , const char * text )
OasisNet.h
웹소켓 서버에 텍스트 메세지를 전송합니다. 바로 리턴하며 백그라운드에서 실제 전송을 이루어집니다.
매개변수
connection  웹소켓 클라이언트 객체입니다.
text  전송할 문자열입니다.
리턴값
성공할 경우, 메세지 길이를 리턴합니다. 실패할 경우 -1을 리턴합니다.
ssize_t wsSendData ( const WsConnectionRef & connection , const uint8_t * data , size_t length )
OasisNet.h
웹소켓 서버에 일반 데이터 메세지를 전송합니다. 바로 리턴하며 백그라운드에서 실제 전송을 이루어집니다.
매개변수
connection  웹소켓 클라이언트 객체입니다.
data  데이터 포인터입니다.
length  데이터 길이입니다. 바이트 단위입니다.
리턴값
성공할 경우, 메세지 길이를 리턴합니다. 실패할 경우 -1을 리턴합니다.
ssize_t wsPing ( const WsConnectionRef & connection , const uint8_t * data , size_t length )
OasisNet.h
웹소켓 서버에 PING 메세지를 전송합니다.
매개변수
connection  웹소켓 클라이언트 객체입니다.
data  PING 메세지에 추가될 사용자 정의 데이터 포인터입니다.
length  데이터 길이입니다. 바이트 단위입니다.
리턴값

예제

아래는 웹소켓 서버 예입니다.

#include "OasisAPI.h"
#include "OasisLog.h"
#include "OasisWeb.h"

#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>

#define DLOG_THIS   0x00080000

#undef DLOG_FLAGS
#define  DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)

#undef DLOG_TAG
#define DLOG_TAG "WSS"

#define USE_RSA 1
#define USE_ECDSA 0
#define USE_DSS 0

using namespace oasis;

static bool continuing = true;


//for WebSocket upstream
class MyWsEventDelegate : public WsEventDelegate
{
public:
    MyWsEventDelegate() {}
    virtual ~MyWsEventDelegate() {}

public: 
    virtual void onOpen(const WsEventRef &evt) {
        DLOG(DLOG_THIS, "Open\n");
    }

    virtual void onMessage(const WsEventRef &evt)  {
        DLOG(DLOG_THIS, "Message\n");

        if(wsEventMessageType(evt) == kWsMessageText) {
            std::vector<uint8_t> data;
            if(wsEventData(evt, data) > 0) {
                std::string str(data.data(), data.data()+data.size());
                wsSendText(evt, str.c_str());
            }
        }
    }

    virtual void onError(const WsEventRef &evt) {
        DLOG(DLOG_THIS, "Error\n");
    }

    virtual void onClose(const WsEventRef &evt) {
        DLOG(DLOG_THIS, "Close\n");
    }
};


void cancel_handler(int signum)
{
    continuing = false;
}


int main(int argc, char* argv[])
{
    int32_t c, err;
    uint16_t port;
    std::thread t3;
    key_value_map_t parameters, fields;


    if(argc < 2) {
        printf("USAGE: %s <port number>\n", argv[0]);
        return 255;
    }

    port = (uint16_t)atoi(argv[1]);
    if(port == 0) {
        printf("USAGE: %s <port number>\n", argv[0]);
        return 255;
    }

    signal(SIGINT, cancel_handler);

    parameters["offs-disable"] = "1";
    if(oasis::initialize(parameters) < 0) {
        DLOG(DLOG_ERROR, "Oasis init failed\n");
        return -1;
    }

    parameters.clear();

    parameters["port"] = std::to_string(port);
    parameters["ca-certs-file-path"] = "/data/certs/RootCA.pem";

#if USE_RSA
        parameters["cert-file-path"] = "/data/certs/fullchain.pem";
        parameters["pkey-file-path"] = "/data/certs/privkey.pem";
#elif USE_ECDSA
        parameters["cert-file-path"] = "/data/certs/ec_fullchain.pem";
        parameters["pkey-file-path"] = "/data/certs/ec_pkey.pem";
#elif USE_DSS
        parameters["cert-file-path"] = "/data/certs/dsa_fullchain.pem";
        parameters["pkey-file-path"] = "/data/certs/dsa_pkey.pem";
#else
        #error "No certficates!"
#endif

    std::shared_ptr<MyWsEventDelegate> delegate = std::make_shared<MyWsEventDelegate>();

    WsServerRef server = createWsServer(parameters, delegate);
    if(!server) {
        DLOG(DLOG_ERROR, "failed to create a websocket server\n");
        return -1;
    }

    if(startWsServer(server) < 0) {
        DLOG(DLOG_ERROR, "start failed\n");
        goto done;
    }

    do {
    usleep(10000);
    } while (continuing);

    stopWsServer(server);

done:

    destroyWsServer(server);

    oasis::finalize();

    return 0;
}


아래는 웹소켓 클라이언트 예입니다.

#include "OasisAPI.h"
#include "OasisLog.h"
#include "OasisWeb.h"

#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>

#define DLOG_THIS   0x00080000

#undef DLOG_FLAGS
#define  DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)

#undef DLOG_TAG
#define DLOG_TAG "WSC"


using namespace oasis;


static TTYRaw tty_;

static bool continuing = true;
static bool ws_closed = false;


//for WebSocket upstream
class MyWsEventDelegate : public WsEventDelegate
{
public:
    MyWsEventDelegate() {}
    virtual ~MyWsEventDelegate() {}

public: 
    virtual void onOpen(const WsEventRef &evt) {
        DLOG(DLOG_THIS, "Open\n");
    }

    virtual void onMessage(const WsEventRef &evt)  {
        DLOG(DLOG_VERBOSE, "Message\n");
        if(wsEventMessageType(evt) == kWsMessageText) {
            std::string str;
            wsEventText(evt, str);
            DLOG0(DLOG_THIS, "==> %s\n", str.c_str());
        }
    }

    virtual void onError(const WsEventRef &evt) {
        DLOG(DLOG_THIS, "Error: %d\n", wsEventStatusCode(evt));
    }

    virtual void onClose(const WsEventRef &evt) {
        DLOG(DLOG_THIS, "Close: %d\n", wsEventStatusCode(evt));
        ws_closed = true;
    }
};

void cancel_handler(int signum)
{
    continuing = false;
}


int main(int argc, char* argv[])
{
    int32_t err;

    int32_t status_code;
    std::string reason_string;
    std::string content_type; 

    int32_t wait_count = 0;

    key_value_map_t parameters, fields;

    std::string url = "wss://echo.websocket.org/";

    if(argc >= 2) {
        url = argv[1];
    }

    signal(SIGINT, cancel_handler);

    parameters["offs-disable"] = "1";
    if(oasis::initialize(parameters) < 0) {
        DLOG(DLOG_ERROR, "Oasis init failed\n");
        return -1;
    }

    parameters.clear();

    parameters["ca-certs-file-path"] = "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem";
    parameters["cert-file-path"] = "/data/certs/client_fullchain.pem";
    parameters["pkey-file-path"] = "/data/certs/client_privkey.pem";

    parameters["tls-disable-certificate-validation"] = "1";
    parameters["tls-use-sni"] = "1";

    //websocket-protocols
    parameters["websocket-fragment-size-max"] = std::to_string(8192);

    WsConnectionRef conn;
    char buffer[512];

    std::shared_ptr<MyWsEventDelegate> delegate = std::make_shared<MyWsEventDelegate>();

    conn = wsOpenConnection(url.c_str(), parameters, delegate, 10000);
    if(conn) {

        DLOG(DLOG_THIS, "connected.\n");

        strpcy(buffer, "hello, world!");
        wsSendText(conn, (const char*)buffer);

        DLOG(DLOG_THIS, "closing...\n");
        wsCloseConnection(conn);

        //need sometime to be closed.
        int32_t max_wait = 20; //2 seconds
        do {
            usleep(10000);
        } while(ws_closed == false && max_wait-- > 0);

    }

    conn = nullptr;

    oasis::finalize();

    return 0;
}