ラベル ネットワーク の投稿を表示しています。 すべての投稿を表示
ラベル ネットワーク の投稿を表示しています。 すべての投稿を表示

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は「楽観的」というか「危険」というか、考え方がかなり違います。

2017年5月18日木曜日

【雑談】NTTの光モデムがやばい

自分が何時、家のネット回線をADSLから光に変えたか忘れてしまいましたが、昔からずっとNTTです。当時の光は3台装置が必要だったんです。光-電気変換器(正式な名称があるんだろうけど忘れました^^;)、CTU、最後にモデムです。この当時は1portしかEtherがなく、複数台のPCで使いたい場合はその1portをブリッジに繋げるしかありませんでした。(本当はルータにして、そこでproxy噛ませたかったんですが、NTTはDHCPの機能はNTTが持つので、ブリッジしか許してくれませんでした。)

1、2回雷が誘雷したのか壊れて交換しましたが、数年前CTUとモデムを一体型にした新型に交換してくれと電話がきたことがありました。その時は交換が面倒だったので断りましたが、数ヶ月前NTTの携帯と回線の契約をまとめると少し料金がお得になりますという甘言に負けて、契約したらその一体型のモデムに無理やり交換させられてしまいました。開通の設定が少し手間がかかりましたが無事使える様にしました。この新しいモデムですが、無線LANのカードを別売りで追加することはできるは、Etherも4port持ってます。さすがに10年以上前みたいに一家にPCが1台だけだとは思わなくなった様です。
ただLANの配線をし直すのも大変なので、従来のブリッジからの1portとメインのPCをモデムにつなぎました。(ブリッジ経由より、少しは速度が有利なんじゃないかという軽い気持ちでした)

ここからが本題なんですが、家ではNASを使っていて、半年〜1年に1回くらいUSB-HDDにバックアップをとってます。半日くらいかかりますが、まあしかたありません。ところが、今回バックアップをメインのPCにUSB-HDDをつないで行うと、なんかむちゃくちゃ遅い…。しかもよくNASからレスがなくなり、コピーできませんと言ってきます。どうしたのか、色々調べました。NAS周りのLAN回線がどこか接触不良した様子もありませんでした。しかも別の部屋(そこまでWiFiが届かないので、別途回線を通して別のWiFi親機を設置しています)でタブレット等でネットに繋ごうとすると、何時もは何も問題が起きないのに今日に限って全然つながりません。しばらく試していて、どうも別の部屋でタブレットを使うタイミングで、メインPCとNASの接続がおかしくなる様です。

ここまできてやっと推測ができました。メインPCはNTTのモデムに直結しています。4portあるうち、1portの通信量が増えて、他のportからのDHCPの要求に応えれなくなった様です。(能力がこのモデム足りなさすぎ)NASもDHCP機能を使って家庭内LANに接続してますから、時々DHCPアドレスの更新要求を出していますが、その時に別の部屋でタブレットがアクセスしようとすると、両方のDHCP応答が時間内にできない状態になっていたようです。メインPCをNTTのモデムに直結はやめ、NTTのモデムにはブリッジだけつなぎ、あとはブリッジ経由にしました。そうしたところ、NASのバックアップの速度が速くなり、別の部屋でのタブレット使用も問題なくなりました。

しかし、このNTTのモデム一体どんな回路構造になっているのやら。4portつけても、どれかが大量にデータ転送すると(まあ普通の一般家庭では、まずそんなことしないかもしれませんが)、他のportの機能の応答に支障がでるとは。かなりおそまつなCPUを使ってるんだろうな。

2014年4月20日日曜日

データ・ロギング・システムの検討(socket::send関数の戻り値について)

半年ほど前にちょっと検討したことがあります。
その時わかったこととして、socketの処理時間(特に受信時間?)が随分かかるのと、ファイルキャッシュが強力で、受信する方はかなりの容量受信してしまうが、本格的にプログラムを作って動かしたときの挙動が心配なことがわかったところで、そのまま放置していました。(特にまだ切羽詰まった必要性がなかったもんで)

なんとなくsocketまわりのコードを見ていたら、ちょっとsend関数の挙動について気になることがでてきました。
send関数のman見ると、以下の説明がでてきます。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

これまでリターンでは"-1"しか気にしてませんでしたが、よくみると「送信できたバイト数を返す」とあります。
これって”必ずしも要求した送信データをすべて1回のAPIコールでは送信しきれないこともあるよ”っていうことです。これまで経験したことないんですが、ここで考えているようなsocket通信に負荷が高いことをやろうとする場合は考えとかないといけないんじゃない?と気づきました。そこんところ注意するなら、以下のようにコーディングすべきです。

