Bluetooth 소켓 통신에 이어서 단말과 단말간에 WiFi 를 활용하여
소켓 통신으로 데이터를 주고받는 간단한 예제를 다뤄보려고 합니다.
해당 예제는 WifiServer, WifiClient 각각 두 개로 나뉘어 집니다.
(단말 두 개가 필요합니다..!)
Bluetooth 소켓 통신 예제가 필요하시다면 아래 링크를 참조해 주세요
↗[안드로이드] Bluetooth 소켓 통신 예제 (Server-Client)
1. 예제 다운로드
https://github.com/bictoselfdev/WifiClient
https://github.com/bictoselfdev/WifiServer
2. WiFi 소켓 통신 순서도
- WiFi 기반 소켓 통신을 다루기 위해서 Hotspot AP 에 대한 IP 주소 및 Port 번호를 준비합니다.
- 클라이언트에서 IP 주소 및 Port 번호를 통해 소켓 생성 및 연결을 시도합니다.
- 서버에서는 accept() 를 통해 소켓을 반환 받을 수 있습니다.
- 서버와 클라이언트는 서로 read() / wirte() 를 통해 데이터를 송수신 할 수 있습니다.
3. Hotspot AP 에 대한 IP 주소 및 Port 번호 가져오기
WiFi 를 활용하기 위해 아래 권한을 manifests 에 추가해 줍니다.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
두 단말 간에 WiFi 를 통해 네트워크가 연결이 되었다면 (Hotspot AP <-> WiFi)
Client 측 단말에서는 소켓 연결(connect) 를 시도 해야하는데,
이때 필요한 정보가 IP 주소 와 Port 번호입니다.
(1) AP 측에 IP 주소를 가져오기
현재 네트워크 정보를 불러와서 IP 주소를 확인합니다.
fun getHostIpinformation(): String {
var ip = ""
try {
val enumNetworkInterfaces = NetworkInterface.getNetworkInterfaces()
while (enumNetworkInterfaces.hasMoreElements()) {
val networkInterface = enumNetworkInterfaces.nextElement()
val enumInetAddress = networkInterface.inetAddresses
while (enumInetAddress.hasMoreElements()) {
val inetAddress = enumInetAddress.nextElement()
val hostAddress = inetAddress.hostAddress
if (hostAddress.contains("192.168.")) { // Class C IP 주소 취급 (Private Address)
ip += "$hostAddress\n"
}
}
}
} catch (e: SocketException) {
e.printStackTrace()
}
when (ip) {
"" -> return "No host ip information."
else -> return ip
}
}
(2) Port 정보
임의의 Port 번호를 정의해주고 사용합니다.
object WifiConstant {
const val PORT_NUM = 51111
}
4. 클라이언트 소켓 생성 및 연결
ClientThread 는 소켓을 생성 및 연결을 시도하고 연결이 정상적으로 되었다면 데이터 송수신 준비를 합니다.
inner class ClientThread(private val ipAddress: String, private val port: Int) : Thread() {
private var socket: Socket? = null
override fun run() {
try {
val socketAddress: InetSocketAddress = try {
InetSocketAddress(InetAddress.getByName(ipAddress), port)
} catch (e: UnknownHostException) {
e.printStackTrace()
return
}
onLogPrint("Try to connect to server..")
socket?.connect(socketAddress, 5000) // 소켓 연결
} catch (e: Exception) {
e.printStackTrace()
onError(e)
return
}
if (socket != null) {
onConnect()
commThread = CommThread(socket)
commThread?.start()
}
}
fun stopThread() {
try {
socket?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
init {
try {
// 소켓 생성
socket = Socket()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
5. 서버 Accept 및 소켓 반환
AcceptThread 는 Client 로 부터 연결 요청을 기다립니다.
요청이 들어오면 accept() 로부터 소켓을 반환을 수 있습니다.
inner class AcceptThread : Thread() {
private var acceptSocket: ServerSocket? = null
private var socket: Socket? = null
override fun run() {
while (true) {
socket = try {
acceptSocket?.accept()
} catch (e: Exception) {
e.printStackTrace()
break
}
if (socket != null) {
onConnect()
commThread = CommThread(socket)
commThread?.start()
break
}
}
}
fun stopThread() {
try {
acceptSocket?.close()
socket?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
init {
try {
acceptSocket = ServerSocket(PORT_NUM)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
6. 데이터 송수신
데이터 송수신은 입출력 스트림을 활용합니다. (inputstream / outputstream)
CommThread 는 Server / Client 가 각각 데이터를 수신할 때 사용됩니다. (read)
internal inner class CommThread(private val socket: Socket?): Thread() {
override fun run() {
try {
outputStream = socket?.outputStream
inputStream = socket?.inputStream
} catch (e: Exception) {
e.printStackTrace()
}
var len: Int
val buffer = ByteArray(1024)
val byteArrayOutputStream = ByteArrayOutputStream()
while (true) {
try {
len = socket?.inputStream?.read(buffer)!!
val data = buffer.copyOf(len)
byteArrayOutputStream.write(data)
socket.inputStream?.available()?.let { available ->
if (available == 0) {
val dataByteArray = byteArrayOutputStream.toByteArray()
val dataString = String(dataByteArray)
onReceive(dataString)
byteArrayOutputStream.reset()
}
}
} catch (e: Exception) {
e.printStackTrace()
stopThread()
accept()
break
}
}
}
fun stopThread() {
try {
inputStream?.close()
outputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun sendData(msg: String) {
if (outputStream == null) return
object : Thread() {
override fun run() {
try {
outputStream?.let {
onSend(msg)
it.write(msg.toByteArray())
it.flush()
}
} catch (e: Exception) {
onError(e)
e.printStackTrace()
stop()
}
}
}.start()
}
7. 결과
소켓연결을 통해 통신을 주고 받는 과정을 백그라운드에서 실행시키고 이 통신을 통해 다른 동작을 하고 싶은데 혹시 어떻게 가능할까요...?
답글삭제백그라운드에서 서로 통신을 주고받고 싶으신건가요?
삭제아니면 액티비티 없이 서비스로 통신을 주고받기를 원하시는걸까요?
내용을 잘 이해하지 못했습니다ㅠㅠ
작성자가 댓글을 삭제했습니다.
답글삭제소켓 클라이언트를 여러개 열어서 여러개의 서버로부터 데이터를 받고 싶습니다!
답글삭제백그라운드는 서비스를 얘기했던 거긴 합니다..!
원하는 답변이 될지는 모르겠습니다ㅠㅠ
삭제서버:클라이언트, N:N 소켓 통신을 원하시는거라면
그만큼 서버 측에서 Port 다르게하여 열어주고
클라이언트 측에서 연결하여 통신을 시도해보시는건 어떨까요?
기존코드를 변경하여 테스트용으로 로컬 영역(127.0.0.1)에서
서버:클라이언트, 3:3 통신을 테스트해서 로그를 확인해봤을 때
각각 통신이 잘 이루어지는걸 확인하였습니다.
(아래 로그는 각각의 서버로부터 전달받은 timeStamp와 port를 출력한 결과입니다.)
onReceive msg 2023-05-19 15:56:51 (PORT : 51111)
onReceive msg 2023-05-19 15:56:51 (PORT : 51112)
onReceive msg 2023-05-19 15:56:52 (PORT : 51113)
onReceive msg 2023-05-19 15:56:52 (PORT : 51111)
onReceive msg 2023-05-19 15:56:52 (PORT : 51112)
onReceive msg 2023-05-19 15:56:53 (PORT : 51113)
감사합니다! 일단 그렇게 해보고있습니다. 한번 해보겠습니다..!
답글삭제