●TCP/IPで通信
まず、権限をアプリに追加しないと始まりません。
そこで、インターネットのアクセス権限を与える為に、
MyApplication?\app\src\main\AndroidManifest.xml
この場所にある、AndroidManifest.xmlファイルを開きます。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mail.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
</intent-filter>
</activity>
</application>
</manifest>
赤文字の部分を追加することによりインターネットのアクセス権限をアプリに追加する事が出来ます。
■クライアント側の通信
androidでTCP/IPで通信するにはjavaのSocketを使います。
クライアント側の通信では、
相手先のアドレスのポートに接続するソケットを用意して、
java.net.Socket socket = new java.net.Socket("アドレス",ポート番号);
データを取得するストリーム
java.io.InputStream in = socket.getInputStream();
データを送信するストリーム
java.io.OutputStream out = socket.getOutputStream();
により基本的に通信を行います。
次のプログラムではグーグルのhttpサーバーのポート80番にアクセスして返ってきた文字を表示します。
インターネットの通信はメインスレッドでは出来ない仕様になっているため、何らかの形でThreadを立てる必要があります。
androidらしく通信をAsyncTaskで行いました。
別に、通常のThreadを立てて通信しても問題なく行けます。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((android.widget.Button)findViewById(R.id.button))
.setOnClickListener(new android.view.View.OnClickListener() {
public void onClick(android.view.View view) {
asyncTask a=new asyncTask();
a.execute((Object)"www.google.com",(Object)80,new Object()) ;
}
});
}
private class asyncTask extends android.os.AsyncTask{
@Override
protected Object doInBackground(Object... obj){
//この関数は別スレッドで実行されるため、画面周りに関わる処理は出来ない。
//また、戻り値はonPostExecuteの引数に渡され、そこで画面周りの処理ができる。
try
{
java.net.Socket socket = new java.net.Socket((String)obj[0],(int)obj[1]);
java.io.InputStream in = socket.getInputStream();
java.io.OutputStream out = socket.getOutputStream();
java.io.BufferedReader br = new java.io.BufferedReader( new java.io.InputStreamReader(in, "UTF-8") );
java.io.BufferedWriter bw = new java.io.BufferedWriter( new java.io.OutputStreamWriter(out, "UTF-8") );
bw.write("GET / HTTP/1.0\n\n");
bw.flush();
String pos,str="";
while((pos = br.readLine()) != null){
str+=pos;
}
in.close();
out.close();
socket.close();
return (Object)str;
}
catch( Exception e )
{
return (Object)e.toString();
}
}
@Override
protected void onPostExecute(Object obj) {
//doInBackgroundの戻り値が引数に渡される
//メインスレッドで実行されるため、画面周りの処理ができる。
//画面にメッセージを表示する
android.content.Context context = getApplicationContext();
android.widget.Toast t=android.widget.Toast.makeText(context,(String)obj, android.widget.Toast.LENGTH_LONG);
t.show();
}
}
}
画面にボタンを張り付けてこのプログラムを実行すると、次の様になります。
グーグルのサーバーが送り返してくるHTMLコードが表示されているのが見えます。
■サーバー側の通信
androidでWEBサーバーを立てる事はあり得ないと思いますが、WEBサーバーの様に振る舞うプログラムを書いてみました。
まあ、サービスとして実行して、プログラム間で通信するのも昔っぽくていいかもしれません。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread th;
th = new Thread(new test());
th.start();
}
private class test implements Runnable {
@Override
public void run() {
try {
java.net.ServerSocket serverSocket = new java.net.ServerSocket(4444);
while (true) {
java.net.Socket socket = serverSocket.accept();
java.io.InputStream in = socket.getInputStream();
java.io.OutputStream out = socket.getOutputStream();
java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(in, "UTF-8"));
java.io.BufferedWriter bw = new java.io.BufferedWriter(new java.io.OutputStreamWriter(out, "UTF-8"));
String str="",pos;
while ((pos = br.readLine()) != null && !pos.equals("")) {
str += pos;
}
bw.write("HTTP/1.1 200 OK\n\n<HTML>" + str + "</HTML>");
bw.flush();
in.close();
out.close();
socket.close();
}
} catch (Exception e) {}
}
}
}
まず、最初の赤文字で示したこの行で、
java.net.ServerSocket serverSocket = new java.net.ServerSocket(4444);
ポート番号4444を開きます。
本来のHTTPサーバーの80番は使用中みたいなので適当に決めました。
次に、
java.net.Socket socket = serverSocket.accept();
この行で接続されるまで待機します。
つまり、プログラム自体がこの行で停止するわけです。
次の赤文字部分のこの部分は文字が空文字だったらループを終了する部分です。
!pos.equals("")
クライアントの場合はNULLで来ますが、サーバー側は受信文字終了が空文字で来るとはなんだか滅茶苦茶だと思いません?
ちなみにStringをバッファの代わりに使ってますが、良くない書き方なのでマネしないでくださいね。
まあ、動けばいっか!!
ここの部分でHTML文を作成しています。
"HTTP/1.1 200 OK\n\n<HTML>" + str + "</HTML>"
受信した文字をHTMLにして返す処理をしています。
ブラウザによってこの文字HTTP/1.1 200 OK\n\nが無いと表示されないとかあるため曲者です。
このプログラムを実行して、閉じずにブラウザを開き、http://localhost:4444にアクセスすると、次の様になるはずです。
ブラウザ自身の送信したデータがブラウザに表示されているのがわかります。
ちなみにlocalhostはhostsにより自分自身のIPアドレスに変換され、:4444は接続先ポート番号の指定です。
■サーバーをマルチスレッドにする
上のプログラムはシングルスレッドで一度に一つの接続しか受け付けませんでしたが、複数の接続を受け付けるために、マルチスレッドにします。
サーバーに接続される前は、serverSocket.accept();の部分で接続されるのを待機していて、接続されたらsocketを返します。
そのsocketを受け取った時点で新しいスレッドを立てて通信することにより複数の接続を受け付ける事が出来ます。
場合によっては、パフォーマンス向上のためにスレッドプールを作成した方がいいかもしれません。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable(){
@Override
public void run() {
try {
java.net.ServerSocket serverSocket = new java.net.ServerSocket(4444);
while (true) {
java.net.Socket socket = serverSocket.accept();
new Thread(new Runnable() {
java.net.Socket socket;
public Runnable setSocket(java.net.Socket _socket){
socket=_socket;
return this;
}
@Override
public void run() {
try{
java.io.InputStream in = socket.getInputStream();
java.io.OutputStream out = socket.getOutputStream();
java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(in, "UTF-8"));
java.io.BufferedWriter bw = new java.io.BufferedWriter(new java.io.OutputStreamWriter(out, "UTF-8"));
String str="",pos;
while ((pos = br.readLine()) != null && !pos.equals("")) {
str += pos;
}
bw.write("HTTP/1.1 200 OK\n\n<HTML>"+new java.util.Date().toString()+"<br>" + str + "</HTML>");
bw.flush();
in.close();
out.close();
socket.close();
}catch(Exception e){
message(e.toString());
}
}
}.setSocket(socket)).start();
}
} catch (Exception e) {
message(e.toString());
}
}
}).start();
}
android.os.Handler handler=new android.os.Handler();
void message(String _str){
final String str=_str;
handler.post(new Runnable() {
@Override
public void run() {
android.content.Context context = getApplicationContext();
android.widget.Toast t=android.widget.Toast.makeText(context,str, android.widget.Toast.LENGTH_LONG);
t.show();
}
});
}
}
赤い部分がサーバーに接続されたsocketを新たなスレッドに渡しています。
すべて無名クラスでスレッドを扱ってるので少々見慣れない処理がありますね。
例えば、最初の紫色の部分の
public Runnable setSocket(java.net.Socket _socket)
この関数の戻り値では自分自身を返すため、この関数を呼び出した後に、別の関数を呼ぶことができます。
また、最後の方の紫色の部分では、
final String str=_str
文字列を定数にすることによりスレッド内からアクセスできるようにしています。
■レンタルサーバーに掲示板を作成してAndroidから書き込む
まずは、レンタルサーバーに用意するシンプルな書き込み可能なcgiを準備します。
#!/usr/bin/perl
#Perlで作られた書き込み専用掲示板
#ファイル data.txt を同一ディレクトリに 空で作成してパーミッションを644にする
print "Content-type: text/html; charset=Shift_JIS\n\n";
#ログの最大数
$Maxlog=1000;
#0だと下から上に
#1だと上から下に書き込む
$view=0;
$filename="data.txt";
#ログファイル読み込み
open(IN, $filename);
@log = <IN>;
close(IN);
read (STDIN, $data, $ENV{'CONTENT_LENGTH'});
my @list = split(/;/, $data);
my @arry = ();
foreach (@list) {
my @mylist;
@mylist = split(/=/, $_);
$arry{$mylist[0]}=$mylist[1];
}
#フォームからの書き込みのときは書き込み処理を行なう
#writeは荒らし対策としてパスワードのように使う
#androidアプリ側も同じ値に合わせる必要あり。
if ($arry{'write'} eq "pass") {
# フォームの値を取得
$message = $arry{'message'};
# タグの無効化
&deltag($message);
# 改行を<br>に変換
$message =~ s/\r\n/<br>/g; # Windows系(\r\n)
$message =~ s/\r/<br>/g; # Mac系(\r)
$message =~ s/\n/<br>/g; # UNIX系(\n)
#現在時刻を取得
$time = `date +'%Y/%m/%d %H:%M:%S'`;
chop $time;
$status=sprintf( "%15s", $ENV{REMOTE_ADDR});
$status=~ s/ /&ensp\;/g;
#ログに書き込み内容を格納
if($view){
#上から下に
push @log, "$time : $status : $message\n";
}else{
#下から上に
unshift @log, "$time : $status : $message\n";
}
#ログ最大数を制限
# splice @log, $Maxlog;
for($i=$Maxlog;$i<=$#log;$i++){
if($view){
#上から下に
shift @log;
}else{
#下から上に
pop @log;
}
}
# ログファイルにロックをかけて書き込み
open(OUT, "+< $filename") or die "open: $!";
flock(OUT, 2) or die "flock: $!";
truncate(OUT, 0) or die "truncate: $!";
seek(OUT, 0, 0) or die "seek: $!";
print OUT @log or die "print: $!";
close(OUT);
exit;
}
print "<html>";
foreach (@log) {
my @mylist;
print "$_<br>";
}
if($view){
#上から下に
}else{
#下から上に
#60秒毎に再読込
print "<meta http-equiv=\"refresh\" content=\"60\">";
}
print "</html>";
# タグを除去するサブルーチン
sub deltag {
$_[0] =~ s/</</g;
$_[0] =~ s/>/>/g;
}
このプログラムをレンタルサーバー上に/test/index.cgiで保存してパーミッションを変更して動作するようにします。
このCGIをレンタルサーバーで動かすとプログラムからの書き込みと、ブラウザからの表示が出来るようになります。
▼このCGIに対してandroidから書き込みます
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((android.widget.Button)findViewById(R.id.button))
.setOnClickListener(new android.view.View.OnClickListener() {
public void onClick(android.view.View view) {
asyncTask a=new asyncTask();
a.execute((Object)"●●●.net",(Object)80,new Object()) ;
}
});
}
private class asyncTask extends android.os.AsyncTask{
@Override
protected Object doInBackground(Object... obj){
//この関数は別スレッドで実行されるため、画面周りに関わる処理は出来ない。
//また、戻り値はonPostExecuteの引数に渡され、そこで画面周りの処理ができる。
try
{
java.net.Socket socket = new java.net.Socket((String)obj[0],(int)obj[1]);
java.io.InputStream in = socket.getInputStream();
java.io.OutputStream out = socket.getOutputStream();
java.io.BufferedReader br = new java.io.BufferedReader( new java.io.InputStreamReader(in, "UTF-8") );
java.io.BufferedWriter bw = new java.io.BufferedWriter( new java.io.OutputStreamWriter(out, "UTF-8") );
String message="test message";
String sendWord="POST /test/index.cgi HTTP/1.0\r\n"
+"Host: " + (String)obj[0] + "\r\n"
+"Content-Type: application/x-www-form-urlencoded\r\n"
+"Content-Length: "+(30+message.length())+"\r\n\r\n"
+"message=" + message + ";write=pass;\r\n\r\n"
+"\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n";
bw.write(sendWord);
bw.flush();
String pos,str="";
while((pos = br.readLine()) != null){
str+=pos;
}
in.close();
out.close();
socket.close();
return (Object)str;
}
catch( Exception e )
{
return (Object)e.toString();
}
}
@Override
protected void onPostExecute(Object obj) {
//doInBackgroundの戻り値が引数に渡される
//メインスレッドで実行されるため、画面周りの処理ができる。
//画面にメッセージを表示する
android.content.Context context = getApplicationContext();
android.widget.Toast t=android.widget.Toast.makeText(context,(String)obj, android.widget.Toast.LENGTH_LONG);
t.show();
}
}
}
上の方のクライアント側の通信プログラムに対して赤文字部分が変更した部分です。
●●●.netはCGIが置かれたサーバーのアドレスを指定します。
実行するとインターネット上の掲示板CGIに test message の文字を書き込みをすることができます。
▲トップページ
>
android