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に繋げてみる
とかで解決できるかもしれません
返信送れました。同じネットワークにつないだことで解決しました。
削除ありがとうございます。