LoginSignup
4
1

More than 1 year has passed since last update.

Harmony OSの高速通信の秘密(CoAP)

Last updated at Posted at 2021-06-16

ワイヤレス通信時によくある問題は通信の遅延です。

Harmony OSはスマホとカメラやテレビ、スピーカーなど様々な種類が異なるデバイスをワイヤレス接続することによって、スーパーデバイスを作り上げる次世代のIoT向けのOSです。OSレベルでどのように通信遅延を改善するか調べてみました。

分散ソフトウェアバスに使用されるプロトコル ― CoAP

現在多くのデバイスはRESTで通信しています。しかし、RESTのデータサイズが大きく、データ転送コストが高いというデメリットがあります。あまりIoTに向いていません。

Harmony OSではデバイス間の通信にCoAPを使っています。

Constrained Application Protocol ( CoAP )は制約のあるデバイス向けに特化したプロトコルであり、以下の特徴があります。

  1. TCPもUDPも使えます。
  2. HTTPとの互換性があり、同じくPOST、GET、PUT、DELETEメソッドが使えます。
  3. バイナリ形式を使うので(HTTPはテキスト形式です)、HTTPよりサイズが小さいです。(たとえば、CoAPのヘッダーは4バイト、HTTPのヘッダーは数十バイト)
  4. マルチキャストをサポートします。
  5. 使用するメモリと電力が少ないです。
  6. 非同期通信、再送処理をサポートします。

CoAPのセキュリティ

CoAPは転送データの保護にDTLSセキュリティ方式を使用します。

Harmony OSのCoAPのソースコード

Harmony OSのCoAPのソースコードはC言語で書かれており、下記のリンクから直接に見ることができます。

ヘッダー:https://gitee.com/openharmony/communication_softbus_lite/tree/master/discovery/coap/include
ソース:https://gitee.com/openharmony/communication_softbus_lite/tree/master/discovery/coap/source

CoAPのパケットの定義

image.png

Harmony OSでは次のようにCoAPパケットを定義します。

coap_def.h
#define COAP_MAX_OPTION 16

enum COAP_ProtocolTypeEnum {
    COAP_UDP = 0,
    COAP_TCP
};

// CoAPのヘッダーの定義
typedef struct {
    // VER
    unsigned int ver : 2;
    // タイプ
    unsigned int type : 2;
    // トークンの長さ
    unsigned int tokenLen : 4;
    // CoAP要求/応答コード
    unsigned int code : 8;
    union {
        unsigned short msgLen;
        // メッセージID
        unsigned short msgId;
    } varSection;
} COAP_Header;

// CoAPのトークンとペイロードの定義
typedef struct {
    const unsigned char *buffer;
    unsigned int len;
} COAP_Buffer;

// CoAPのオプションの定義
typedef struct {
    unsigned short num;
    const unsigned char *optionBuf;
    unsigned int len;
} COAP_Option;

// CoAPのパケットの定義
typedef struct {
    // UDPまたはTCPの使用
    enum COAP_ProtocolTypeEnum protocol;
    unsigned int len;

    // CoAPのヘッダー
    COAP_Header header;
    // トークン
    COAP_Buffer token;
    // オプション数
    unsigned char optionsNum;
    // オプション
    COAP_Option options[COAP_MAX_OPTION];
    // ペイロード
    COAP_Buffer payload;
} COAP_Packet;

coap_def.h

CoAPのメソッドタイプの定義

GET、POST、PUT、DELETEをサポートします。

coap_def.h
enum COAP_MethodTypeEnum {
    COAP_METHOD_GET = 1,
    COAP_METHOD_POST = 2,
    COAP_METHOD_PUT = 3,
    COAP_METHOD_DELETE = 4
};

coap_def.h

CoAPのメッセージタイプの定義

再送制御における到達確認の4つの定義をサポートします。

