過去記事 www.shujima.work のC言語バージョンです.
Pythonだと何もかもが遅いので,C言語で書き直すことにします.
筆者の環境
- Raspberry Pi 3 × 2
- Raspbian 9.4( stretch )
- gcc version 6.3.0 20170516 (Raspbian 6.3.0-18+rpi1+deb9u1)

Raspberry Pi 3 Model B V1.2 (日本製) 国内正規代理店品
- 出版社/メーカー: Raspberry Pi
- 発売日: 2016/02/29
- メディア: Tools & Hardware
- この商品を含むブログを見る
前提
- Raspberry Piが2つある
目次
Cのプログラムが見たい方は下まで飛ばしてください。
1. ネットワーク接続
この内容はPythonと全く同じです。
Raspberry PiどうしをLANケーブルで直接接続します。
または、下図のようにハブを使って接続することもできます。ただし、うまく動かない場合の原因究明が面倒なので、最初は直結のほうがいいでしょう。ストレートケーブルorクロスケーブルはどちらでも大丈夫です。
接続したら、お互いのIPアドレスなどをチェックします。 下記記事を参照してください。
ただのチェックなので、IPアドレスなどが既に分かっている場合には飛ばして問題ありません。
2. UDP送受信チェック
次の送信の工程がうまくいくことを確認するのに必要です。 厳密には不要な工程ですが、失敗した時の原因を見極めるのに役立ちます。 面倒ならば飛ばしてください。 この節ではaptパッケージ
- tcpdump
- hping3
をインストールします。これらは次節以降のCプログラムの実行には直接関係しません。
tcpdumpをインストール[受信側]
以下のコマンドでtcpdumpをインストールします。
$ sudo apt-get install tcpdump
tcpdumpを受信状態にする[受信側]
以下のコマンドを打ちます
$ sudo tcpdump -A -n udp port 60000
すると2〜3行の
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
のような文字列がでます。これ以降、自分のIPアドレス宛に届いた60000番のUDPパケットを受信し、ターミナル上に表示する状態となります。 60000に特に意味はありません。 0〜65535であればなんでもよいです。ただし0〜49151は決まった利用目的が決められているため、この先の工程でインターネットに接続する時には、自由に使える49152〜65535をおすすめします。 この記事では60000に統一しています。
なお、受信を停止するには、Ctrl + Cを押します。
hping3をインストール[送信側]
以下のコマンドでhping3をインストールします。
$ sudo apt-get install hping3
ポートを指定してpingする[送信側]
以下のコマンドで、高度なpingを打ちます。
$ sudo hping3 -2 -p 60000 xxx.xxx.xxx.xxx
すると、tcpdumpを受信状態にしておいた受信側のターミナルに、こんなのが1秒ごとに表示されるはずです。
xx:xx:xx.xxxxxx IP xxx.xxx.xxx.xxx.xxxxx > xxx.xxx.xxx.xxx.60000: UDP, length 0 <...!..'.5....E.......@.`.......@..&.`......................
60000とUDPが確認できれば、あとはどうでもいいです。
3. C言語でUDP送信プログラム
送信側のプログラムを書きます。
文字列送信プログラム[送信側,クライアント側]
以下のページ等を参考にしています
#include <stdio.h> //printf(), perror() #include <sys/socket.h> //sendto(), socket() #include <unistd.h> //close() #include <arpa/inet.h> //htons(), inet_addr() #include <time.h> //time(), localtime() #include <string.h> //strlen(); const int PortNumber = 60000; const char *IPaddress = "169.254.46.83"; int main(int argc, char** argv) { struct sockaddr_in addr; int sock_df; //ソケット作成 sock_df = socket(AF_INET, SOCK_DGRAM, 0); //ソケット作成失敗 if(sock_df < 0) { perror("Couldn't make a socket"); return -1; } // 通信の設定 addr.sin_family = AF_INET; //IPv4を指定 addr.sin_port = htons(PortNumber); //ポート番号。ここでは60000を指定 addr.sin_addr.s_addr = inet_addr(IPaddress); //サーバー側のアドレス ssize_t send_status; const char *SendString = "Hello with UDP"; while(1) { // 送信 send_status = sendto(sock_df, SendString , strlen(SendString)+1 , 0, (struct sockaddr *)&addr, sizeof(addr) ); // 送信失敗 if(send_status < 0) { perror("send error"); return -1; } printf("send:%s\n",SendString); // 1秒wait time_t t; time(&t); struct tm start, *now; now = localtime(&t); start = *now; // nowの値をコピー while(now->tm_sec == start.tm_sec) { time(&t); localtime(&t); } } close(sock_df); return 0; }
IPアドレスは受信側のものを入力してください。
以上のCプログラムを"UDPsend.c"のような名前でどこかに保存します。 そして
$ cd (cプログラムのディレクトリ) $ gcc -o UDPsend UDPsend.c $ sudo ./UDPsend
で実行します。
gccはcをビルドするコマンドです。拡張子の無い実行可能な形式のUDPsendファイルが生成されます。
3行目./UDPsendで実行されます。sudoは付けなくても動きましたが念のため。
実行すると、tcpdumpを受信状態にしておいた受信側のターミナルに
xx:xx:xx.xxxxxx IP xxx.xxx.xxx.xxx.xxxxx > xxx.xxx.xxx.xxx.60000: UDP, length 13 E..)tf.... ...@...... .`..t.Hello with UDP.....
のように表示されます。関係ない文字列が前後にありますが、「Hello with UDP」の文字列が送られていることがわかります。 この表示が1秒ごとに増えれば成功です.
C言語でUDP数値送信プログラム[送信側,クライアント側]
上記のソースをさらに改変します。
#include <stdio.h> //printf(), perror() #include <sys/socket.h> //sendto(), socket() #include <unistd.h> //close() #include <arpa/inet.h> //htons(), inet_addr() #include <time.h> //time(), localtime() const int PortNumber = 60000; const char *IPaddress = "169.254.46.83"; int main(int argc, char** argv) { struct sockaddr_in addr; int sock_df; sock_df = socket(AF_INET, SOCK_DGRAM, 0); if(sock_df < 0) { perror("Couldn't make a socket"); return -1; } // 通信の設定 addr.sin_family = AF_INET; //IPv4を指定 addr.sin_port = htons(PortNumber); //ポート番号。ここでは60000を指定 addr.sin_addr.s_addr = inet_addr(IPaddress); //サーバー側のアドレス ssize_t send_status; double sendval = 3.141592654; while(1) { // 送信 send_status = sendto(sock_df, &sendval , sizeof(double) , 0, (struct sockaddr *)&addr, sizeof(addr) ); // 送信失敗 if(send_status < 0) { perror("send error"); return -1; } printf("send:%f\n",sendval); // 1秒wait time_t t; time(&t); struct tm start, *now; now = localtime(&t); start = *now; // nowの値をコピー while(now->tm_sec == start.tm_sec) { time(&t); localtime(&t); } } close(sock_df); return 0; }
文字列送信プログラムから数行だけ変えています。
これを実行すると
xx:xx:xx.xxxxxx IP xxx.xxx.xxx.xxx.xxxxx > xxx.xxx.xxx.xxx.60000: UDP, length 8 E..$.<@.@.>7.......S.].`....8./T.! @..........
受信側に表示されます。先ほどと違い、実行結果が入力と一致しません。 これは、double型のバイト列を数値に戻していないためです。 そのためには受信プログラムが必要です。
4. C言語でUDP数値受信プログラム
受信側のプログラムを書きます。
数値を受信する[受信側,サーバー側]
ソースは以下を参考にしています.
- [ C言語 ] UDP / IP でパケットの送受信を行う – 行け!偏差値40プログラマー
- C言語で学ぶソケットAPI入門 第1回 サーバ編 - Qiita
- ノンブロッキングソケット:Geekなぺーじ
#include <stdio.h> //printf(), perror() #include <sys/socket.h> //socket() #include <sys/ioctl.h> //ioctl() #include <netinet/in.h> //htons(), inet_addr() #include <string.h> //memset() #include <unistd.h> //close() #include <stdlib.h> //atof() const int PortNumber = 60000; const int BufLength = 2048; int main(int argc, char** argv) { struct sockaddr_in addr , from_addr; int sock_df; socklen_t from_addr_size; //ソケット作成 sock_df = socket(AF_INET, SOCK_DGRAM, 0); //ソケット作成失敗 if(sock_df < 0) { perror("Couldn't make a socket"); return -1; } //通信の設定 addr.sin_family = AF_INET; //IPv4を指定 addr.sin_port = htons(PortNumber); //ポート番号。ここでは60000を指定 addr.sin_addr.s_addr = INADDR_ANY; //クライアントを指定せず,全て受け付ける //addrとソケットの紐付け int bind_status; bind_status = bind(sock_df, (struct sockaddr *)&addr, sizeof(addr) ); if( bind_status < 0) { perror("bind"); return -1; } printf("bind success\n"); //受信バッファ char buf[BufLength]; memset(buf, 0, sizeof(buf)); //初期化 while(1) { int recv_status; //受信 recv_status = recvfrom(sock_df, buf, BufLength + 1, 0, (struct sockaddr *)&from_addr, &from_addr_size); if ( recv_status < 0)continue; buf[BufLength - 1]= '\0'; //もしbufに\0が含まれていなかったときでも正常にprintできるように printf("receive data:%s\n", buf); //バッファbufの先頭8バイトが送られてきた数値である。 //そこで、bufの先頭のアドレスをdoubleのアドレスとして読むことで、数値として読み出せる。 double *valp = (double *)&buf; printf("value:%f\n", *valp); } //ソケットをクローズ close(sock_df); return 0; }
送信側は先ほどのプログラム、受信側はこちらのプログラムを共に実行すると、受信側で
receive data:8�/T�! @ value:3.141593
が1秒ごとに表示されます。
下段の数値をプログラム内で利用することもできます。
5. お互いに送受信するプログラム
長くなったので別の記事で投稿しました.
プログラムが動かないなどの問題があればTwitterやお問い合わせフォームでお知らせいただきますと幸いです(返信できない可能性があります).