株式会社アイズ・ソフトウェア

/~eyes-software-co-jp/A1A2
お問い合わせ |  情報セキュリティへの取組について情報セキュリティ方針個人情報保護方針) |  ブログ

BLOG 社員ブログ

複数通信先のソケット通信サンプル

C言語で、複数の通信先とソケット通信を行う手順についてサンプルプログラムにて説明します。

本サンプルプログラムはソケット通信のサーバー側で、1つのポートで最大10個の通信先プロセスからデータを受信するものです。
クライアント側のサンプルプログラムについては以前のソケット通信サンプルのを参照すること。

  1. socket()にてソケットを生成し、ディスクリプタを得る。
  2. bind()にてソケットにポート番号及びIPアドレスを設定する。
  3. listen()にて接続を待ち状態とする。
  4. 接続待ちのディスクリプタをディスクリプタ集合に設定する。
    ディスクリプタ集合はFD_ZERO()にて初期化し、FD_SETにてディスクリプタを設定する。
  5. 2回目以降で受信ディスクリプタがある場合、受信ディスクリプタをディスクリプタ集合に設定する。
  6. struct timeval 構造体の変数tvにタイムアウト値を設定する。
    変数tvはselect()にて値が変更される場合がある。その為、タイムアウト値は毎回設定すること。
  7. select()にて接続&受信を待ち受ける。
      select()の第一引数へはディスクリプタ集合にセットしたディスクリプタの最大値+1を指定する。
      第二引数へは接続及び受信待ちのディスクリプタ集合を指定する。
      第三引数へは送信完了待ちのディスクリプタを指定する。
      第四引数へは例外を監視するディスクリプタを指定する。
      本サンプルでは第三、第四引数は使用しない為、NULLを指定している。
      第五引数へはタイムアウト値を指定する。タイムアウト時間を指定しない場合はNULLを指定する。
      select()はシグナル受信した場合、タイムアウトした場合、ディスクリプタ集合で指定したディスクリプタにデータ受信などの変化があった場合にリターンする。
  8. select()の戻り値が負の値の場合、select()がエラー終了している。ただし、errnoがEINTRの場合はシグナル受信での終了なので、リトライで継続できる。
    本サンプルではシグナル受信の場合は4.からの処理を繰り返すようにしている。
  9. select()の戻り値が0の場合、タイムアウトしている。
    本サンプルでは何もしないため、4.からの処理を繰り返すようにしている。
  10. select()の戻り値が正の値の場合、ディスクリプタ集合で指定したディスクリプタのどれかに変化が有ったことを示している。
    どのディスクリプタに変化があったかはFD_ISSET()にて調べることが可能である。
  11. 接続待ちディスクリプタに接続があったかをFD_ISSET()にて調べ、接続があればaccept()にて接続を確立する。
    本サンプルではaccept()にて取得したディスクリプタをfd2配列にセットし、受信ディスクリプタとして使用する。
  12. 受信ディスクリプタに受信データがあるかをFD_ISSET()にて調べ、受信データがrecv()にてデータを受信する。
    recv()の戻り値が0であった場合、そのディスクリプタはクライアント側から切断された事を示している。
  13. 本サンプルではfd2配列にて10個の受信ディスクリプタを管理している。接続されたらディスクリプタをセットし、切断されたら-1へリセットすることで同時に最大10個の通信先からのデータを受信できる。
  14. なお、ソケット通信とは無関係であるが、何箇所か出てくる sizeof(fd2)/sizeof(fd2[0]) はfd2配列の要素数を取得するものである。

========== サーバー側 ============


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

