C++言語(サーバ側)と、Java言語(クライアント)の入り乱れたややこしい分野から開始
ソケットとは
よく聞く通信プロトコルにTCP/IPがあると思う。
これはインターネット通信をする時に必要なプロトコルで、この通信をプログラムで使うには
TCP/IPとプログラムを結びつける口が必要になる。
その口がソケットと呼ばれるものであり、この通信をソケット通信とも呼ぶ
今回のお題は
サーバ側、クライアント側にそれぞれソケットという通信の口を作り、それをTCP/IPでつなげて通信を行う
具体的には、サーバで入力した文字をクライアント側で表示させる
これはインターネット通信をする時に必要なプロトコルで、この通信をプログラムで使うには
TCP/IPとプログラムを結びつける口が必要になる。
その口がソケットと呼ばれるものであり、この通信をソケット通信とも呼ぶ
今回のお題は
サーバ側、クライアント側にそれぞれソケットという通信の口を作り、それをTCP/IPでつなげて通信を行う
具体的には、サーバで入力した文字をクライアント側で表示させる
サーバ側の作り
サーバはラズパイを使う
適当にディレクトリを作って、cppファイルを作る
今回は「SocketTest.cpp」て名前でする
ソケットを作って通信する流れは下記な感じ
適当にディレクトリを作って、cppファイルを作る
今回は「SocketTest.cpp」て名前でする
ソケットを作って通信する流れは下記な感じ
- ソケット作成 まずはソケット作りから、
- // ソケットの作成
- if ((s1 = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- cout << "ソケット作成失敗" << endl;
- return -1;
- } else {
- cout << "ソケット作成成功" << endl;
- }
- ソケットのデータ設定 ソケットアドレス(sockaddr_in構造体)を初期化、設定を行う
- // ソケットアドレスの初期化と設定
- 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);
- 1,2で作ったソケットとデータを結びつける bind()により、ソケットとデータの関連つけを行う
- // 作成したソケットと、アドレスを関連つける(バインド)
- if (bind(s1, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
- cout << "バインド失敗" << endl;
- return -1;
- } else {
- cout << "バインド成功" << endl;
- }
- コネクションの受信準備 listen()により、何個のクライアントと通信するの?とかの受信準備を行う
- // クライアントからの接続の受付準備をする
- if (listen(s1, 1) < 0) {
- cout << "受付準備失敗" << endl;
- return -1;
- } else {
- cout << "受付準備成功" << endl;
- }
- コネクションの受信待機 accept()により、コネクトしたらクライアントのデータがclient_addrに格納され、クライアント側のソケットハンドルがs2に入る
- // クライアントからコネクション受付を待つ
- unsigned int len = sizeof(client_addr);
- if ((s2 = accept(s1, (struct sockaddr *) &client_addr, &len)) < 0) {
- cout << "コネクト失敗" << endl;
- return -1;
- } else {
- cout << "コネクト成功" << endl;
- }
- データ送信 write()によってchar配列のデータを送信する。
- // データ送信
- cout << "送信データを入力してください.-> ";
- cin.getline(buf,BUFSIZ);
- write(s2, buf, sizeof(buf));
- ソケットの終焉 最後にソケットを閉じる
- // ソケットを閉じる
- close(s1);
- close(s2);
socket()関数により、ソケット作成。作ったソケットのハンドルをs1(int型)で受ける
- /**
- * サーバ側。
- * ソケット通信にて、サーバで入力した文字をクライアントに送信するプログラム
- */
- #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 connection = new Socket(HOST_NAME, PORT_NO);
- データを受けれるようにする ソケットが生成できたら、サーバからのデータを受信する設定をおこなう
- // 受信データを読み込むBufferedReaderを生成
- InputStream istream = connection.getInputStream();
- InputStreamReader isr = new InputStreamReader(istream);
- BufferedReader reader = new BufferedReader(isr);
- // 受信するまで待機して、受信したらデータをString型で取得する
- String message = reader.readLine();
そのため、Socketインスタンス生成前に、サーバ側は実装手順5「コネクションの受信待機」まで準備しとかないといけない
もしできてなければ、生成時に、接続先が見つからないException(UnknownHostException)が投げられる
受信データはSocketインスタンスから、InputStreamを取り出し、BufferedReaderで読み込む
ひとまず、
ボタンを押すと、ソケット生成⇒繋ぎに行って、データを受信したら内容を表示するアプリを作る!
- マニフェスト定義 ネットワーク通信するので、ネットワーク許可をマニフェストに記載
- <-- 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を引数で渡して、通信処理を開始する感じ
22,23行目。サーバ側のIPアドレス、サーバ側実装時に設定したポート番号を入力を忘れずに
- // 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);
- }
- };
- }
通信のキモは、doInBackground()
※上記はjava7の「try-with-resources」文で書いてる。
Java6で実装する場合は「try-cath-finally」文で書き直しがいるので注意(クローズ処理書き忘れ注意)
完成?
実行手順はこんなかんじで
- サーバ側を起動させる ラズパイ上でコンパイル、実行を行う
- $ g++ SocketServer.cpp
- $ ./a.out
- ソケット作成成功
- バインド成功
- 受付準備成功
- 受付開始...
- クライアント側を接続させる Androidアプリの「SOCKET START」ボタン押下
- サーバ側で文字を送る
- 受付開始...
- コネクト成功
- 送信データを入力してください.-> あけましておめでとうございます!今年もよろしくお願いします!!
- $
うまくいかないときはボートがすでに使われてる可能性が高い
一回実行させて、「Clrt+C」とかで止め、正常クローズされてない的な..
そういう時は、リブート作戦!く
あれ?受信文字列の後ろに変なのついてるッ…!
「A.aeabi $ 6」ってなんだ?
文字コードあってないのかな?ちょっと調査中。。。
コネクション失敗とはなんですか?
返信削除解決方法ありますか?
ConnectExceptionの所でしょうか?
削除上記のプログラムだと、
サーバ側が「受付開始...」になっていない時に出ます
「受付開始...」でもConnectExceptionが出てるのなら
ExceptionのStackTrace出力させてみて、
java.net.ConnectException:XXXX
このXXXの部分にエラーメッセージが出るので、その文言含めてググってみてはいかがでしょうか
簡単に思いつく要因だと、
同じネットワークではない、とかかな
ラズパイ、Android端末両方とも同じWifiに繋げてみる
とかで解決できるかもしれません
返信送れました。同じネットワークにつないだことで解決しました。
削除ありがとうございます。