while (送信したいバイト数 > 0) {
  送信済バイト数 = send(  );
  送信したいバイト数 -= 送信済バイト数
}

みたいな感じでwhile-loopを組むべきです。
しかし更に問題として、ここでそもそも「一度に送信できない状況って何?」ということがあります。
このsocketが実装された当時は単純にCPUの処理速度やらまわりのHWの性能が遅くて、途中でも制御を返すようにしていたと考えられますが、現在の様にHWの性能が格段に進歩していて、それでも要求されたバイト数を送りきれない、としたら以下の状況が考えられます。

  1. 送信側のバッファが一杯になった
  2. 上記が起きる原因として、受信側の処理が間に合わなくなりAckを十分返しきれてない

従って、単純にwhile-loopで待ってても大丈夫なのか?ということになります。それにあまりsend()で負荷をかけるとkernelが不機嫌になる(暴走する)ことも起きそうで、そうなるとsend自体がerrorで返されることも考えられます。

そもそもデータ・ロギング・システムを作ろうと考えたとき、ロギングデータの抜けを許すか?ということを頭から考えてませんでした。抜けを許さない状況なら、エラーに対処するコード(sendがerrorを返したときの復旧コード)を考えるだけ無駄です。実際、現状自分が考えている用途は試験データの収集を想定しており、途中でログ・データの抜けがあったら試験失敗になり、再試験となります。従ってエラーがでない限りのロギング可能なデータ量が事前にわかっている必要があります。

とりあえず、sendの戻り値は監視するようにしとかないといけませんね。(現状は、エラーの"-1"しかチェックしないことがほとんどです)
もし送りきれない状況が起きたら、システムとしてロギング可能量をオーバーしたのか見極めないと。

あと、現状Webサーバー等でこの種の大量のデータをSocketで送受信しているシステムだと、CPU負荷の問題でもこのような送信不可の状況が一時的に発生することが考えられます。そういうシステムの場合は、抜けが発生したらそれはいさぎよく諦めて再送信してね、という対応になるでしょうから、上記while-loop内に更にtime-outに関する実装も必要になってくるでしょう。

2013年10月7日月曜日

データ・ロギング・システムの検討(Etherによるデータ転送能力の調査)

さて、snappyの実力に関する実験データは取れたので、いよいよ検討してるデータ収集(ロギング)の検討に入ります。まずデータをEtherで転送するときの、実際の転送時間や、HDDへの記録にかかる時間、タイミングなんかを調べます。

まず、いま検討しているシステムは以下の様なものです。

PC1PCxはリアルタイムでデータを収集します。そのため自前でデータを記録していると、リアルタイムでのデータ収集のタイミングが乱される可能性があるので、収集したデータをEhterでどんどんPC0に送ります。(TCP/IPのSocket通信を想定しています)PC0では、送られたデータをどんどんHDDにログしていく。(ここは特にリアルタイムである必要はないが、PC1PCxより送られてくるデータを受信・ログが間に合う必要はある。)

本システムでタイミング的にボトルネックになりそうなのは以下の2点です。

PC1→PC0で収集データを送るときのEtherの通信時間。(データ長が長いと、それだけ通信時間が長くなる)
PC0によるHDDへの書き込み時間。(システムはLinuxで動かすのを想定しているので、PC0では強烈なファイルキャッシュが働くはずですが、それでも結局はHDDへの書き込み時間が問題になる)

結局、2か所でデータ長が問題になり、収集データを圧縮したいという要求がでてきます。それも高速に。(リアルタイム処理が乱されないように)
考えた、システムでの動作シーケンスの概要を以下に示します。
話を簡単にするために、データ収集としてPC1だけにします。

PC1PC0のSocketを接続
PC1で収集したデータをsnappy等で圧縮(今回の実験では、まだこの部分は未実装で計測)
PC1からPC0のEhterでデータを送信
PC0では受信したデータをHDDにログ
PC0からログ処理が終了し、次のデータを送ってもいいことを知らせるためにAckをPC1へ送る

まず基本的なデータを取るために、圧縮なしで100KBのデータをPC1は全速で送り、1回のloop処理時間を計測してみました。(10回loopを、3回実施)