int main(int argc, char** argv)
{
    int fd;             // 接続待ち用ディスクリプタ
    int fd2[10];        // 通信用ディスクリプタの配列
    struct servent *serv;
    struct sockaddr_in addr;
    socklen_t len = sizeof(struct sockaddr_in);
    struct sockaddr_in from_addr;
    char    buf[2048];
    int     cnt;

    // 受信バッファを初期化する
    memset(buf, 0, sizeof(buf));
    // 通信用ディスクリプタの配列を初期化する
    for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
        fd2[i] = -1;
    }
    // ソケットを作成する
    if (( fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) {
        fprintf( stdout, "socket error : fd = %d\n", fd );
        return -1;
    }

    // IPアドレス、ポート番号を設定
    addr.sin_family = AF_INET;
    addr.sin_port = ntohs(50000);
    addr.sin_addr.s_addr = INADDR_ANY;
    // バインドする
    if ( bind( fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) {
        fprintf( stdout, "bind error\n" );
        return -1;
    }
    // 接続待ち状態とする。待ちうけるコネクト要求は1個
    if ( listen( fd, 1 ) < 0 ) {
        fprintf( stdout, "listen error\n" );
        return -1;
    }

    int     maxfd;          // ディスクリプタの最大値
    fd_set  rfds;           // 接続待ち、受信待ちをするディスクリプタの集合
    struct timeval  tv;     // タイムアウト時間

    while ( 1 ){
        // 接続待ちのディスクリプタをディスクリプタ集合に設定する
        FD_ZERO( &rfds );
        FD_SET( fd, &rfds );
        maxfd = fd;
        // 受信待ちのディスクリプタをディスクリプタ集合に設定する
        for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
            if ( fd2[i] != -1 ){
                FD_SET( fd2[i], &rfds );
                if ( fd2[i] > maxfd ) maxfd = fd2[i];
            }
        }
        // タイムアウト時間を10sec+500000μsec に指定する。
        tv.tv_sec = 10;
        tv.tv_usec = 500000;

        // 接続&受信を待ち受ける
        cnt = select( maxfd+1, &rfds, NULL, NULL, &tv );
        if ( cnt < 0 ){
            // シグナル受信によるselect終了の場合、再度待ち受けに戻る
            if ( errno == EINTR ) continue;
            // その他のエラーの場合、終了する。
            goto end;
        } else if ( cnt == 0 ) {
            // タイムアウトした場合、再度待ち受けに戻る
            continue;
        } else {
            // 接続待ちディスクリプタに接続があったかを調べる
            if ( FD_ISSET( fd, &rfds )){
                // 接続されたならクライアントからの接続を確立する
                for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
                    if ( fd2[i] == -1 ){
                        if (( fd2[i] = accept(fd, (struct sockaddr *)&from_addr, &len )) < 0 ) {
                            goto end;
                        }
                        fprintf( stdout, "socket:%d  connected. \n", fd2[i] );
                        break;
                    }
                }
            }
            for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
                // 受信待ちディスクリプタにデータがあるかを調べる
                if ( FD_ISSET( fd2[i], &rfds )){
                    // データがあるならパケット受信する
                    cnt = recv( fd2[i], buf, sizeof(buf), 0 );
                    if ( cnt > 0 ) {
                        // パケット受信成功の場合
                        fprintf( stdout, "recv:\"%s\"\n", buf );
                    } else if ( cnt == 0 ) {
                        // 切断された場合、クローズする
                        fprintf( stdout, "socket:%d  disconnected. \n", fd2[i] );
                        close( fd2[i] );
                        fd2[i] = -1;
                    } else {
                        goto end;
                    }
                }
            }
        }
    }
end:
    // パケット送受信用ソケットクローズ
    for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
        close(fd2[i]);
    }
    // 接続要求待ち受け用ソケットクローズ
    close(fd);
    return 0;
}
============ ここまで ============

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。

ETロボコン技術要素・自己位置推定

ETロボコン・デベロッパーズ部門プライマリークラスは基本的にはコースに引かれたラインをトレースして走る競技です。
ですので、自己位置については1次元的に走った距離がわかっていれば位置がわかります。
しかし、チームアイズでは少しでもタイムを縮めるために、急カーブや複合カーブではラインをはずして走るという戦略をとりました。
最初はバランスコントロールライブラリに渡す"ターン値"を決めて曲がるという事を試しましたがロボットの速度、バッテリー残量等の違いで常に同じ弧を描いて曲がるという事が出来ませんでした。
そして考えたのが仮想の円をライントレースするという方法です。内側が黒、外側が白の仮想の円を描き、そのエッジをPID制御でライントレースして走行させていると考えてください。
この仮想円は中心の座標と半径で決まります。あと、ロボットの座標がわかれば、ロボットが仮想円のエッジ(半径)からの距離が計算できます。この距離を明るさの差分に当てはめ、PID制御でライントレースします。

