週末のアプリ作成

androidアプリ個人開発者の実態を報告します。アプリの収入も公開中です♪

【Android ソケット通信】サーバーとクライアントの実装(スマホ〜PC間、スマホ〜スマホ間の通信)※サンプルソース公開

Android ソケットサーバーとソケットクライアントの簡易アプリを実装(サンプルソース公開)

こんにちはTF's apps(滋賀のアプリ開発者)です。なぜか年の瀬にAndroid でソケット通信プログラムを作成しています、ちょっと本業で使えると思いましたの勉強も兼ねて作ってみました、その時のソースコードを紹介します、興味のある方はぜひ参考の一つとして使用頂ければと思います。

■アプリの動作です

使い方は次の通りです。それぞれにアプリをインストールして、起動させた後、下記の手順に従い操作すると相互通信をスタートします。クライアント側は「テストメッセージ」を送信し、サーバー側は「私はサーバーです」を応答する、ただそれを繰り返すだけのシンプルな通信アプリです。

サーバー側

①ソケット「サーバー」のモードを選択

②「START」ボタンを押下

クライアント側

①ソケット「クライアント」のモードを選択

②サーバーのIPアドレスを入力(例:192.168.1.17)

③(事前にPINGボタンでサーバーとの導通確認を勧めます)

④「START」ボタンを押下

(1)動作環境

まずは今回構築したシステムの概要です、ごくごく一般的な環境で構築しています。この記事を読んでいる方は皆さん似たような環境だと思います。家庭用の無線ルーターを使用して、PC(Android Studio エミュレータ)とスマホに同じアプリをインストールさせてします、PC側をクライアント、スマホ側をサーバーにしています。注意点としてはスマホサーバーにすることです、同じプログラムでPC側サーバーにするとうまく通信できませんでした、理由としてはエミュレータ側のwindowサイズが0バイトとなってしまうためでした。エミュレータ側のwindowサイズを設定する方法がわからなかったので、調査するのを打ち切っています。なので、下記の構成の通り、スマホ側をサーバー、PC側をクライアントで使用ください。(もし、スマホ2台で使用するのなら全く問題ありません。どちらをサーバー、クライアントにするかは特に気にする必要がありません)

(2)ソケットクライアント

ソケットクライアントとして、MyClientというクラスを作成しています。そのメソッドとして、接続:Connect、切断:DisConnect、送信:SendMessage、受信:RecvMessageを用意しています。ポート番号は2002を使用しています。では実際のソースコードとなります。

package tfsapps.sockapp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

public class MyClient {

public String str_status = "";
private Socket cSocket = null;
private OutputStream writer = null; //書込み
private InputStream reader = null; //読込み
private String ipaddress = "192.168.1.17";
private int counter = 0;

public MyClient(String ipaddr) {
if(ipaddr.isEmpty() == false) {
ipaddress = ipaddr;
}
}

/*********************************
ソケット接続
*********************************/
public boolean Connect() {
str_status += "[1] 接続中\n";
InetSocketAddress _socket = new InetSocketAddress(ipaddress, 2002);
try {
cSocket = new Socket();
cSocket.connect(_socket, 1000);
}
catch (Exception e) {
e.printStackTrace();
return false;
}
str_status += "[2] 接続完了\n";
return true;
}
/*********************************
ソケット切断
*********************************/
public boolean DisConnect() {
try {
cSocket.close();
cSocket = null;
writer.close();
reader.close();
}
catch (Exception e) {
}
return true;
}

/*********************************
メッセージ送信
*********************************/
public boolean SendMessage(String temp) {

if (cSocket.isConnected() == false){
return false;
}

str_status = "[3] テキスト送信中です・・・\n";

// テキスト送信
try {
writer = cSocket.getOutputStream();
counter++;
String str = (temp + counter);
writer.write(str.getBytes("UTF-8"));
str_status += "送信>>>:"+str+"\n";

} catch (IOException e) {
return false;
}
return true;
}

/*********************************
メッセージ受信
*********************************/
public Boolean RecvMessage() {

String temp = "";
byte w[] = new byte[4048];
int size;
if (cSocket.isConnected() == false){
return false;
}
str_status += "[4] テキスト受信中です・・・\n";

// テキスト受信
try {
reader = cSocket.getInputStream();
size = reader.read(w);
if (size <= 0) {
return false;
}
else{
temp = new String(w, 0, size, "UTF-8");
}
str_status += "受信<<<:"+temp+"\n";

} catch (IOException e) {
return false;
}
return true;
}
}

(3)ソケットサーバー

次にソケットサーバーです、同じくMyServerというクラスを用意しています。そのメソッドとして、通信受付:Accept、切断:DisConnect、受信:RecvMessage、送信:SendMessageを作成しています。