1回目開始からの時間[sec]loopの時間[sec]2回目開始からの時間[sec]loopの時間[sec]3回目開始からの時間[sec]loopの時間[sec]
00.0094580700.0093951200.00931001
10.0188470.0093889310.0186870.0092918810.01864890.00933889
20.0281250.00927820.0280430.00935620.02789090.009242
30.0374820.00935730.03741910.009376130.0371830.0092921
40.0467150.00923340.04685210.00943340.04670190.0095189
50.05598310.009268150.05619910.00934750.05608110.0093792
60.06527520.009292160.0654850.009285960.06545310.009372
70.07459810.009322970.07483820.009353270.0748780.0094249
80.08389210.00929480.08408310.009244980.0841250.009247
90.0931940.009301990.09342410.00934190.09344390.0093189

約9msちょっとかかるようですね。なお、各loop処理時間の計測結果は毎回coutしてしまうと、コンソール出力処理の待ちにかなり時間を喰われてしまい、正確なloopの処理時間が計測できません。(このコンソール処理ってすごい時間がかかるんですよ。HDDへの出力はファイルキャッシュがあるので高速なんですが。)なので、プログラム中に計測した時間をメモリに記憶し、loop処理終了後にcoutするようにしています。

さて9ms/回はわかりましたが、ボトルネックとなる①、②はこのうちどれだけ時間がかかっているんでしょう?PC1にてWireSharkを起動し、パケットが何時送られたか時間を計測してみました。(PC0はHDDにログをしているのでファイルキャッシュがかなりきつい状態になっているはずです。そんな高負荷な状態でWireShark動かしてしまうと時間計測に影響を与えるといけないので、PC1で行います)
大体、以下の様な結果を得ました。(動作説明:PC1では「データ送信」の時に100KB分を1回だけsend( )関数をCallしています。それが35個のパケットに分解され、全部で2msかかったのがWireSharkのログで判明しました。また、PC0では1回のrecv( )関数では100KB分を受信できず、2,3回にわけて受信していました。つまりTCP/IPの実装部で35個のパケットを受信バッファにどんどん取り込んでいますが、アプリに制御が渡るのはまとめて2,3回になっていました。ただし、個々のパケット受信に対してはパケット受領のackがデータ送信の1ms後から始まり、8msの間続いたのがWireSharkのログで判明しました。PC0側で100KB分のデータをHDDにfwrite( )してプログラムに制御が返ってきたところで、PC1にAckの信号を送っています。それらの時間関係を示したのが、下の図です。)


PC0は受信において8msの間に35個のackパケットを250~300usにてほぼ等間隔にPC1に返していました。(例外もあります。時々50usのときもありましたし、1回300msもかかったことがありました。)

ところで上記の実験では10回loopでしたが、これだとログ・サイズは1MBにしかなりません。OSはLinuxでやっているので、余裕でファイルキャッシュに吸収されてしまい、実際のHDDへの書き込み時間(処理)の影響を測定できていません。試しにloopを30,000回にして2GB以上のログファイルを転送するときの時間を計測してみました。(PC0は32bit Linuxでメモリ2GB搭載です。これだけのログファイルをHDDへ書き込もうとすれば、オンメモリ上のファイルキャッシュは一杯になるでしょう。)
結果はやはり9ms/回でした。HDDの書き込み速度がそこそこ速く、処理が間に合ったのでしょう。(しかしファイルキャッシュの効果は偉大だ)

ただそれにしても送信処理より受信処理が4倍かかるのはちょっと予想外に時間がかかりすぎです。これだと高速な圧縮処理使うより、(snappy使うより)多少時間かかってもデータサイズを小さく圧縮(高圧縮率)した方がトータルで性能が出るんでない?
試しにHDDへの書き込みだけコメントアウトして、socketの受信処理だけに絞って計測してみました。

結果は、変わらないでした。ちょっとびっくりしましたが、受信時のackパケットを250us毎に返していたのはTC/IPの受信スタックの処理時間が優勢のようです。TCP/IPの処理は重いとは聞いていましたが、これほどとは・・・ データの圧縮どうこう言う前に、socket通信を少しでも早くするために、socketのパケットサイズを調整しないといけません。

今回実験してみて感じたことをまとめてみます。


  • Linuxのファイルキャッシュはかなり強力だった。(テストプログラムを終了したあとも、しばらくHDDが動きっぱなしでした。実際に2GB強のデータをHDDに書き込まれたのはテストプログラム終了後しばらくしてからでしょう。)
  • TCP/IPの処理はかなり時間がかかる。今回、1回のloopで100KBをまとめてsend( )しましたが、パケットとしては35個に分割されていました。他に何台のPCがPC0に接続されるかによりますが、システムの利用状況に応じてパケットサイズを調整しないといけない。(あるいはこんなくらいの時間がかかると思ってシステムの設計を行うかのどちらか)
  • 結局、snappyの有効性確認ができる状況が作れませんでしたが、ある程度の圧縮率が見込める運用状況ならやった方がいいでしょう。(圧縮率200%が見込めるなら、単純にパケット長が半分になるので、上図の送信・受信処理時間も半分になってくれるはず。)
  • 収集データをHDDにロギングまでする必要があるというのはかなり特殊な用途ですが、1回のシステムの動作で10GBくらいのデータを収集するなら64bit OSマシンでオンメモリに保持した方がいい。今回2GB強のログサイズだったので、ファイルキャッシュがなんとかもったが、10GBくらいだとたぶんHDDの状態にも影響されるでしょう。(テストプログラム終了後のdisk I/Oがすごかったので、ちょっと怖くなった)