この案の実現の為、ロボットの自己位置推定処理を実装しました。



ETロボコンのロボットで自己位置推定の為に使用できるのは左右の車輪モーターから取得できる回転角度だけです。
この回転角度より 2πr×(回転角度÷360) で車輪の進んだ距離が算出できます。

ロボットが一定期間の間に図1のように走ったとします。
進んだ距離と曲がった角度を計算する為に、図2のように変形させます。
図2よりロボットを取り除き、計算に必要な記号等を追加したのが図3です。

進んだ距離と曲がった角度は下記の式で計算できます。
   R = Rモーター回転角度×車輪直径×π÷360
   L = Lモーター回転角度×車輪直径×π÷360
   d = (L+R)÷2
   θ= atan((R-L)÷W)

進んだ距離と曲がった角度より新しい座標を求めます。この座標と角度を始点として上記の計算を繰り返して自己位置推定をしています。

自己位置推定の結果をエクセルで座標にプロットしたのが図4です。コースは2010年版のコースです。

クリックでエクセルをダウンロード



図1から図2への変形でわかるとおり、この自己位置推定処理は誤差を含んだ計算となっています。長く走るほど誤差が累積していきます。その為、遠くまで正確にコースを走るにはコースの何らかの印を検出し、位置情報を補正する処理が必要となります。

本ブログの説明はチームアイズのロボットに実装している自己位置推定処理を説明したものです。
本ブログの記載を元にETロボコンのモデル又はプログラムを作成した結果については(株)アイズ・ソフトウェアは一切の責任を負いません。使用はご自身の責任で行ってください。

ETロボコン技術要素・PID制御

私たちが参加しているETロボコン・デベロッパーズ部門プライマリークラスは倒立振子制御で二輪走行するロボットを ラインに沿ってゴールまで走らせる競技です。
さて、ロボットはラインをどの様に見てラインの何処を走っているのでしょう。
ロボットには足元に光センサ(EV3ではカラーセンサ)がついており、これでラインを見ています。

この光センサは明るさしか見ることが出来ません。ロボットが白い部分にいる時は明るい事を示す値。
黒いラインの上にいる時は暗い事を示す値が取れます。白と黒との境にいる時はその中間の値になります。
ロボットはこの白と黒の境を(以降エッジと云います)走ることでライントレースを行います。

一番簡単な走り方はロボットがエッジの明るさより白い色の個所を走っている時にはライン側へハンドルを切り、逆にエッジの明るさより黒い色の個所を走っている時にはラインの外側へハンドルを切って走る方法です。
この様に光センサの明るさで、ハンドルを右左に切るのはON/OFF制御と云います。

でも、ロボットがエッジの上にいる時にはハンドルはまっすぐでいいですよね。
ON/OFF制御を改良して光センサの明るさでハンドルを切る量を調整すれば、エッジの上ではハンドルがまっすぐになります。これを比例制御と云います。

比例制御ではエッジに近づくとハンドルを切る量が小さくなりますが、エッジを超えるまでハンドルは白->黒、または黒->白の方向へ切っています。
車を運転する人は分かると思いますが、これではハンドルを切るのが遅くてラインに沿って走れません。

そこで、エッジから遠ざかったらハンドルを切り増しし、エッジに近づいたらハンドルを逆へ切るようにします。この「エッジに近づいたら」というのは光センサの少し前の明るさと現在の明るさを比べて判断します。これを微分制御といいます。

上記の比例制御と微分制御を組み合わせた制御では、エッジに近づけようとする比例制御とハンドルを逆へ切る微分制御がぶつかり合い、エッジから少し離れたところをトレースする場合があります。
それに対応してエッジに近づけている制御を行う積分制御という制御方式がありますが、チームアイズでは使用していない為、説明は省略します。

ここまでに説明した比例制御(以降P制御)、微分制御(以降D制御)、積分制御(以降I制御)をあわせて一般的にPID制御と呼びます。

チームアイズのロボットは上記のうちのP制御とD制御を使用してライントレース走行を実現しています。I制御についてはエッジから少し離れて走っても問題ないので、採用していません。


