안드로이드 개발 환경에서 다양한 장비와 기기를 네트워크로 연결해 데이터를 주고받는 기능은 필수적입니다.
네트워크 통신에 관심이 많은 저는 이러한 다양한 통신 방식을
하나의 모듈로 통합하고 관리할 수 있는 Connector 모듈을 작성하였습니다.
이번 글에서는 Connector 모듈을 활용해 안드로이드 WiFi 소켓 통신을 중점적으로 다룰 예정입니다.
예제의 주요 코드를 살펴보고, 안드로이드 단말과 PC 간에 데이터를 주고받는 테스트 방법도 확인할 수 있습니다.
또한 예제 코드는 GitHub에서 다운로드하여 직접 확인할 수 있습니다.
Connector 모듈 소개
먼저 Connector 모듈에 대해서 간단하게 살펴보겠습니다.
Connector 모듈은 안드로이드에서 다양한 통신 방식을 하나의 구조로 통합하여 관리할 수 있도록 설계된 모듈입니다.
이 모듈을 통해 WiFi, USB, 블루투스, 시리얼 통신 등의 통신을 구현하고 효율적으로 유지 관리할 수 있으며
통신 방식에 관계없이 일관된 인터페이스를 제공합니다.
WiFi 소켓 통신
WiFi 소켓통신을 간략하게 아래와 같이 이해하면 좋을 것 같습니다.
- 하나의 네트워크(WiFi) 환경을 만든다.
- 서버 측에서 Port를 열고 기다린다.
- 클라이언트 측에서 서버 측의 IP, Port를 통해 연결한다.
- 서로 데이터를 송수신한다.
WiFi 소켓 통신 예제
WiFi 소켓통신 예제에서 주요 코드를 살펴보려고 합니다.
해당 예제는 클라이언트 측 코드입니다.
1. 권한 추가
WiFi 소켓 통신은 네트워크를 통해 데이터 전송을 수행하기 때문에 아래 권한을 AndroidManifest.xml에 추가합니다.
<uses-permission android:name="android.permission.INTERNET" />
2. ConnectionWiFi 클래스
WiFi 소켓 통신의 실제 연결 및 데이터 송수신을 처리하는 역할을 하는 클래스입니다.
class ConnectionWiFi(private val ipAddress: String, private val port: Int) : ConnectionHandler {
private var socket: Socket? = null
private var inputStream: InputStream? = null
private var outputStream: OutputStream? = null
override suspend fun connect(): Boolean = withContext(Dispatchers.IO) {
try {
val socketAddress = InetSocketAddress(ipAddress, port)
socket = Socket()
socket?.connect(socketAddress, 3000)
inputStream = socket?.getInputStream()
outputStream = socket?.getOutputStream()
Log.d("Connector(WiFi)", "Connected to $ipAddress, $port")
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
override fun disconnect() {
try {
inputStream?.close()
outputStream?.close()
socket?.close()
Log.d("Connector(WiFi)", "Disconnected")
} catch (e: Exception) {
e.printStackTrace()
}
}
override suspend fun sendData(data: ByteArray): Boolean = withContext(Dispatchers.IO) {
try {
withTimeout(ConnectionUtil.TIMEOUT.toLong()) {
outputStream?.write(data)
outputStream?.flush()
Log.d("Connector(WiFi)", "Data sent: ${String(data).trim()}")
}
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
override suspend fun receiveData(): ByteArray? = withContext(Dispatchers.IO) {
try {
val buffer = ByteArray(ConnectionUtil.BUFFER_SIZE)
withTimeout(ConnectionUtil.TIMEOUT.toLong()) {
var receivedData: ByteArray? = null
val bytesRead = inputStream?.read(buffer) ?: -1
if (bytesRead > 0) {
receivedData = buffer.copyOf(bytesRead)
Log.d("Connector(WiFi)", "Data received: ${String(receivedData).trim()}")
}
receivedData
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
3. ControllerWiFi 클래스
ConnectionWiFi 기능을 관리하고 제어하는 상위 컨트롤러 클래스입니다.
class ControllerWiFi : BaseController() {
private var connectionWiFi: ConnectionWiFi? = null
suspend fun connectTo(ipAddress: String, port: Int): Boolean {
connectionWiFi = ConnectionWiFi(ipAddress, port)
return startConnection()
}
override suspend fun connect(): Boolean {
return (connectionWiFi?.connect() ?: false)
.also { isConnected ->
if (isConnected) connectionType = ConnectionType.WIFI
}
}
override fun disconnect() {
connectionWiFi?.disconnect()
connectionType = ConnectionType.NONE
}
override suspend fun sendData(data: ByteArray): Boolean {
return connectionWiFi?.sendData(data) ?: false
}
override suspend fun receiveData(): ByteArray? {
return connectionWiFi?.receiveData()
}
}
4. MainActivity 동작 구현
ControllerWiFi를 통해서 WiFi 소켓 연결동작, WiFi 소켓 송신 동작, WiFi 소켓 수신 동작을 각각 구현할 수 있습니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
// WiFi 소켓통신 컨트롤러 정의
private val controllerWiFi = ControllerWiFi()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.btnWifiConnect.setOnClickListener {
// WiFi 소켓 연결 동작
tryConnectToWIFI()
}
binding.btnSend.setOnClickListener {
// WiFi 소켓 송신 동작
val message = binding.etMessage.text.toString()
sendMessageToTarget(message)
}
}
private fun tryConnectToWIFI() {
try {
CoroutineScope(Dispatchers.IO).launch {
val ip = binding.etWifiIP.text.toString()
val port = binding.etWifiPort.text.toString().toInt()
if (controllerWiFi.connectTo(ip, port)) {
updateLogView("[WiFi] Connected!\n")
// WiFi 소켓 수신 동작
startReceivingData(controllerWiFi)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun startReceivingData(controller: BaseController) {
CoroutineScope(Dispatchers.IO).launch {
while (controller.connectionType != ConnectionType.NONE) {
val receivedData = controller.receiveData()
receivedData?.let {
val receivedMessage = String(it).trim()
updateLogView("Received: $receivedMessage\n")
}
delay(500)
}
}
}
private fun disconnectAll() {
controllerWiFi.terminateConnection()
//controllerUSB.terminateConnection()
//controllerBT.terminateConnection()
//controllerSerial.terminateConnection()
updateLogView("Disconnected All!\n")
}
private fun sendMessageToTarget(message: String) {
try {
CoroutineScope(Dispatchers.IO).launch {
when {
controllerWiFi.connectionType == ConnectionType.WIFI -> {
if (controllerWiFi.sendData(message.toByteArray())) {
updateLogView("Sent : $message\n")
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun updateLogView(message: String) {
CoroutineScope(Dispatchers.Main).launch {
binding.tvLog.text = "${binding.tvLog.text}$message"
}
}
}
WiFi 소켓 통신 예제 다운로드
WiFi 소켓 통신 테스트: 서버(PC) - 클라이언트(안드로이드)
WiFi 소켓 통신 예제가 정상동작하는지 테스트를 해봐야겠죠.
기존에 코드를 가지고 계신 분이 아니라면 예제를 다운로드해서 확인해 보시는 걸 추천드립니다.
테스트 방법은 안드로이드 단말과 PC 간에 WiFi 소켓 통신으로 확인해보려고 합니다.
클라이언트 역할로는 예제를 안드로이드 단말에 실행시켜 주고,
서버 역할로는 PC에서 Hercules(네트워크 및 시리얼 통신 테스트) 프로그램을 실행시켜 줍니다.
하나의 네트워크 WiFi 환경이 만들어졌다면 아래와 같이 진행합니다.
1. 서버(PC Hercules 프로그램)
- TCP Server 카테고리 이동
- 특정 Port를 입력하고 Listen 버튼 클릭
- 연결이 됐다면 Send Message 입력하고 Send 버튼 클릭
2. 클라이언트(안드로이드 예제)
- 서버 측에 포트가 열렸다면 IP, Port로 연결
- 연결이 됐다면 Send Message 입력하고 보내기 버튼 클릭
차근차근 진행하면 간단하게 WiFi 소켓 통신 테스트를 완료할 수 있습니다.
테스트 결과는 아래와 같습니다.
끝으로..
Connector 모듈을 통해 WiFi 소켓 통신에 대해서 알아보았고 예제를 통해 데이터를 주고받는 테스트도 확인해 보았습니다. 이번 글을 정리하면서 저한테 있어서도 WiFi 소켓 통신에 대해 개념적으로 이해하는데 도움이 되었고 방문해 주신 분들에게도 조금이나마 도움이 되었으면 좋겠습니다. 시간적 여유가 생긴다면 USB 소켓통신, 블루투스 소켓통신, 시리얼 통신에 대해서도 다뤄보려고 합니다.
좋은 하루 보내세요:)
관련 글
'안드로이드 > 코틀린' 카테고리의 다른 글
안드로이드 블루투스 소켓 통신 예제 다운로드 (0) | 2024.11.23 |
---|---|
안드로이드 USB 소켓 통신 (예제 다운로드) (0) | 2024.11.19 |
안드로이드 화면 전환: Navigation 기능 사용법 및 예제 (2) | 2024.07.13 |
코틀린 스레드 및 코루틴 중복 실행 방지 방법 (0) | 2024.05.14 |
안드로이드 소수점 표기 올림? 버림? : String.format, DecimalFormat (0) | 2024.05.10 |
댓글