package tfsapps.sockapp;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {
/**
* ソケット通信(サーバー側)
*/
private String ipaddress = "127.0.0.1";
public String recv_mess = "";
private ServerSocket sSocket = null;
private Socket socket = null;
private InputStream reader = null; //読込み
private OutputStream writer = null; //書込み

public MyServer() {
;
}

/*********************************
ソケット受付処理
*********************************/
public boolean Accept(int port) {
int myport = 0;
try {
if (sSocket != null) {
return true;
}

if (port == 0) myport = 2002;
else myport = port;

//IPアドレスとポート番号を指定してサーバー側のソケットを作成
sSocket = new ServerSocket(myport);
socket = sSocket.accept();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/*********************************
ソケット切断
*********************************/
public boolean DisConnect() {
try {
socket.close();
socket = null;
writer.close();
reader.close();
}
catch (Exception e) {
}
return true;
}

/*********************************
メッセージ受信
*********************************/
public boolean RecvMessage() {

String temp = "";
byte w[] = new byte[4048];
int size;

if (socket.isConnected() == false){
return false;
}
try {
reader = socket.getInputStream();
size = reader.read(w);
if (size <= 0) {
return false;
}
else{
temp = new String(w, 0, size, "UTF-8");
}
recv_mess += "受信<<<:"+temp+"\n";
return true;
}
catch (Exception e){
return false;
}
}

/*********************************
メッセージ送信
*********************************/
public boolean SendMessage() {

String str = "私はサーバーです";

if (socket.isConnected() == false){
return false;
}
try {
writer = socket.getOutputStream();
writer.write(str.getBytes("UTF-8"));
return true;
}
catch (Exception e){
return false;
}
}
}

(4)メインプログラム

最後にメインプログラムです。アプリ本体のMainActivityは、UIのボタン処理や、ソケットクライアント、ソケットサーバーの起動を担当しています。ポイントはサーバーもクライアントもどちらもスレッドにて実装することです。今回は、Threadクラスのrunメソッドを使用してwhileループさせています。説明より実際のソースを見る方が早いと思いますのでどうぞ。

package tfsapps.sockapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private MyServer mySvr;
private MyClient myClt;
private TextView message;
private RadioButton rbtn_Svr;
private RadioButton rbtn_Clt;
private EditText inp_IpAdress;

private int mode;
private boolean taskrun = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

message = (TextView) findViewById(R.id.message);
rbtn_Svr = (RadioButton) findViewById(R.id.radio_server);
rbtn_Clt = (RadioButton) findViewById(R.id.radio_client);
inp_IpAdress = (EditText) findViewById(R.id.input_ipAdress);
}

/* ラジオボタン:ソケットサーバー モード[ON] */
public void onRbtn_Server(View view)
{
mode = 0;
rbtn_Svr.setChecked(true);
rbtn_Clt.setChecked(false);
}
/* ラジオボタン:ソケットクライアント モード[ON] */
public void onRbtn_Client(View view)
{
mode = 1;
rbtn_Svr.setChecked(false);
rbtn_Clt.setChecked(true);
}
/* ボタン:モード強制終了 */
public void onStop(View view) {
taskrun = false;
message.setText("初期状態 .. .. .. ");
}
/* ボタン:スタート */
public void onStart(View view) {

if (mode == 0){
ServerStart();
}else{
ClientStart();
}
}
/* ボタン:PING */
public void onPing(View v){
String ipadrr = inp_IpAdress.getText().toString();
String tempcmd = "ping -c 5 " + ipadrr;
Thread thread = new Thread(){
public void run() {
message.setText("---PING START---");
Runtime runtime = Runtime.getRuntime();
Process proc = null;
try{
if (ipadrr.isEmpty()) {
proc = runtime.exec("ping -c 5 192.168.1.17");
}
else {
proc = runtime.exec(tempcmd);
}
proc.waitFor();
}catch(Exception e){}
int exitVal = proc.exitValue();
if(exitVal == 0){
message.setText("-->PING OK ");
}
else{
message.setText("-->PING NG ");
}
}
};
thread.start();
}

/*
ソケットクライアント モード処理
*/
public void ClientStart(){
String tmpAddress = inp_IpAdress.getText().toString();
taskrun = true;

if (myClt == null){
myClt = new MyClient(tmpAddress);
}

/* 専用スレッド起動 */
Thread thread = new Thread(){
public void run() {
int step = 0;
int retry = 0;

try {
while (taskrun) {
switch (step) {
case 0: //接続待ち
if (myClt.Connect()) {
step = 1;
}
break;
case 1: //送信
if (retry > 5) {
myClt.DisConnect();
retry = 0;
step = 0;
}
else {
if (myClt.SendMessage("テストメッセージ")) {
step = 2;
} else {
retry++;
}
}
break;
case 2: //受信
if (retry > 5) {
myClt.DisConnect();
retry = 0;
step = 0;
}
else {
if (myClt.RecvMessage()) {
step = 1;
retry = 0;
} else {
retry++;
}
}
break;
}
//画面更新
message.setText(myClt.str_status);
sleep(300);
}

// loop 抜ける
myClt.DisConnect();
myClt = null;

}catch(Exception e){
return;
}
}
};
thread.start();
}

/*
ソケットサーバー モード処理
*/
public void ServerStart() {

taskrun = true;

if (mySvr == null){
mySvr = new MyServer();
}

/* 専用スレッド起動 */
Thread thread = new Thread(){
public void run() {
int step = 0;
int retry = 0;

try {
while (taskrun) {
switch (step) {
case 0: //接続待ち
if (mySvr.Accept(2002) ){
step = 1;
}
break;
case 1: //受信
if (mySvr.RecvMessage()) {
step = 2;
}
break;
case 2: //送信
if (mySvr.SendMessage()) {
step = 1;
}
break;
}
message.setText(mySvr.recv_mess);
sleep(100);
}

// loop 抜ける
mySvr.DisConnect();
mySvr = null;

}catch(Exception e){
return;
}
}
};
thread.start();
}
}

(5)おまけに、PING

今回、最初に試行錯誤しているときにPING処理もあわせて実装していますので、上記ソースの「ボタン:PING」を参考にしてください。あとやっぱり通信プログラムですので、どこまで正常に動作しているのかわからない場合がありますので「wireshark」などのパケットアナライザを使用しながらデバッグすることを勧めます。

あと、最後になりましたが、あらかじめAndroidManifest.xmlに下記のパーミッションを追記するのも忘れずに。


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

以上、簡単ですが、ソケットサーバーとソケットクライアントの実装の仕方を紹介しました。一度コピペで試して見てください。