◆EV3への実装について
ETロボコンで使用するPID制御プログラムについては省略します。

◆PID制御の実装と調整
ETロボコンのロボットのハンドル制御はロボコン参加者が共通で使用するバランスコントロールライブラリの引数に“ターン値”として設定します。
ターン値は上で説明したPID制御によって求めます。その際、明るさの値をそのままPID制御に掛けて出たターン値でロボットを走らせてもうまく走りません。
明るさの値を適切なターン値を決める為の係数が必要なのです。また、その係数はP制御、D制御にそれぞれ必要です。
さて、EV3のカラーセンサは明るさを0~100の値として出力します。これがそのまま白と黒の値になるわけではありません。
そこで、ロボットが白の上にいるときの光センサの値とラインの上にいるときの光センサの値を事前に調べてみました。
その結果は、黒の時:2、白の時:24 となりました。このときに目標とするエッジの明るさは(黒の明るさ+白の明るさ)/2 = 13 です。
その次に、ロボットをライントレースで走らせて4ms毎に光センサから出力された値のログを取ってみました。その時の値が図1の青線のグラフです。なお、赤線のグラフはターン値です。

図1
このようなグラフを元にP制御の係数とD制御の係数を調整していきます。

私たちがPID制御の係数を調整する時は以下の3点を考慮しています。
1. ターン値が100を超えない事。
2. 走らせてカーブを曲がれる事。曲がりが足りない場合はP制御の係数を大きくする。
3. 走らせてふらふら左右に振れない事。振れる場合はD制御の係数を大きく、P制御の係数を小さくする。

◆EV3での問題点
図1の赤線のグラフは調整途中のターン値ですが、何か変だ、バグじゃないか?と思いませんか?
青矢印の所だけターン値が尖っています。これは何でしょう?
矢印の上、青線まで目線を上げるともうお判りですよね。光センサの明るさ値が変わっている所です。
最初のPID制御の説明を思い出してください、少し前との明るさの差でD制御をしている事を。
ロボットの下の明るさはラインとの距離によってどんどん変わっているはずなのに、明るさの値が5段階しかないので明るさ値の変わった時にしかD制御が行われていないのです。
これではD制御が少ししか掛からず、D制御の係数を調整しても左右のふらつきが消えませんね。
このターン値を求める制御のどこに問題があるのか、これへの対策はどのようにしたらいいのか?は読んでいる方が考えてみてください。


この記事を書いている時点では2016年のETロボコンは終わっていません。
チャンピオンシップ大会が終わった後にその気があったらチームアイズでどのように対策したか等書くかもしれません。期待せずにお待ちください。

本ブログの説明はPID制御とロボットのハンドルの切り方を大雑把に説明したのもです。
本ブログの記載を元にETロボコンのモデル又はプログラムを作成した結果については(株)アイズ・ソフトウェアは一切の責任を負いません。使用はご自身の責任で行ってください。

Linux®課題

当社社員がLinux®を使用したプロジェクトに参加する際に事前学習に使用したテキストを公開します。
解答は記載しませんが、課題を解いてみてください。

※ Linux® は、Linus Torvalds 氏の日本およびその他の国における登録商標または商標です。

==================== ここから ====================
■■総合課題■■
  「課題」は、課題をこなすことが目的ではなく、
  「課題」を通じて、「知識」と「気づき」を得てもらうことが目的です。
  
  従って、総合課題として以下を記載します。
  
  1.各「個別課題」から得られた「知識」をノートにまとめてください。
      各「個別課題」毎に自分のノートにまとめ、コピーを提出してください。
      
  2.各「個別課題」終了後に「どんな学び」「どんな気づき」があったかを
      報告してください。
      これは日報に入れてください。

■■個別課題■■