coap_def.h
enum COAP_MsgTypeEnum {
    // 確認必要
    COAP_TYPE_CON = 0,
    // 確認不要
    COAP_TYPE_NONCON = 1,
    // CONの到達確認
    COAP_TYPE_ACK = 2,
    // パケットの整合性が乱れたときに送る
    COAP_TYPE_RESET = 3
};

coap_def.h

CoAPのパケット転送

ソケットをたてます。5684ポートを使います。

coap_socket.h
#define COAP_DEFAULT_PORT      5684

int CoapInitSocket(void);
int CoapCreateUdpClient(const struct sockaddr_in *sockAddr);
int CoapCreateUdpServer(const struct sockaddr_in *sockAddr);

coap_socket.h

coap_socket.c
int CoapInitSocket(void)
{
    if (g_serverFd >= 0) {
        return NSTACKX_EOK;
    }
    struct sockaddr_in sockAddr;
    (void)memset_s(&sockAddr, sizeof(sockAddr), 0, sizeof(sockAddr));
    sockAddr.sin_port = htons(COAP_DEFAULT_PORT);
    g_serverFd = CoapCreateUdpServer(&sockAddr);
    if (g_serverFd < 0) {
        return NSTACKX_OVERFLOW;
    }
    COAP_SoftBusInitMsgId();
    return NSTACKX_EOK;
}

int CoapCreateUdpServer(const struct sockaddr_in *sockAddr)
{
    if (sockAddr == NULL) {
        return NSTACKX_EINVAL;
    }

    struct sockaddr_in localAddr;
    socklen_t len = sizeof(localAddr);
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        return NSTACKX_OVERFLOW;
    }

    (void)memset_s(&localAddr, sizeof(localAddr), 0, sizeof(localAddr));
    localAddr.sin_family = AF_INET;
    localAddr.sin_port = sockAddr->sin_port;
    if (sockAddr->sin_addr.s_addr != 0) {
        localAddr.sin_addr.s_addr = sockAddr->sin_addr.s_addr;
    } else {
        localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }

    if (bind(sockfd, (struct sockaddr *)&localAddr, len) == -1) {
        CloseSocket(&sockfd);
        return NSTACKX_EFAILED;
    }

    if (getsockname(sockfd, (struct sockaddr *)&localAddr, &len) == -1) {
        CloseSocket(&sockfd);
        return NSTACKX_EFAILED;
    }
    return sockfd;
}

int CoapCreateUdpClient(const struct sockaddr_in *sockAddr)
{
    if (sockAddr == NULL) {
        return NSTACKX_EFAILED;
    }

    struct sockaddr_in tmpAddr;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        return NSTACKX_EFAILED;
    }

    int ret = connect(sockfd, (struct sockaddr *)sockAddr, sizeof(struct sockaddr));
    if (ret != 0) {
        CloseSocket(&sockfd);
        return NSTACKX_EFAILED;
    }

    socklen_t srcAddrLen = sizeof(struct sockaddr_in);
    (void)memset_s(&tmpAddr, sizeof(tmpAddr), 0, sizeof(tmpAddr));
    ret = getsockname(sockfd, (struct sockaddr *)&tmpAddr, &srcAddrLen);
    if (ret != 0) {
        CloseSocket(&sockfd);
        return NSTACKX_EFAILED;
    }

    CloseSocket(&g_clientFd);
    g_clientFd = sockfd;

    return NSTACKX_EOK;
}

coap_socket.c

デバイス検索

coap_discover.c
int CoapInitDiscovery(void)
{
    int ret = CoapInitSocket();
    if (ret != NSTACKX_EOK) {
        SOFTBUS_PRINT("[DISCOVERY] Init socket fail\n");
        return ret;
    }

    ret = CoapInitWifiEvent();
    if (ret != NSTACKX_EOK) {
        SOFTBUS_PRINT("[DISCOVERY] Init wifi event fail\n");
        return ret;
    }
#if defined(__LITEOS_A__)
    ret = CreateQueryIpThread();
    if (ret != NSTACKX_EOK) {
        SOFTBUS_PRINT("[DISCOVERY] Init query Ip fail\n");
        return ret;
    }
#endif
    if (CreateMsgQueThread() != NSTACKX_EOK) {
        return NSTACKX_EFAILED;
    }

    // CoAP監視スレッドを作成
    return CreateCoapListenThread();
}

