ภาพรวม
บทช่วยสอนนี้แสดงวิธีส่งข้อมูลระหว่างอุปกรณ์ Android สองเครื่องผ่าน Wi-Fi โดยใช้ Socket Programming จำเป็นต้องสร้าง 2 โปรเจกต์แยกกัน คือ Server (ฝั่งรับ) และ Client (ฝั่งส่ง) ทั้งสองต้องอยู่ในเครือข่าย Wi-Fi เดียวกัน
ส่วน Server (ฝั่งรับข้อมูล)
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
เปิด Android Studio → File → New Project → เลือก Empty Activity → กรอกข้อมูลโปรเจกต์ (Package name: com.server.myapplication.server)
ขั้นตอนที่ 2: จัดการ Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvIP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/tvPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvIP"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/tvConnectionStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tvPort"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/tvMessages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/etMessage"
android:layout_below="@+id/tvConnectionStatus"
android:inputType="textMultiLine"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />
<EditText
android:id="@+id/etMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/btnSend"
android:hint="Enter Message"
android:inputType="text" />
<Button
android:id="@+id/btnSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="SEND" />
</RelativeLayout>
ขั้นตอนที่ 3: เขียนโค้ด MainActivity.java
package com.server.myapplication.server;
import android.annotation.SuppressLint;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
private ServerSocket serverSocket;
private Thread serverThread = null;
private TextView tvIP, tvPort, tvMessages;
private EditText etMessage;
private Button btnSend;
public static String SERVER_IP = "";
public static final int SERVER_PORT = 8080;
private PrintWriter output;
private BufferedReader input;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvIP = findViewById(R.id.tvIP);
tvPort = findViewById(R.id.tvPort);
tvMessages = findViewById(R.id.tvMessages);
etMessage = findViewById(R.id.etMessage);
btnSend = findViewById(R.id.btnSend);
try {
SERVER_IP = getLocalIpAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
tvIP.setText("IP: " + SERVER_IP);
tvPort.setText("Port: " + SERVER_PORT);
serverThread = new Thread(new ServerRunnable());
serverThread.start();
btnSend.setOnClickListener(v -> {
String message = etMessage.getText().toString().trim();
if (!message.isEmpty()) {
new Thread(new SendRunnable(message)).start();
}
});
}
private String getLocalIpAddress() throws UnknownHostException {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipInt = wifiInfo.getIpAddress();
return InetAddress.getByAddress(
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array()
).getHostAddress();
}
class ServerRunnable implements Runnable {
@Override
public void run() {
Socket socket;
try {
serverSocket = new ServerSocket(SERVER_PORT);
runOnUiThread(() -> tvMessages.setText("รอการเชื่อมต่อ...\n"));
socket = serverSocket.accept();
output = new PrintWriter(socket.getOutputStream());
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
runOnUiThread(() -> tvMessages.append("เชื่อมต่อแล้ว\n"));
new Thread(new ReceiveRunnable()).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReceiveRunnable implements Runnable {
@Override
public void run() {
while (true) {
try {
final String message = input.readLine();
if (message != null) {
runOnUiThread(() -> tvMessages.append("Client: " + message + "\n"));
} else {
// Client ปิดการเชื่อมต่อ เริ่มรอใหม่
runOnUiThread(() -> tvMessages.append("Client ตัดการเชื่อมต่อ\n"));
serverThread = new Thread(new ServerRunnable());
serverThread.start();
return;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class SendRunnable implements Runnable {
private final String message;
SendRunnable(String message) { this.message = message; }
@Override
public void run() {
output.write(message + "\n");
output.flush();
runOnUiThread(() -> {
tvMessages.append("Server: " + message + "\n");
etMessage.setText("");
});
}
}
}
ขั้นตอนที่ 4: กำหนด Permission ใน AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.server.myapplication.server">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity" android:label="Server">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
ส่วน Client (ฝั่งส่งข้อมูล)
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
สร้างโปรเจกต์อีกอัน (Package name: com.client.myapplication.client)
ขั้นตอนที่ 2: จัดการ Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context=".MainActivity">
<EditText
android:id="@+id/etIP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="IP-Address"
android:inputType="text" />
<EditText
android:id="@+id/etPort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/etIP"
android:hint="Port No"
android:inputType="number" />
<Button
android:id="@+id/btnConnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/etPort"
android:layout_marginTop="16dp"
android:text="Connect To Server" />
<TextView
android:id="@+id/tvMessages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/etMessage"
android:layout_below="@+id/btnConnect"
android:inputType="textMultiLine"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />
<EditText
android:id="@+id/etMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/btnSend"
android:hint="Enter Message"
android:inputType="text" />
<Button
android:id="@+id/btnSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="SEND" />
</RelativeLayout>
ขั้นตอนที่ 3: เขียนโค้ด MainActivity.java
package com.client.myapplication.client;
import android.annotation.SuppressLint;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
private Thread clientThread = null;
private EditText etIP, etPort, etMessage;
private TextView tvMessages;
private Button btnSend;
private String SERVER_IP;
private int SERVER_PORT;
private PrintWriter output;
private BufferedReader input;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etIP = findViewById(R.id.etIP);
etPort = findViewById(R.id.etPort);
tvMessages = findViewById(R.id.tvMessages);
etMessage = findViewById(R.id.etMessage);
btnSend = findViewById(R.id.btnSend);
Button btnConnect = findViewById(R.id.btnConnect);
btnConnect.setOnClickListener(v -> {
tvMessages.setText("");
SERVER_IP = etIP.getText().toString().trim();
SERVER_PORT = Integer.parseInt(etPort.getText().toString().trim());
clientThread = new Thread(new ConnectRunnable());
clientThread.start();
});
btnSend.setOnClickListener(v -> {
String message = etMessage.getText().toString().trim();
if (!message.isEmpty()) {
new Thread(new SendRunnable(message)).start();
}
});
}
class ConnectRunnable implements Runnable {
@Override
public void run() {
try {
Socket socket = new Socket(SERVER_IP, SERVER_PORT);
output = new PrintWriter(socket.getOutputStream());
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
runOnUiThread(() -> tvMessages.setText("เชื่อมต่อแล้ว\n"));
new Thread(new ReceiveRunnable()).start();
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> tvMessages.setText("เชื่อมต่อล้มเหลว: " + e.getMessage()));
}
}
}
class ReceiveRunnable implements Runnable {
@Override
public void run() {
while (true) {
try {
final String message = input.readLine();
if (message != null) {
runOnUiThread(() -> tvMessages.append("Server: " + message + "\n"));
} else {
runOnUiThread(() -> tvMessages.append("Server ตัดการเชื่อมต่อ\n"));
return;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class SendRunnable implements Runnable {
private final String message;
SendRunnable(String message) { this.message = message; }
@Override
public void run() {
output.write(message + "\n");
output.flush();
runOnUiThread(() -> {
tvMessages.append("Client: " + message + "\n");
etMessage.setText("");
});
}
}
}
ขั้นตอนที่ 4: กำหนด Permission ใน AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.client.myapplication.client">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity" android:label="Client">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
วิธีทดสอบ
- ติดตั้งแอป Server บนอุปกรณ์เครื่องแรก และแอป Client บนอุปกรณ์เครื่องที่สอง
- รันแอป Server ก่อน จะแสดง IP Address และ Port (8080)
- รันแอป Client ใส่ IP และ Port ของ Server แล้วกด "Connect To Server"
- เมื่อเชื่อมต่อสำเร็จ สามารถพิมพ์ข้อความและกด SEND ได้ทั้งสองฝั่ง
หมายเหตุ: โค้ดนี้ใช้ Thread แบบเดิม สำหรับแอปจริงควรใช้ Coroutines หรือ ExecutorService ร่วมกับ Lifecycle-aware components เพื่อจัดการ Thread ให้ปลอดภัยกว่า