●課題1
  内容:Unix系コマンドを使ってフォルダ移動、ファイルのコピー等を行う。
  前提:Linuxまたは互換環境があること。
  達成レベル:良く使う基本的なコマンドが使えること。各オプション確認。
        ・cd
        ・pwd
        ・ls
        ・cp
        ・mv
        ・mkdir
        ・rmdir
        ・clear
        ・diff
        ・cut
        ・du
        ・df
        ・su
        ・tar
        ・telnet
        ・netstat
        ・ping
        ・head
        ・history
        ・rm
        ・ps
        ・find
        ・grep
        ・cat
        ・tail
        ・more
        ・less
        ・echo
        ・chown
        ・chmod
        ・chgrp
        ・whereis
        ・man
        ・printenv
        ・shutdown
        ・reboot
        ・kill
        ・popd
        ・pushd
        ・set
        ・ln
        ・シェル
        ・ファイル属性等の意味
        ・環境変数
        ・実行ファイルの実行方法
        ・パス変数の意味
        ・大文字小文字
        ・漢字の入力
        ・特殊フォルダ
        ・デバイススペシャルファイル
        ・先頭が . で始まるファイル
        ・ログファイルの位置
        ・(|)の意味
        ・(>)の意味
        ・(&)の意味
        ・(.)の意味
        ・(..)の意味
        ・(/)の意味
        ・(~)の意味
        
        ※ 上記にあげたコマンド等は、環境やディストリビューションによって
           使えないものやインストールされていないものがあるので、留意すること。
  課題:
    ① man ページを使って上記に挙げてある各コマンドの概要を確認してみよ。
      (例) man cp
        <manページ上での操作の確認>
       ・表示されている画面で↑を押下するとどうなるか?
       ・表示されている画面で↓を押下するとどうなるか?
       ・表示されている画面でスペースキーを押下するとどうなるか?
       ・表示されている画面でwを押下するとどうなるか?
       ・表示されている画面でエンターを押下するとどうなるか?
       ・表示されている画面でqを押すとどうなるか?
        <cpコマンドの説明の確認>
       ・cpコマンドは何をするコマンドか?
       ・どういう文法か?
       ・オプションはどのようなものがあるか?
        -----上記は例なので全てのコマンドについて確認すること-----
    ② 環境変数の一覧から使用しているシェルを特定せよ。
    ③ man ページを使ってシェルについて調査せよ。
      (例) man bash      ※シェルにbashを使っている場合
        <manページ上での操作の確認>
       ・表示されている画面でhを押すとどうなるか?
       ・表示されている画面でqを押すとどうなるか?
        <シェル文法の確認>
       ・リダイレクトについて確認せよ( REDIRECTION で検索 )
       ・パイプラインについて確認せよ( Pipe または Pipeline で検索)
    ④ 現在のディレクトリはどこか確認せよ。
    ⑤ サブディレクトリ「test」を作成せよ。
    ⑥ サブディレクトリ「test」に移動せよ。
    ⑦ 「test」フォルダにて、echoコマンドを使ってファイルを1つ作成せよ。
       ファイル名は、echo_test.txt とすること。
       ヒント:リダイレクト(>)
    ⑧ ファイルが作成されていることを確認せよ。
    ⑨ echo_test.txtの内容を表示せよ。
    ⑩ echo_test.txtを コピーして echo_test2.txt を作成せよ。
    ⑪ catコマンドを使って、echo_test2.txt ファイルと同じ内容の
       echo_test3.txt ファイルを作成せよ。
    ⑫ ④のディレクトリへ移動し、
       ⑪で作成した echo_test3.txt ファイルを現在のディレクトリにコピーせよ。
    ⑬ 現在のフォルダに「/cygdrive/c/windows/system32/notepad.exe」の、
       リンク(シンボリックリンク)を作成せよ。
    ⑭ ファイル一覧を表示させ、属性、オーナー、グループ、
       リンクについて確認せよ。
    ⑮ ファイル名にlogがつくものを、「/」フォルダから検索せよ。
    ⑯ /var/log 以下にあるファイルで一番大きいサイズのファイルを表示させよ。
    ⑰ ⑯のファイルの先頭から10行を表示させよ。また、末尾10行の表示もさせよ。
    ⑱ ⑯のファイルの中から「/etc」を含む行だけを表示させよ。
       また先頭10行の表示、末尾10行の表示をそれぞれ行うこと。
    ⑲ 「./notepad.exe」と「./notepad.exe &」の差を確認せよ。
       また、psコマンドで起動プロセスの確認をせよ。
    ⑳ フォルダ「test」を削除せよ。(「test」フォルダ内のファイルも削除。)