まあ、snappy使って送信データを圧縮・伸長しようなんてのは、たいていはクラウド系のシステムでの利用がほとんどでしょうから、今回の検討が特殊な状況だとは思います。

PS:
結論として高速にEtherで通信したいと思ったら、snappy等でデータを圧縮し、パケットのサイズを減らし(なんとか1パケットに抑えれるのがベスト!)、受信側の負荷を下げてやるのが一番いいということになると思います。



2013年1月14日月曜日

リアルタイムのEthernet通信はないか?

複数の計算機を並列で動かしていると、複数の計算機間で「同期をとって」通信をしたくなります。現状、主流のWindowsやLinuxではEthernetが非常に安価で高速なんですが、いかんせんCSMA/CDというでたとこ勝負の通信方式のため、この「同期」のためのリアルタイム性が保障されていません。(そのかわり、「安価」で非常に頑丈な通信システムにできています)ただ、やっぱ10~20台程度の計算機を並列で動かすとき、リアルタイム性の欲しいシステムを作りたいときがあります。2~4Hz程度なら普通のTCP/IPでもなんとかごまかせますが、10Hzでも怪しくなります。(たまに抜けてもいいというならいいんですが、1個のパケットも抜けちゃダメ!と言われるともう保障できません。数時間くらいならいけると思いますが、たいていそういう要求するシステムは計測のログ収集が目的なんでダメです。

こういう時は、やはり昔IBMが出してたToken-Ring方式が確実です。でも性能-コストの関係でかなり初期の段階でEthernetに負けてしまいました。こうなると物理層はEthernetでTokenとばす通信スタックはないもんかと思って探してみたんですが、同じこと考える人がやはりいました。目的が産業用のセンサーやモーター制御のためなんですが、Ether CatとEthernet/IPというのがありました。どちらも一応Openな規格をうたってるんですが、詳しい仕様は会員にならないと入手できないようです。また参考実装もなく、見つかったのは商品のソフトウェアだけでした。(Ethernet/IPはSourceforgeに通信ソフトがあったんですが、使い方が全然わからない。おまけに、windowsのCygwin環境に調整してあるようで、そのままではLinuxでビルドもできない)もうちょっと頑張ってみますが、簡単にわかった仕様をメモしておきます。

1.Ether CAT
文字通り自分の考えてた構想をそのまま実現してます。TCP/IPの通信スタックをまるきり置き換える感じで、Token飛ばして通信するようです。(イメージ下図)


通信のトポロジーは図のようなものだけでなく、通常のツリー型だろうがサポートするとはありましたが、基本的には図のようにMasterからTokenとばして、端のRepeaterでそれを折り返すという感じです。さすがにこの形態なので反応速度は速く、通信量にもよるんでしょうが、100us以下で通信できるとのことです。(通信帯域も80%くらいは使いきれるとのこと)
ただ文字通り産業用機器の通信のようで、専用のI/Fカードとか使います。(通常のEhternetカードでもいけそうなのもありましたが、基本はセットで販売という感じでした)

2.Ethernet/IP
これはTokenとかは特に使ってないようで、UDPで通信するようです。(ただTCPでもできるとあり、その場合のリアルタイム性どうするんだ?という気はしますが)TCP/IPの通信スタックは既存のものを使い、アプリケーション層でなんとかするというタイプです。そのため、通常のLANも使いながらでもOKらしいんですが、その場合のリアルタイム性の保障はどうするんだ?という不安はあります。そのため、リアルタイム性もぐっと落ちて数10msオーダーの反応速度です。(自分的には産業用の機器制御するつもりはないんで、それで十分なんですが)


調べてたら、オムロンとかキーエンスとかのHPが普通にひっかかり、自分は専門外で知らなかったんですが、結構使われてるんでしょうか?(Ethernetは昔から光ファイバーがありましたから、工場等の雑音が多いところでもその気になれば安定して通信できますから、下手に専用の通信機器作るよりは高速で安価にできそうな気もします)