連載
バグ検出ドリル(18)IoT時代だからこそ重要、単純でも検出が難しい通信系バグ:山浦恒央の“くみこみ”な話(118)(3/4 ページ)
バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第18回の問題は、相互に通信する機器のソフトウェアのバグです。IoT時代を迎えて利用場面が増えている、通信系のバグを見つけ出しましょう!
プログラムリストと実行結果
/* ClientMusicSim.c 音楽プレーヤー(クライアント) */ #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <unistd.h> //送信データ typedef struct{ int in; //モード遷移指令の入力値 int id; //ユーザーID char password[10]; //パスワード }SendMessage; //受信データ typedef struct{ int mode; //モード }RecvMessage; //送受信データの初期化 void Init(SendMessage *send_data, RecvMessage *recv_data){ //ユーザーID:100、パスワード:adminとする send_data->in = 0; send_data->id = 100; strcpy(send_data->password, "admin"); recv_data->mode = 0; } // モード出力 void Output(int result){ if (result == 0) { printf("電源OFFモード\n"); } else if (result == 1) { printf("アイドルモード\n"); } else if (result == 2) { printf("再生モード\n"); } else if (result == 3) { printf("一時停止\n"); } else { printf("定義されていないモードです\n"); } } int main(void){ int sock; //ソケット変数 struct sockaddr_in server_addr; //サーバ側アドレス構造体 SendMessage send_data; //送信データ構造体の宣言 RecvMessage recv_data; //受信データ構造体の宣言 int count; //ループ回数 //送受信データの初期化 Init(&send_data, &recv_data); // ソケット作成 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //IPアドレスをローカルホスト(127.0.0.1)に設定 //ポート番号を12345に設定 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(12345); //コネクト処理を実行 connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); printf("0:電源ON/OFF 1:再生/一時停止 2:停止\n"); count = 1; while(1){ //モード遷移指令の入力 printf("(%d) モード遷移指令の入力: ",count); scanf("%d",&send_data.in); //データ送信 send(sock, &send_data, sizeof(send_data), 0); //データ受信 recv(sock, &recv_data, sizeof(recv_data), 0); //モードの表示 Output(recv_data.mode); count++; } //ソケットをクローズ close(sock); return 0; }
リスト2 音楽プレーヤーのプログラム(クライアント)
/* ServerMusicSim.c 音楽プレーヤー(サーバ) */ #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <netinet/in.h> #include <unistd.h> #include <stdlib.h> //モード定義 typedef enum{ MODE_POWER_OFF, //電源OFFモード MODE_IDLE, //アイドルモード MODE_PLAY, //再生モード MODE_PAUSE //一時停止モード }MODE; //モード遷移指令 typedef enum{ ACT_POWER_ONOFF, //電源ON/OFF指令 ACT_PLAY, //再生指令 ACT_STOP //停止指令 }ACT; //送信データ typedef struct{ int mode; //モード遷移指令(受信) }SendMessage; //受信データ typedef struct{ int in; //モード遷移指令の入力値 char password[10]; //パスワード int id; //ユーザーID }RecvMessage; int gMode; //モード変数(グローバル) void init(SendMessage *send_data, RecvMessage *recv_data){ //送受信データの設定 recv_data->in = 0; recv_data->id = 100; strcpy(recv_data->password, "admin"); send_data->mode = 0; } void modeset(int in){ switch(gMode){ // 電源OFFモードの場合 case MODE_POWER_OFF: if (in == ACT_POWER_ONOFF) gMode = MODE_IDLE; break; // アイドルモードの場合 case MODE_IDLE: if (in == ACT_PLAY){ gMode = MODE_PLAY; } else if (in == ACT_POWER_ONOFF){ gMode = MODE_POWER_OFF; } break; // 再生モードの場合 case MODE_PLAY: if (in == ACT_STOP){ gMode = MODE_IDLE; } else if (in == ACT_PLAY){ gMode = MODE_PAUSE; } else if (in == ACT_POWER_ONOFF){ gMode = MODE_POWER_OFF; } break; // 一時停止モードの場合 case MODE_PAUSE: if (in == ACT_STOP){ gMode = MODE_IDLE; } else if (in == ACT_PLAY){ gMode = MODE_PLAY; } else if (in == ACT_POWER_ONOFF){ gMode = MODE_POWER_OFF; } break; default: break; } } int main() { int server_sock, client_sock; //ソケット変数の宣言 struct sockaddr_in server_addr, client_addr; //アドレス構造体の宣言 int client_len; //クライアント側アドレス構造体のサイズ SendMessage send_data; //送信データ構造体の宣言 RecvMessage recv_data; //受信データ構造体の宣言 int count; //ループ回数 //データの初期化 init(&send_data, &recv_data); //ソケットを作成 server_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // サーバ側のアドレス構造体の設定 // IPアドレスを任意、ポート番号を12345に設定 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(12345); // バインド、リッスンを実行 bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); listen(server_sock, SOMAXCONN); //クライアントからの接続要求を待機 client_len = sizeof(client_addr); client_sock = accept(server_sock, (struct sockaddr *) &client_addr, &client_len); count = 1; while (1){ printf("(%d) ",count); //クライアントからの受信メッセージ int n=0; if (n = recv(client_sock, &recv_data, sizeof(recv_data), 0) != sizeof(recv_data)){ return -1; } //IDとパスワードを確認し、一致した場合はモードを変更する if (recv_data.id == 100 && strcmp(recv_data.password,"admin") == 0){ modeset(recv_data.in); } else { printf("IDかパスワードが一致しません\n"); //デバッグ用 受信データの出力 printf("モード遷移指令: %d \n",recv_data.in); printf("パスワード: %s\n",recv_data.password); printf("ユーザーID: %d\n",recv_data.id); } //モードをセットし、クライアントにモードを送信する send_data.mode = gMode; send(client_sock, &send_data, sizeof(send_data), 0); count++; } //ソケットをクローズ close(client_sock); return 0; }
リスト3 音楽プレーヤーのプログラム(サーバ)
以下の入力を実行した結果を図1に示す。
- ①クライアントから「0(電源ON/OFF指令)」を入力し、サーバに送信する。サーバは、指令を受信したが、「IDかパスワードが一致しません」と出力した
- ②クライアントから「1(再生/一時停止指令)」を入力し、サーバに送信する。サーバは、指令を受信したが、「IDかパスワードが一致しません」と出力した
- ③クライアントから「Ctrl+c」を入力し、クライアントとサーバのプログラムが終了した
Copyright © ITmedia, Inc. All Rights Reserved.