2018年9月30日日曜日

WindowsのSocket(TCP)

WindowsのSocketはAPIに変遷の歴史があり、ちょっと苦手です。(最初に作った時は、まだWinSock(1)のころで、Linuxとかなり似たAPIでした。その後、WinSock2になり、M$は.NetやMFCを推奨してきて嫌になります)
QtのSocketクラスを使えば、その辺をラップしてくれて楽ができます。ちょっと試しにServerのconsoleプログラムを作ってみました。telnet等をclientにして簡単に実験ができます。

proファイルですが、networkの追加を忘れずに。

MyTcpServer.pro
QT -= gui
QT       += core
QT       += network

CONFIG += c++11 console
CONFIG -= app_bundle

mainは以下の通りです。

main.cpp
#include <qcoreapplication>
#include "mytcpserver.h"

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

    MyTcpServer server;

    return a.exec();
}

MyTcpServerクラスのヘッダは最低限の関数だけ定義します。

mytcpserver.h
#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <qobject>
#include <qtcpsocket>
#include <qtcpserver>
#include <qdebug&gt

class MyTcpServer : public QObject
{
    Q_OBJECT
public:
    explicit MyTcpServer(QObject *parent = 0);

signals:

public slots:
    void newConnection();
 void readData();
 void socketError(QAbstractSocket::SocketError);
 void deleteLater();

private:
    QTcpServer *server;
 QTcpSocket *socket;
};

#endif // MYTCPSERVER_H

本体です。connectして文字列を送ったら、後は受信するだけのものです。相手がcloseしたり、勝手に切ったり、エラーが起きたらServer側はcloseするようにしてあります。また、"bye"を受信したらやはりcloseします。(windowsだと、受信文字列の最後にCR/LFが含まれますが、Linux等では行末記号は含まれてきません。)

mytcpserver.cpp
// mytcpserver.cpp

#include "mytcpserver.h"
#include <qdatastream .h>

MyTcpServer::MyTcpServer(QObject *parent) :
    QObject(parent)
{
    server = new QTcpServer(this);

    // 誰かがconnectしてきたら、signalを出してnewConnection()をCall
    connect(server, SIGNAL(newConnection()),
            this, SLOT(newConnection()));

    if(!server->listen(QHostAddress::Any, 9999))
    {
        qDebug() << "Server could not start";
    }
    else
    {
        qDebug() << "Server started!";
    }
}

void MyTcpServer::newConnection()
{
    // socketのインスタンスを生成
    socket = server->nextPendingConnection();

 qDebug() << " connect";

 // 切断した時の処理を定義
 connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));

 // 受信した時の処理を定義
 connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
 // エラー処理の定義
 connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
 // 切断したときの処理としてSLOT関数を設定する。
 connect(socket, SIGNAL(disconnected()), this, SLOT(deleteLater()));

 // クライアントのIPアドレスを取得
 QString ippaddr = socket->localAddress().toString();

 // 送信データをstreamとして定義
 QDataStream out(socket);
 out.setVersion(QDataStream::Qt_5_0);

 out << "Hello client\r\n";
 
}

void MyTcpServer::readData()
{
 QString s;

 // 受信データを」streamとして定義
 //QDataStream in(socket);
 //in.setVersion(QDataStream::Qt_5_11);

 //in >> s;

 s = socket->readAll();

 qDebug() << "receive : " << s << "( " << s.size() << " ) ";

 if ("bye\r\n" == s)
 {
  qDebug() << "close process";

  //deleteLater();
  emit socket->disconnected();
 }

}

void MyTcpServer::socketError(QAbstractSocket::SocketError )
{
 qDebug() << " socket error!";

 socket->close();
}

void MyTcpServer::deleteLater()
{
 qDebug() << " socket disconnected";

 socket->close();
}

さて、ここでこのサンプルを作った本題に入ります。前からLinuxでSocket(TCP)のプログラムで悩んでいたのが、通信しているプログラムの片方が、socketをつないだまま、異常終了すると、しばらくその時のportが使えなくなる現象に困っていました。一方、昔winsockを見ていた時、windowsのAPIには、portを強制的にconnectするというフラグがあり、どう違うのか疑問に思っていました。
このサンプルプログラムで、windowsとLinux(or Mac)での挙動の違いを調べてみました。

(1)通信中にServerを強制終了すると、Linux(Mac)ではやはり当該portは使えなくなります。(already useのエラーメッセージがでます)しかし、windowsは何事もなかったようにconnectできます。
(2)Linux(Mac)でも、client側が強制終了しても、サンプルのServerでは相手との通信が切れたことを検知して、portをcloseするようにしてあるので、再度clientは接続できます。(これまでLinuxでうまくいかなかったのは、この処理を入れてなかったことに気づきました)

Windowsは「楽観的」というか「危険」というか、考え方がかなり違います。

2018年9月9日日曜日

