簡単なSocke通信プログラムでSNAP間でデータをやり取りしてみる 

#SNAPパッケージとは
アプリケーション・ソフトウェアとその依存パッケージをバンドルしたパッケージになります。SNAPパッケージは[SnapStore](https://snapcraft.io/store)を通して公開されており、様々な既存アプリケーションのスナップパッケージが登録されています。


#環境
Ubuntu 20.04がインストールされたラップトップでテストをしています。デフォルトでインストールされていない場合は、[リファレンス](https://snapcraft.io/docs/installing-snapd)にSNAPをサポートするDistroと各Distroでのスナップの使い方が書いてあります。またサンプルプログラムは[こちらのページ](http://bkmts.xsrv.jp/c-socket/)を参考にさせて頂きました。


#SNAPパッケージ作成の流れ
SNAPパッケージを作成するには大きく2つのやり方があります。

* Dumpプラグインを利用して、バイナリファイルを直接パッケージ化する
* 他のプラグインを利用して、ソースファイルをビルドしてパッケージ化する

今回はcmakeプラグインを利用してソースコードをビルドし、シンプルなサーバー、クライアンとプログラムをSNAP化してみようと思います。

#ClientとServerプログラムを作る
ローカルホストに接続して、データを送るクライアントプログラムを作成します。
<details><summary>Serverコード</summary><div>

```.rb
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "sample_data_type.h"


int main()
{
    unsigned short port = PORT_NUM;
    int server_soket = 0;
    int client_soket = 0;

    struct sockaddr_in server_address = {0};
    struct sockaddr_in client_address = {0};
    unsigned int client_addressSize = sizeof(client_address);
    int ret_val = 0;

    //Configure sockaddr_in
    server_address.sin_family = AF_INET; 
    server_address.sin_port = htons(port); 
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //create a socket
    errno = 0;
    server_soket = socket(AF_INET, SOCK_STREAM, 0);
    if(server_soket == -1){
        printf("socket() failed. errno : %d\n", errno);
        return -1;
    }

    // bind socket
    errno = 0;
    ret_val = bind(server_soket, (struct sockaddr*) &server_address, sizeof(server_address));
    if(ret_val == -1){
        printf("bind() failed. errno : %d\n", errno);
        return -1;
    }


    // Connect 
    errno = 0;
    ret_val = listen(server_soket, 1); 
    if(ret_val == -1){
        printf("listen() failed. errno : %d\n", errno);
        return -1;
    }

    //wait for connection
    printf("Waiting for connection\n");
    int retry_count = 0;
    while(retry_count < 3){
        errno = 0;
        client_soket = accept(server_soket, (struct sockaddr*)&client_address, &client_addressSize);
        
        if(client_soket == -1){
             printf("accept() failed. count : %d errno : %d\n", retry_count, errno);
            ++retry_count;
        }else{
            break;
        }

        if(retry_count < 3){
            sleep(1);
        }
    }

    if(client_soket == -1){
        return -1;
    }

    printf("Connected from localhost %s\n", inet_ntoa(client_address.sin_addr));

    //Receive packet
    while(1){
        int byte_received = 0;
        SampleDataType received_data = {0};

        errno = 0;
        byte_received = recv(client_soket, &received_data, sizeof(received_data), 0);
        if(byte_received == 0){
            break;
        }

        if(byte_received == -1){
            printf("recv() failed. errno : %d\n", errno);
            break;
        }

        printf("received: %s\n",
                received_data.text);
    }

    // Close socket
    errno = 0;
    int status = close(client_soket);
    if(status == -1){
        printf("Send: close() failed. errno : %d\n", errno);
    }

    //Close socket for receiving data
    errno = 0;
    status = close(server_soket);
    if(status == -1){
        printf("Receive: close() failed. errno : %d\n", errno);
    }

    return 0;
}
```

</div></details>
ローカルホストに接続して、データを送るクライアントプログラムを作成します。
<details><summary>Clientコード</summary><div>

```.rb
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "sample_data_type.h"


int main()
{
    char dest_ip[80] = {0};
    unsigned short port = PORT_NUM;
    int client_socket = 0;
    struct sockaddr_in client_addr = {0};

    printf("Connect to local host.");
    strncpy(dest_ip, "127.0.0.1", sizeof(dest_ip));


    // configure socket_addr
    client_addr.sin_family = AF_INET; 
    client_addr.sin_port = htons(port); 
    client_addr.sin_addr.s_addr = inet_addr(dest_ip);
    
    // Create a socket
    errno = 0;
    client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if(client_socket == -1){
        printf("socket() failed. errno : %d\n", errno);
        return -1;
    }

    //Connect to socket
    printf("Connect to localhost: \n");
    int ret_val = connect(client_socket, (struct sockaddr*) &client_addr, sizeof(client_addr));
    if(ret_val == -1){
        printf("connect() failed. errno : %d\n", errno);
        return -1;
    }


    // Send a packet
    for(int i=0; i<10; i++) {
        int sent_byte = 0;
        SampleDataType send_data = {0};
        char *text = "Send Sample message";
        sprintf(send_data.text, "%s", text);
    
        printf("Sending data to localhost\n");
        errno = 0;
        ret_val = send(client_socket, &send_data, sizeof(send_data), 0);
        if(sent_byte == -1){
            printf("send() failed. errno : %d\n", errno);
        }

        sleep(1);
    }

    // Close socket
    int status = close(client_socket);
    if(status == -1){
        printf("close() failed. errno : %d\n", errno);
        return -1;
    }

    return 0;
}


```

</div></details>
<details><summary>sample_data_type.h</summary><div>

```
#ifndef SAMPLE_DATA_TYPE_H_
#define SAMPLE_DATA_TYPE_H_

// データ
typedef struct{
    char text[256];
} SampleDataType;

// ポート番号
#define PORT_NUM 9000

#endif // SAMPLE_DATA_TYPE_H_
```
</div></details>

#Snapcraftを使用してSNAPパッケージを作成する
前のステップで作成したサーバープログラムとクライアントプログラムをそれぞれ
別々のパッケージとして作成してみます。

SNAPパッケージの作成にはsnapcraftというSNAPを使用しするため、まずはsnapcraft
SNAPを以下のコマンドでインストールします。またスナップパッケージを作成する際に
デフォルトでMultipass（VM）を使用するのでこちらもインストールします。

```snapcraftをインストール
snap　install snapcraft　--classic
sudo snap install multipass
```
####snapcraft.yamlを作成する
snapcraftはsnapcrafy.yamlに記載された情報をもとにパッケージを作成するので、
snapcrafy.yamlを作成、編集していきます。

```snapcraft.yamlの作成
snapcraft init

Created snap/snapcraft.yaml.
Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more information about the snapcraft.yaml format.
```
snapcraft initを実行したパスの直下にsnapというフォルダにsnapcraft.yamlが作成されます。
クライアント、サーバーの別々のパッケージを作成するので、それぞれのフォルダでsnapcraft init
を実行しsnapcraft.yamlを作成します。　

```フォルダ構成
tree -d
.
├── client
│   ├── snap
│   └── src
└── server
    ├── snap
    └── src
```

####snapcraft.yamlの編集
作成されたYAMLファイルを編集します。
[snapcraft.yamlファイルのフォーマッとのリファレンス](https://snapcraft.io/docs/snapcraft-yaml-reference)
ここでは必要な部分のみを変更しました。
name: 
SNAPパッケージの名前。SnapStoreに登録する場合は、ストア内でSNAPパッケージを一意に設定する必要があります。同じ名前のパッケージは複数存在できません。例えばhello-worldというパッケージはすでにストアに
存在するので、hello-worldというSNAPパッケージを作ることはできません。

base:
SNAPパッケージに実行環境を提供するベースsnap。

version: 
作成するSANPパッケージのバージョン

summary: 
ストアに表示されるSNAPパッケージのサマリ

description: |
SNAPパッケージの詳細

parts:
SNAPパッケージを構成するパーツ。このサブフィールドで、ビルドの仕方やソースファイルの場所、ビルドに必要なパッケージなどを指定します。ここではそれぞれのプログラムのソースファイルの位置を指定します。

apps:
SNAPパッケージに含まれる実行ファイルを指定します。サブフィールドで、SANPパッケージに含まれるアプリ、必要なインターフェースを指定します。ここではビルドされたバイナリファイルを指定し、ソケット通信を行うのに必要なインターフェース（network-bind)を指定しています。


<details><summary>Client:snapcraft.yaml</summary><div>

```
name: snapclient # you probably want to 'snapcraft register <name>'
base: core18 # the base snap is the execution environment for this snap
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary:  Demo snap for snap communication
description: |
  snap client. Sends pre-defined test message to snap server.

grade: stable # must be 'stable' to release into candidate/stable channels
confinement: strict # use 'strict' once you have the right plugs and slots

parts: 
  snapclient:
    # See 'snapcraft plugins'
    plugin: cmake
    source: src
    build-packages:
      - g++
      - make


apps:
  snapclient:
    command: tcp_client
    plugs:
      - network
```
</div></details>
<details><summary>Server:snapcraft.yaml</summary><div>

```
name: snapserver # you probably want to 'snapcraft register <name>'
base: core18 # the base snap is the execution environment for this snap
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary:  Demo snap for snap communication
description: |
  This is my-snap's description. You have a paragraph or two to tell the
  most important story about your snap. Keep it under 100 words though,
  we live in tweetspace and your description wants to look good in the snap
  store.

grade: devel # must be 'stable' to release into candidate/stable channels
confinement: strict # use 'strict' once you have the right plugs and slots

parts:
  snapserver:
    # See 'snapcraft plugins'
    plugin: cmake
    source: src
    build-packages:
      - g++
      - make
apps:
  snapserver:
    command: tcp_server
    plugs:
      - network-bind

```
</div></details>

####パッケージ化を実行する
パッケージを作成するにはClientあるいはServerフォルダ内で、snapcraftコマンドを実行します。

```
Stealth-9SE:~/study/simple_client_server/server$ snapcraft
Launching a VM.
Running with 'sudo' may cause permission errors and is discouraged. Use 'sudo' when cleaning.
snapd is not logged in, snap install commands will use sudo
snap "core18" has no updates available
Skipping pull snapserver (already ran)
Skipping build snapserver (already ran)
Skipping stage snapserver (already ran)
Skipping prime snapserver (already ran)
The command 'tcp_server' for 'tcp_server' was resolved to 'bin/tcp_server'.
The command 'tcp_server' has been changed to 'bin/tcp_server'.
The requested action has already been taken. Consider
specifying parts, or clean the steps you want to run again.
Snapping |                                                                                                                                                                                         
Snapped snapserver_0.1_amd64.snap
masahiro@masahiro-GS65-Stealth-9SE:~/study/simple_client_server/server$ snapcraft clean
masahiro@masahiro-GS65-Stealth-9SE:~/study/simple_client_server/server$ snapcraft
Launching a VM.
Launched: snapcraft-snapserver                                                  
Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]     
Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
Get:3 http://security.ubuntu.com/ubuntu bionic-security/multiverse Sources [3192 B]
Get:4 http://security.ubuntu.com/ubuntu bionic-security/restricted Sources [7828 B]
Get:5 http://security.ubuntu.com/ubuntu bionic-security/universe Sources [173 kB]
Get:6 http://security.ubuntu.com/ubuntu bionic-security/main Sources [166 kB]       
Get:7 http://security.ubuntu.com/ubuntu bionic-security/main amd64 Packages [849 kB]          
Get:8 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]                          
（略）
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /root/parts/snapserver/build
cmake --build . -- -j2
Scanning dependencies of target tcp_server
[ 50%] Building C object CMakeFiles/tcp_server.dir/tcp_server.c.o
[100%] Linking C executable tcp_server
[100%] Built target tcp_server
cmake --build . --target install
[100%] Built target tcp_server
Install the project...
-- Install configuration: ""
-- Installing: /root/parts/snapserver/install/bin/tcp_server
Staging snapserver 
Priming snapserver 
The command 'tcp_server' for 'tcp_server' was resolved to 'bin/tcp_server'.
The command 'tcp_server' has been changed to 'bin/tcp_server'.
Snapping |                                                                                                                                                                                         
Snapped snapserver_0.1_amd64.snap
```

これで SNAPパッケージ（snapserver_0.1_amd64.snap）が作成されました。
ClinetSNAPパッケージも同様に作成できます。

#作成したSNAPパッケージをインストールして実行する
ローカル上で作成されたSNAPパッケージをインストールするには"--dangerous"オプションが必要になります。


```
:~/study/simple_client_server/server$ snap install snapserver_0.1_amd64.snap --dangerous
snapserver 0.1 installed
```

同様にクライアントSNAPパッケージもインストールして実行してみると、異なるSNAPパッケージの間
でデータのやり取りが出来ていることが確認できました。

```
:~/study/simple_client_server/server$ snapserver 
Waiting for connection
Connected from localhost 127.0.0.1
received: Send Sample message
received: Send Sample message
received: Send Sample message
received: Send Sample message
received: Send Sample message
received: Send Sample message
```

```
:~/study/simple_client_server/client$ snapclient
Connect to local host.Connect to localhost: 
Sending data to localhost
Sending data to localhost
Sending data to localhost
Sending data to localhost
Sending data to localhost
Sending data to localhost
Sending data to localhost

```

中川　雅裕　Canonical Japan (Iot Field Engineer)
Ubuntu Blog: https://jp.ubuntu.com/blog
Canonical Japan: https://jp.ubuntu.com/
Email: masahiro.nakagawa@canonical.com
Email:info-jp@canonical.com
Phone: 03-6205-3075