int CreateCoapListenThread(void)
{
    g_terminalFlag = 1;

#if defined(__LITEOS_M__) || defined(__LITEOS_RISCV__)
    if (g_coapTaskId != NULL) {
        return NSTACKX_EOK;
    }

    osThreadAttr_t attr;
    attr.name = "coap_listen_task";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    attr.priority = osPriorityNormal4; // COAP_DEFAULT_PRIO -> cmsis prio

    g_coapTaskId = osThreadNew((osThreadFunc_t)CoapReadHandle, NULL, &attr);
    if (g_coapTaskId == NULL) {
        g_terminalFlag = 0;
        SOFTBUS_PRINT("[DISCOVERY] create task fail\n");
        return NSTACKX_EFAILED;
    }
#else
    if (g_coapTaskId != -1) {
        return NSTACKX_EOK;
    }

    ThreadAttr attr = {"coap_listen_task", 0x800, 20, 0, 0};

    // CoAP読み込みスレッドを作成
    int error = CreateThread((Runnable)CoapReadHandle, NULL, &attr, (unsigned int *)&g_coapTaskId);
    if (error != 0) {
        g_terminalFlag = 0;
        SOFTBUS_PRINT("[DISCOVERY] create task fail\n");
        return NSTACKX_EFAILED;
    }
#endif
    return NSTACKX_EOK;
}

static void CoapReadHandle(unsigned int uwParam1, unsigned int uwParam2, unsigned int uwParam3, unsigned int uwParam4)
{
    (void)uwParam1;
    (void)uwParam2;
    (void)uwParam3;
    (void)uwParam4;
    int ret;
    fd_set readSet;
    int serverFd = GetCoapServerSocket();
    SOFTBUS_PRINT("[DISCOVERY] CoapReadHandle coin select begin\n");
    while (g_terminalFlag) {
        FD_ZERO(&readSet);
        FD_SET(serverFd, &readSet);
        ret = select(serverFd + 1, &readSet, NULL, NULL, NULL);
        if (ret > 0) {
            if (FD_ISSET(serverFd, &readSet)) {

                // 読み込みイベントの処理
                HandleReadEvent(serverFd);
            }
        } else {
            SOFTBUS_PRINT("[DISCOVERY]ret:%d,error:%d\n", ret, errno);
        }
    }
    SOFTBUS_PRINT("[DISCOVERY] CoapReadHandle exit\n");
}

static void HandleReadEvent(int fd)
{
    int socketFd = fd;
    unsigned char *recvBuffer = calloc(1, COAP_MAX_PDU_SIZE + 1);
    if (recvBuffer == NULL) {
        return;
    }
    ssize_t nRead;

    // ソケット内容の読み込み
    nRead = CoapSocketRecv(socketFd, recvBuffer, COAP_MAX_PDU_SIZE);
    if ((nRead == 0) || (nRead < 0 && errno != EAGAIN &&
        errno != EWOULDBLOCK && errno != EINTR)) {
        free(recvBuffer);
        return;
    }

    COAP_Packet decodePacket;
    (void)memset_s(&decodePacket, sizeof(COAP_Packet), 0, sizeof(COAP_Packet));
    decodePacket.protocol = COAP_UDP;

    // ソケット内容をCoAPパケットのオブジェクトに書き込む
    COAP_SoftBusDecode(&decodePacket, recvBuffer, nRead);

    // CoAPパケットを解析し、デバイスを検索する
    PostServiceDiscover(&decodePacket);
    free(recvBuffer);
}

coap_discover.c

参考

OpenHarmony:https://gitee.com/openharmony
CoAP:http://coap.technology/

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1