●課題2
  内容:viエディタを使ってC言語のソースを入力する。
  前提:viエディタがインストールされているLinuxまたは互換環境があること。
  達成レベル:viエディタの最低限の操作ができること。
        ・入力モード/編集モード切替
        ・検索
        ・置換
        ・保存終了、破棄
        ・カーソル移動
        ・行コピー
        ・1文字削除
        ・行削除
        (等)
        
  課題:
    ① 以下の内容を hoge.c として作成し保存せよ。
      -------
      #include <stdio.h>
      
      main(){
          int cnt=0;
          cnt=cnt+5;
          printf("%d\n",cnt);
      }
      --------
    ② 作成された hoge.c を編集して以下の内容にして保存せよ。
      -------
      #include <stdio.h>
      
      main(){
          int cnt=0;
          int num[10]={0,};
          int hsize=sizeof(num)/sizeof(num[0]);
          
          for(cnt=0;cnt<hsize;cnt++){
              num[cnt]=cnt*cnt;
          }
          for(cnt=0;cnt<hsize;cnt++){
              printf("%d\n",num[cnt]);
          }
      }
      --------

●課題3
  内容:単一C言語ファイルをビルドして実行、デバッグする
  前提:gcc、gdbがインストールされているLinuxまたは互換環境があること。
  達成レベル:単一ファイルのビルドと実行、デバッグができる。各オプションの確認。
        ・gcc
        ・gdb
        
  課題:
    ① manページを使って、gccのオプション等を確認せよ。
    ② 課題2で作成したhoge.cを、以下のコマンドライン入力をしてビルドせよ。
       gcc hoge.c
    ③ 上記②で作成されたファイルは何か?サイズは?属性は?
       を確認し、実行してみよ。
    ④ 課題2で作成したhoge.cを、以下のコマンドライン入力をしてビルドせよ。
       gcc -g -O0 -ohoge hoge.c
    ⑤ 上記④で作成されたファイルは何か?サイズは?属性は?
       を確認し、実行してみよ。
    ⑥ manページを使って、gdbのオプション等を確認せよ。
    ⑦ 上記②で作成されたファイルをgdbで指定し、
       gdb内でソースコードを閲覧できるか確認せよ。→確認後quit
    ⑧ 上記④で作成されたファイルをgdbで指定し、
       gdb内でソースコードを閲覧できるか確認せよ。
    ⑨ 上記⑧の状態で、5行目(int num[10]={0,};)に
       ブレークポイントを設定し、実行してみよ。
    ⑩ さらに1行実行してみよ。
    ⑫ 変数 hsizeの値を表示せよ。
    ⑬ 最後までいっきに実行せよ。

●課題4
  内容:複数C言語ファイルをビルドして実行、デバッグする
  前提:gcc、gdbがインストールされているLinuxまたは互換環境があること。
  達成レベル:複数ファイルのビルドと実行、デバッグができる。
  課題:
    ① 課題2の hoge.c をviエディタで編集して以下の内容にして保存せよ。
      -------
      #define _HOGE_C_
      #include "hoge.h"
      
      int main(){
          int cnt=0;
          int num[10]={0,};
          int hsize=sizeof(num)/sizeof(num[0]);
          
          for(cnt=0;cnt<hsize;cnt++){
              //change
              num[cnt]=extFunc(cnt);
          }
          for(cnt=0;cnt<hsize;cnt++){
              printf("%d\n",num[cnt]);
          }
          return 0;
      }
      --------
    ② 以下の内容を extfunc.c として保存せよ。
      -------
      #include "hoge.h"
      
      int extFunc(int number) {
          return number*number;
      }
      --------
    ③ 以下の内容を inc/hoge.h として保存せよ。
      -------
      #include <stdio.h>
      
      #ifdef _HOGE_C_
      extern int extFunc(int);
      #endif
      --------
    ④ 以下の内容を makefile として保存せよ。
       尚、下記の行の★印以降は課題用のコメントなので入力不要である。
      -------
      #Makefile for hoge
      
      PROGRAM = hoge
      OBJS = hoge.o extfunc.o
      CC = gcc
      INCDIR = inc
      CFLAGS = -Wall -g -O0 -I$(INCDIR)
      
      .SUFFIXES: .c .o
      
      $(PROGRAM): $(OBJS)
              $(CC) -o $(PROGRAM) $^   ★先頭の空白はタブ
      
      .c.o:
              $(CC) $(CFLAGS) -c $<   ★先頭の空白はタブ
      
      .PHONY: all
      all: hoge
      
      .PHONY: clean
      clean:
              $(RM) $(PROGRAM) $(OBJS)   ★先頭の空白はタブ
      --------
    ⑤ make all でビルドせよ。
    ⑥ gdb で hoge.exe を指定して開き、extfunc.cのソースを表示せよ。
    ⑦ extfunc.c の4行目( return number*number; )にブレークを設定し実行せよ。
    ⑧ 変数 number を表示せよ。

