Bluetooth 소켓 통신에 이어서 단말과 단말간에 WiFi 를 활용하여 소켓 통신으로 데이터를 주고받는 간단한 예제를 다뤄보려고 합니다. 해당 예제는 WifiServer, WifiClient 각각 두 개로 나뉘어 집니다.  (단말 두 개가 필...

[안드로이드] WiFi 소켓 통신 예제 (Server-Client)

 


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)

sendData 메서드는 데이터를 송신할 때 사용됩니다. (write)
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. 결과

 


 wifiServer
 
 
 wifiClient

 

 

 

 

 

댓글 6개:

  1. 소켓연결을 통해 통신을 주고 받는 과정을 백그라운드에서 실행시키고 이 통신을 통해 다른 동작을 하고 싶은데 혹시 어떻게 가능할까요...?

    답글삭제
    답글
    1. 백그라운드에서 서로 통신을 주고받고 싶으신건가요?
      아니면 액티비티 없이 서비스로 통신을 주고받기를 원하시는걸까요?
      내용을 잘 이해하지 못했습니다ㅠㅠ

      삭제
  2. 작성자가 댓글을 삭제했습니다.

    답글삭제
  3. 소켓 클라이언트를 여러개 열어서 여러개의 서버로부터 데이터를 받고 싶습니다!
    백그라운드는 서비스를 얘기했던 거긴 합니다..!

    답글삭제
    답글
    1. 원하는 답변이 될지는 모르겠습니다ㅠㅠ
      서버:클라이언트, 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)

      삭제
  4. 감사합니다! 일단 그렇게 해보고있습니다. 한번 해보겠습니다..!

    답글삭제