C++言語(サーバ側)と、Java言語(クライアント)の入り乱れたややこしい分野から開始
ソケットとは
よく聞く通信プロトコルにTCP/IPがあると思う。
これはインターネット通信をする時に必要なプロトコルで、この通信をプログラムで使うには
TCP/IPとプログラムを結びつける口が必要になる。
その口がソケットと呼ばれるものであり、この通信をソケット通信とも呼ぶ
今回のお題は
サーバ側、クライアント側にそれぞれソケットという通信の口を作り、それをTCP/IPでつなげて通信を行う
具体的には、サーバで入力した文字をクライアント側で表示させる
これはインターネット通信をする時に必要なプロトコルで、この通信をプログラムで使うには
TCP/IPとプログラムを結びつける口が必要になる。
その口がソケットと呼ばれるものであり、この通信をソケット通信とも呼ぶ
今回のお題は
サーバ側、クライアント側にそれぞれソケットという通信の口を作り、それをTCP/IPでつなげて通信を行う
具体的には、サーバで入力した文字をクライアント側で表示させる
サーバ側の作り
サーバはラズパイを使う
適当にディレクトリを作って、cppファイルを作る
今回は「SocketTest.cpp」て名前でする
ソケットを作って通信する流れは下記な感じ
適当にディレクトリを作って、cppファイルを作る
今回は「SocketTest.cpp」て名前でする
ソケットを作って通信する流れは下記な感じ
- ソケット作成 まずはソケット作りから、
- ソケットのデータ設定 ソケットアドレス(sockaddr_in構造体)を初期化、設定を行う
- 1,2で作ったソケットとデータを結びつける bind()により、ソケットとデータの関連つけを行う
- コネクションの受信準備 listen()により、何個のクライアントと通信するの?とかの受信準備を行う
- コネクションの受信待機 accept()により、コネクトしたらクライアントのデータがclient_addrに格納され、クライアント側のソケットハンドルがs2に入る
- データ送信 write()によってchar配列のデータを送信する。
- ソケットの終焉 最後にソケットを閉じる
socket()関数により、ソケット作成。作ったソケットのハンドルをs1(int型)で受ける
// ソケットの作成 if ((s1 = socket(AF_INET, SOCK_STREAM, 0)) < 0) { cout << "ソケット作成失敗" << endl; return -1; } else { cout << "ソケット作成成功" << endl; }
// ソケットアドレスの初期化と設定 memset((char*) &server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT);
// 作成したソケットと、アドレスを関連つける(バインド) if (bind(s1, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) { cout << "バインド失敗" << endl; return -1; } else { cout << "バインド成功" << endl; }
// クライアントからの接続の受付準備をする if (listen(s1, 1) < 0) { cout << "受付準備失敗" << endl; return -1; } else { cout << "受付準備成功" << endl; }
// クライアントからコネクション受付を待つ unsigned int len = sizeof(client_addr); if ((s2 = accept(s1, (struct sockaddr *) &client_addr, &len)) < 0) { cout << "コネクト失敗" << endl; return -1; } else { cout << "コネクト成功" << endl; }
// データ送信 cout << "送信データを入力してください.-> "; cin.getline(buf,BUFSIZ); write(s2, buf, sizeof(buf));
// ソケットを閉じる close(s1); close(s2);
/** * サーバ側。 * ソケット通信にて、サーバで入力した文字をクライアントに送信するプログラム */ #include <iostream> #include <cstdio> #include <cstring> #include <unistd.h> /* read(),write(),close() */ #include <sys/socket.h> /* socket(), bind(), listen(), accept() */ #include <netinet/in.h> /* struct sockaddr_in */ #define PORT (23456) using namespace std; int main(int argc, char *argv[]) { int s1, s2; // ソケットのハンドル struct sockaddr_in server_addr, client_addr; // ソケットアドレスのデータ構造体 char buf[BUFSIZ]; // 送信データ // ソケットの作成 if ((s1 = socket(AF_INET, SOCK_STREAM, 0)) < 0) { cout << "ソケット作成失敗" << endl; return -1; } else { cout<< "ソケット作成成功" << endl; } // ソケットアドレスの初期化と設定 memset((char*) &server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 作成したソケットと、アドレスを関連つける(バインド) if (bind(s1, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) { cout << "バインド失敗" << endl; return -1; } else { cout << "バインド成功" << endl; } // クライアントからの接続の受付準備をする if (listen(s1, 1) < 0) { cout << "受付準備失敗" << endl; return -1; } else { cout << "受付準備成功" << endl; } // クライアントからコネクション受付を待つ cout << "受付開始..." << endl; unsigned int len = sizeof(client_addr); if ((s2 = accept(s1, (struct sockaddr *) &client_addr, &len)) < 0) { cout << "コネクト失敗" << endl; return -1; } else { cout << "コネクト成功" << endl; } // データ送信 cout << "送信データを入力してください.-> "; cin.getline(buf,BUFSIZ); write(s2, buf, sizeof(buf)); close(s1); close(s2); return 0; }
クライアント側の作り
クライアントはAndroid端末を使う
ソケット通信の手段はJavaで用意されてるクラスを使うため、Androidにこだわる必要はないんだけどねw
ソケットの手順はこんな感じ
ひとまず、
ボタンを押すと、ソケット生成⇒繋ぎに行って、データを受信したら内容を表示するアプリを作る!
レイアウト定義
ソケット生成ボタンと受信テキスト表示Viewを1個ずつ配置する
実装
Activityに通信処理を書く
通信処理は以前紹介したAsyncTaskを使った通信処理でする
ボタンをClickされたとき、AcyncTaskを生成、更新するViewを引数で渡して、通信処理を開始する感じ
通信のキモは、doInBackground()
※上記はjava7の「try-with-resources」文で書いてる。
Java6で実装する場合は「try-cath-finally」文で書き直しがいるので注意(クローズ処理書き忘れ注意)
ソケット通信の手段はJavaで用意されてるクラスを使うため、Androidにこだわる必要はないんだけどねw
ソケットの手順はこんな感じ
- ソケット作成 Socketインスタンスを生成した時点で、サーバー側は実装手順5「コネクションの受信待機」が完了する
- データを受けれるようにする ソケットが生成できたら、サーバからのデータを受信する設定をおこなう
そのため、Socketインスタンス生成前に、サーバ側は実装手順5「コネクションの受信待機」まで準備しとかないといけない
もしできてなければ、生成時に、接続先が見つからないException(UnknownHostException)が投げられる
// ソケットを作成 Socket connection = new Socket(HOST_NAME, PORT_NO);
受信データはSocketインスタンスから、InputStreamを取り出し、BufferedReaderで読み込む
// 受信データを読み込むBufferedReaderを生成 InputStream istream = connection.getInputStream(); InputStreamReader isr = new InputStreamReader(istream); BufferedReader reader = new BufferedReader(isr); // 受信するまで待機して、受信したらデータをString型で取得する String message = reader.readLine();
ひとまず、
ボタンを押すと、ソケット生成⇒繋ぎに行って、データを受信したら内容を表示するアプリを作る!
- マニフェスト定義 ネットワーク通信するので、ネットワーク許可をマニフェストに記載
<-- AndroidManifest.xml --> <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.kazuki.socketsample" > <application ・ ・ ・ </application> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
<-- activity_main.xml --> <?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/socket_open" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="SOCKET OPEN"/> <TextView android:id="@+id/socket_message" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="CLICK BUTTON."/> </LinearLayout>
通信処理は以前紹介したAsyncTaskを使った通信処理でする
ボタンをClickされたとき、AcyncTaskを生成、更新するViewを引数で渡して、通信処理を開始する感じ
// MainActivity.java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Click処理を実装 findViewById(R.id.socket_open).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { TextView v = (TextView)MainActivity.this.findViewById(R.id.socket_message); new MyAsyncTask(v).execute(); return; } }); } /** * ソケット通信を行い、TextViewの更新を行うクラス. */ static class MyAsyncTask extends AsyncTask<Void,Void,String> { private static final String HOST_NAME = "XXX.XXX.XX.XX"; // ラズパイのIPアドレス private static final int PORT_NO = 23456; // ソケット通信するポート番号 final TextView mSocketText; public MyAsyncTask(TextView v) {mSocketText = v;} @Override protected void onPreExecute() { mSocketText.setText("通信中...."); return ; } @Override protected String doInBackground(Void[] params) { String message = ""; try (Socket connection = new Socket(HOST_NAME, PORT_NO); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));) { message = reader.readLine(); if(message == null) throw new NullPointerException(); }catch(NullPointerException e){ message = "読み込みデータがないよ(>_<)"; }catch(ConnectException e){ message = "コネクション失敗(>_<)"; } catch(UnknownHostException e) { message = "ホストが見つかりません(>_<)"; } catch(SocketException e) { message = "通信エラー(>_<)"; } catch (IOException e) { message = "読み込み失敗エラー(>_<)"; } return message; } @Override protected void onPostExecute(String message) { mSocketText.setText(message); } }; }22,23行目。サーバ側のIPアドレス、サーバ側実装時に設定したポート番号を入力を忘れずに
通信のキモは、doInBackground()
※上記はjava7の「try-with-resources」文で書いてる。
Java6で実装する場合は「try-cath-finally」文で書き直しがいるので注意(クローズ処理書き忘れ注意)
完成?
実行手順はこんなかんじで
- サーバ側を起動させる ラズパイ上でコンパイル、実行を行う
- クライアント側を接続させる Androidアプリの「SOCKET START」ボタン押下
- サーバ側で文字を送る
$ g++ SocketServer.cpp $ ./a.out ソケット作成成功 バインド成功 受付準備成功 受付開始...うまくいかないときはボートがすでに使われてる可能性が高い
一回実行させて、「Clrt+C」とかで止め、正常クローズされてない的な..
そういう時は、リブート作戦!く
受付開始... コネクト成功 送信データを入力してください.-> あけましておめでとうございます!今年もよろしくお願いします!! $あれ?受信文字列の後ろに変なのついてるッ…!
「A.aeabi $ 6」ってなんだ?
文字コードあってないのかな?ちょっと調査中。。。
コネクション失敗とはなんですか?
返信削除解決方法ありますか?
ConnectExceptionの所でしょうか?
削除上記のプログラムだと、
サーバ側が「受付開始...」になっていない時に出ます
「受付開始...」でもConnectExceptionが出てるのなら
ExceptionのStackTrace出力させてみて、
java.net.ConnectException:XXXX
このXXXの部分にエラーメッセージが出るので、その文言含めてググってみてはいかがでしょうか
簡単に思いつく要因だと、
同じネットワークではない、とかかな
ラズパイ、Android端末両方とも同じWifiに繋げてみる
とかで解決できるかもしれません
返信送れました。同じネットワークにつないだことで解決しました。
削除ありがとうございます。