●課題5
  内容:ファイルを読書きする複数C言語ファイルを作成、ビルドして実行、デバッグする
  前提:gcc、gdbがインストールされているLinuxまたは互換環境があること。
  達成レベル:自分でファイルを読書きするプログラムを作成できる。
              複数ファイルのビルドと実行、デバッグができる。
  課題:
    ① 入力した値をファイルに書き出すプログラムを作成せよ。
    ② ファイル内に書かれたカンマ区切りの数値を読込み、
       入力した値に最も近い値を表示するプログラムを作成せよ。


  • Category:
  • タグ:Linux
  • Author:eyes-software-co-jp

Excel®をコーディングに活用する方法

Excel®をコーディングに活用する方法について説明します。

1. セルのコピー&ペースト

まず、基本形としてエクセルのセルをテキストファイルにコピペした場合、どうなるかを見てみます。

エクセル上でセルを選択しコピーします。
エクセル画面
それを、テキストエディタ上でペーストします。
エディタ画面
そうすると、この様に、セル毎にタブで分けてコピペされます。(このエディタではタブは"^"で表示されています)
逆に、エディタ画面でコピーしたテキストをエクセルに張るにはタブと改行で区切ったテキストをエクセルへコピペします。

2. データコーディングへの応用

では、エクセルをコーディングに使用する場合、どの様な箇所に使用したらよいでしょうか。
簡単に考え付くのはデータコーディングでしょう。
Cのプログラミングで配列 fixdata[] に初期値を入れる場合を例に説明します。

まず、エクセル上で値を編集し、そのセルを選択しコピーします。
エクセル画面
プログラムは初期値を入れる所までコーディングしてあるものとします。
エディタ画面
プログラムの初期値の個所へペーストします。
コピーしたエディタ画面
字下げとカンマを入れて整形します。このさい、エディタの置換機能を使うと良いでしょう
整形したエディタ画面
設計書や別資料で値を記入したファイルがある場合、初期値を計算する必要がある場合等は特に有効です。

3. データ構造作成への応用

次にデータ仕様書から構造体を作成する例を示します。

この様なデータ仕様書があるとします。
データ仕様書
これを構造体のメンバ変数定義となるように文字列を編集する数式を使って加工します。
そして、メンバ変数定義となるところを選択し、コピーします。
加工したデータ仕様書
プログラムはメンバ変数定義を入れる所までコーディングしてあるものとします。
そして、エクセルでコピーした物をここへペーストします。
エディタ画面
その後、コピーした物に字下げや修正を入れて整形します。
整形したエディタ画面
これで完成です。

他にもエクセルを活用できるところはあります。状況に応じてエクセルをコーディングに活用してください。

  • Category:
  • Author:eyes-software-co-jp

ソケット通信サンプル

C言語でのソケット通信の手順について説明します。

サーバー側
ソケット通信において、通信先からの接続を待つ側がサーバーとなります。

1.socket()にてソケットを生成する。
2.getservbyname()にてポート情報を得る。
  これは/etc/servicesをサービス名で検索し、ポート番号を求めるための手順であり、ポート番号を決め打ちする場合は必要ない。
  ポート番号を決め打ちする場合には、bind()の引数のsockaddr_in構造体に addr.sin_port = htons( 50000 ); の様に、ポート番号をネットワークバイトオーダーで設定する。
