検索
連載

バグ検出ドリル(18)IoT時代だからこそ重要、単純でも検出が難しい通信系バグ山浦恒央の“くみこみ”な話(118)(3/4 ページ)

バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第18回の問題は、相互に通信する機器のソフトウェアのバグです。IoT時代を迎えて利用場面が増えている、通信系のバグを見つけ出しましょう!

Share
Tweet
LINE
Hatena

プログラムリストと実行結果

/*
	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」を入力し、クライアントとサーバのプログラムが終了した
図1
図1 実行結果(左側:クライアント、右側:サーバ)

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る