Raspberrry Pi3にOpenSUSE(64bit)をインストール

Raspbery Pi3のRaspbianでしばらく遊んでいましたが、折角CPUが64bitアーキテクチャになったというのに、Raspbianは32bitのままというのが寂しくなってきました。折角なので、64bitのOSを入れましょう。

1.適当なOSを探す
ぐぐってみるといくつかでてきました。ただ個人的にやられていそうな方は今後の継続性が怖いのと、一番例が多く見つかったOpenSUSEにすることにしました。(コンサバにいきます)
OpenSUSEのHPにいくと、頻繁に更新がされているらしく、DLしたいものをクリックしても、「リンクがありません」と言われたりします。しかしそこはしょせん単なるFTPサーバーみたいなものですから、「リンクがありません」と言われたURLから、目的としているファイル名だけ削除して、当該ディレクトリ内一覧を表示させ、より新しいバージョンが入っているのを見つけてDLしてきました。私がDLしてきたのは、Tumbleweed版のXFCE版です。(LXQt版というのもありましたが、どちらがいいかはまた時間があったら試してみます)
注意しないといけないのは、多数のパッケージが入っていますが、その中にRapberrry Pi以外のARMベースのものも混じっているらしいということです。ファイル名の中にきちんと"raspberry"の文字が入っているのをDLしてきましょう。(最初、これでなんでbootしないんだと、何回か失敗しました)

2.DLしたOSイメージをSDカードに焼く
この辺りは他の人が多数解説していますので、省きます。一つだけ注意があります。他の方はもう当たり前だと思って、書いていてくれませんでしたが、SDカードの初期化は専用アプリの"SD Card Formatter"を使いましょう。Windows標準でフォーマットさせると、32GBのSDがなんかちっこい2つのドライブにフォーマットされて、後からのOS image書き込みがうまくいきません。(Windowsは標準のFATでフォーマットしようとするんで、最近の32GB等の様な大容量には対応できていません)
2年くらい前にRaspberry Pi2を初めていじったときは、一生懸命調べてやってたんですが、すっかり忘れていました。

3.OpenSUSEの起動
後はもう特に注意点はありません。最初の起動に時間がかかるのはいつものことです。(設定諸々やっていいたり、SD内の使える領域を全域にしたりの作業をしていますから)

4.Wi-Fiの設定
さて、ここからが今回の本題です。Raspberry Pi3には折角内蔵のWi-Fiがついています。(Broadcom BCM43438のチップがついています)
OpenSUSEの記事を色々みていると、みなさんかなり苦労しているようです。とりあえず標準的な方法として、設定ツール"YaST"→"Network Setting"から試してみます。
画面の表示から、とりあえず最初の起動で、BCM43430 WLAN CArdの文字がでてきています。(チップの認識に問題があるのか、それとも製造ロットで違いがでた?)
これならYaSTで設定できるかと思い作業を進めていきましたが、どうもうまくいきません。YaSTで設定していて、最後のOK押すと画面に右下にメッセージで「無線ネットワークが認識されました」みたいなものが出てくるんで、成功したかと思うとだめ。ifconfigコマンドで確認してみると、wlan0は認識していますが、DHCPのアドレスが割り当てられていません。
そもそも、YaSTのwi-fiのSSIDと暗号key設定のところのIFが分かりにくいんですよね。keyのnegotiationで失敗している可能性が大きいです。
そこで今度は別のHPをぐぐると、以下のファイルにSSIDとkeyを登録しろとの情報が。

   /etc/wpa_supplicant/wpa_supplicant.conf

ここのファイルの最後に以下を追加しろとありました。(でもYaSTはここに何も書いていてくれないんですが…不安がよぎります)

network={
    ssid="調べたSSID"
    psk="WiFiのパスワード"
}

まあダメ元でと試してみましたが、やはりkeyのnegotiationに失敗とのログが残ります。少し考えて、上記の例との違いに気づきました。例ではssidやpskの前にインデントがしてあります。私は何も考えずにTabで行ってしまいましたが、もしかしてTabはまずかった?Tabを削除してマシンを再起動し、やっとWi-Fiがつながりました。

5.総論
OpenSUSEと言えば結構有名どころなんですが、まだ落ち着いているとはいいがたい状況ですね。特に今回のWi-Fiまわりは皆さん苦労しているようで、他のHPのデータを見るたびにbroadcomのライブラリ名称や個数が変わってきたりで苦労している後が見えます。(このあたりのドライバ回りまで64bitにするのが大変なので、本家がなかなか対応できないんでしょうが)

後、OpenSUSEとRaspbianを使った感想ですが、一言「OpenSUSE遅すぎ!」もうイライラしてくるレベルです。Raspbianの時は、最初のPi2の頃からは大幅に改善されていて「使い物になるじゃん」と感じたのとは大違いです。Graphic回りがまだまだ作りこみに問題があるのかわかりませんが、もう少し様子見した方がよさげです。