3.bind()にてソケットにポート番号及びIPアドレスを設定する
4.listen()にて接続を待つ
5.accept()にて接続を確立する。
6.以後、acceptにて取得したソケットにてソケットの送受信を行う。
  先に生成した接続待ちの為のソケットと、acceptで取得した通信用のソケットの2つのソケットがあることに注意すること。

クライアント側
ソケット通信において、サーバーへ接続する側がクライアントとなります。

1.socket()にてソケットを生成する。
2.gethostbyname()にてホスト情報を得る。
  これは/etc/hostsをホスト名で検索し、IPアドレスを求めるための手順であり、IPアドレスを決め打ちする場合は必要ない。
  IPアドレスを決め打ちする場合には、connect()の引数のsockaddr_in構造体に sin.sin_addr.s_addr = inet_addr("127.0.0.1"); の様にIPアドレスを設定する。
3.getservbyname()にてポート情報を得る。
4.connect()にてサーバー側に接続する
5.以後、connectしたソケットにてソケットの送受信を行う。

========== サーバー側 ============


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

int main(int argc, char** argv)
{
    int fd;
    int fd2;
    struct servent *serv;
    struct sockaddr_in addr;
    socklen_t len = sizeof(struct sockaddr_in);
    struct sockaddr_in from_addr;
    char buf[2048];

    // 受信バッファを初期化する
    memset(buf, 0, sizeof(buf));
    // ソケットを作成する
    if (( fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) {
        fprintf( stdout, "socket error : fd = %d\n", fd );
        return -1;
    }

    serv = getservbyname( "vboxd", "tcp" );
    if ( !serv ){
        close( fd );
        return -1;
    }
    // IPアドレス、ポート番号を設定
    addr.sin_family = AF_INET;
    addr.sin_port = serv->s_port;
    addr.sin_addr.s_addr = INADDR_ANY;
    // バインドする
    if ( bind( fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) {
        fprintf( stdout, "bind error\n" );
        return -1;
    }
    // パケット受信待ち状態とする。待ちうけるコネクト要求は1
    if ( listen( fd, 1 ) < 0 ) {
        fprintf( stdout, "listen error\n" );
        return -1;
    }
    // クライアントからの接続を確立する
    if (( fd2 = accept(fd, (struct sockaddr *)&from_addr, &len )) < 0 ) {
        fprintf( stdout, "accept error : fd2 = %d\n", fd2 );
        return -1;
    }
    // パケット受信
    if ( recv( fd2, buf, sizeof(buf), 0 ) < 0 ) {
        fprintf( stdout, "recv");
        return -1;
    }
    // パケット送受信用ソケットクローズ
    close(fd2);
    // 接続要求待ち受け用ソケットクローズ
    close(fd);
    // 受信データの出力
    fprintf( stdout, "%s\n", buf );
    return 0;
}
========== クライアント側 ==========

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

int main(int argc, char** argv)
{
    int fd;
    struct hostent *info;
    struct servent *serv;
    struct sockaddr_in addr;
    char buff[2048];

    // IPv4 TCP のソケットを作成する
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        fprintf( stdout, "socket error\n" );
        return -1;
    }
    // hostsからホスト情報をを読み出す
    info = gethostbyname("localhost");
    if ( !info ){
        close( fd );
        fprintf( stdout, "gethostbyname error\n" );
        return -1;
    }
    serv = getservbyname( "vboxd", "tcp" );
    if ( !serv ){
        close( fd );
        fprintf( stdout, "getservbyname error\n" );
        return -1;
    }
    // 送信先アドレスとポート番号を設定する
    addr.sin_family = AF_INET;
    addr.sin_port = serv->s_port;
    addr.sin_addr = *(struct in_addr *)(info->h_addr_list[0]);
    // サーバ接続
    connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    sprintf( buff, "Thi is send Message" );
    // パケットを送信
    if ( send( fd, buff, strlen(buff), 0 ) < 0 ) {
        close( fd );
        fprintf( stdout, "send error\n" );
        return -1;
    }
    close(fd);
    return 0;
}
============ ここまで ============

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。

  • Category:
  • タグ:C
  • Author:eyes-software-co-jp