안드로이드 개발 환경에서 다양한 장비와 기기를 네트워크로 연결해 데이터를 주고받는 기능은 필수적입니다.
네트워크 통신에 관심이 많은 저는 이러한 다양한 통신 방식을
하나의 모듈로 통합하고 관리할 수 있는 Connector 모듈을 작성하였습니다.
이번 글에서는 Connector 모듈을 활용해 안드로이드 USB 소켓 통신을 중점적으로 다룰 예정입니다.
예제의 주요 코드를 살펴보고, 안드로이드 단말과 PC 간에 데이터를 주고받는 테스트 방법도 확인할 수 있습니다.
또한 예제 코드는 GitHub에서 다운로드하여 직접 확인할 수 있습니다.
Connector 모듈 소개
Connector 모듈은 안드로이드에서 다양한 통신 방식을 하나의 구조로 통합하여 관리할 수 있도록 설계된 모듈입니다.
이 모듈을 통해 WiFi, USB, 블루투스, 시리얼 통신 등의 통신을 구현하고 효율적으로 유지 관리할 수 있으며
통신 방식에 관계없이 일관된 인터페이스를 제공합니다.
USB 소켓 통신
USB 소켓통신을 간략하게 아래와 같이 이해하면 좋을 것 같습니다.
- 하나의 네트워크(USB 포트포워딩) 환경을 만든다.
- 서버 측에서 Port를 열고 기다린다.
- 클라이언트 측에서 서버 측의 IP, Port를 통해 연결한다.
- 서로 데이터를 송수신한다.
포트포워딩이란?
데이터를 보낼 포트를 정하고, 그 포트를 통해 데이터가 어디로 이동할지 길을 만들어주는 과정입니다.
통신할 기기간에 usb를 연결하고 아래 명령어를 상황에 맞는 걸로 입력해 주세요.
adb forward tcp:5555 tcp:5555
* 예시: 호스트(PC)에서 디바이스(안드로이드)로 연결 요청을 해야 할 때
adb reverse tcp:5555 tcp:5555
* 예시: 디바이스(안드로이드)에서 호스트(PC)로 연결 요청을 해야 할 때
USB 소켓 통신 예제
USB 소켓 통신 예제에서 주요 코드를 살펴보려고 합니다.
해당 예제는 클라이언트 측 코드입니다.
1. ConnectionUSB 클래스
USB 소켓 통신의 실제 연결 및 데이터 송수신을 처리하는 역할을 하는 클래스입니다.
class ConnectionUSB(private val port: Int) : ConnectionHandler {
private var socket: Socket? = null
private var outputStream: OutputStream? = null
private var inputStream: InputStream? = null
override suspend fun connect(): Boolean = withContext(Dispatchers.IO) {
try {
val socketAddress = InetSocketAddress("127.0.0.1", port)
socket = Socket()
socket?.connect(socketAddress, 3000)
outputStream = socket?.getOutputStream()
inputStream = socket?.getInputStream()
Log.d("Connector(USB)", "Connected to 127.0.0.1:$port")
true
} catch (e: IOException) {
e.printStackTrace()
false
}
}
override fun disconnect() {
try {
outputStream?.close()
inputStream?.close()
socket?.close()
Log.d("Connector(USB)", "Disconnected")
} catch (e: IOException) {
e.printStackTrace()
}
}
override suspend fun sendData(data: ByteArray): Boolean = withContext(Dispatchers.IO) {
try {
outputStream?.write(data)
outputStream?.flush()
Log.d("Connector(USB)", "Data sent: ${String(data)}")
true
} catch (e: IOException) {
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(USB)", "Data received: ${String(receivedData).trim()}")
}
receivedData
}
} catch (e: IOException) {
e.printStackTrace()
null
}
}
}
2. ControllerUSB 클래스
ConnectionUSB 기능을 관리하고 제어하는 상위 컨트롤러 클래스입니다.
class ControllerUSB : BaseController() {
private var connectionUSB: ConnectionUSB? = null
suspend fun connectTo(port: Int): Boolean {
connectionUSB = ConnectionUSB(port)
return startConnection()
}
override suspend fun connect(): Boolean {
return (connectionUSB?.connect() ?: false)
.also { isConnected ->
if (isConnected) connectionType = ConnectionUtil.ConnectionType.USB
}
}
override fun disconnect() {
connectionUSB?.disconnect()
connectionType = ConnectionUtil.ConnectionType.NONE
}
override suspend fun sendData(data: ByteArray): Boolean {
return connectionUSB?.sendData(data) ?: false
}
override suspend fun receiveData(): ByteArray? {
return connectionUSB?.receiveData()
}
}
3. MainActivity 동작 구현
ControllerUSB를 통해서 USB 소켓 연결동작, USB 소켓 송신 동작, USB 소켓 수신 동작을 각각 구현할 수 있습니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val controllerUSB = ControllerUSB()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.btnUsbConnect.setOnClickListener {
// USB 소켓 연결 동작
tryConnectToUSB()
}
binding.btnDisconnect.setOnClickListener {
disconnectAll()
}
binding.btnSend.setOnClickListener {
// USB 소켓 송신 동작
val message = binding.etMessage.text.toString()
sendMessageToTarget(message)
}
}
private fun tryConnectToUSB() {
try {
CoroutineScope(Dispatchers.IO).launch {
val port = binding.etUsbPort.text.toString().toInt()
if (controllerUSB.connectTo(port)) {
updateLogView("[USB] Connected!\n")
// USB 소켓 수신 동작
startReceivingData(controllerUSB)
}
}
} 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 {
controllerUSB.connectionType == ConnectionType.USB -> {
if (controllerUSB.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"
}
}
}
USB 소켓 통신 예제 다운로드
USB 소켓 통신 테스트: 서버(PC) - 클라이언트(안드로이드)
테스트 방법은 안드로이드 단말과 PC 간에 USB 소켓 통신이 정상 동작하는지 확인해보려고 합니다.
클라이언트 역할로는 예제를 안드로이드 단말에 실행시켜 주고,
서버 역할로는 PC에서 Hercules(네트워크 및 시리얼 통신 테스트) 프로그램을 실행시켜 줍니다.
먼저 포트 포워딩을 통해서 USB 소켓 통신 환경을 만들어 줍니다.
1. 포트 포워딩
- PC와 안드로이드 단말을 USB 연결
- PC cmd 창을 열어서 명령어 입력 (adb )
포트 포워딩으로 하나의 네트워크 환경이 만들어졌다면 아래와 같이 진행합니다.
1. 서버(PC Hercules 프로그램)
- TCP Server 카테고리 이동
- 특정 Port를 입력하고 Listen 버튼 클릭
- 연결이 됐다면 Send Message 입력하고 Send 버튼 클릭
2. 클라이언트(안드로이드 예제)
- 서버 측에 포트가 열렸다면 IP, Port로 연결
- 연결이 됐다면 Send Message 입력하고 보내기 버튼 클릭
테스트 결과는 아래와 같습니다.
끝으로..
WiFi 소켓통신에 이어서 USB 소켓 통신에 대해서 알아보았습니다. 같은 TCP 소켓 통신을 가지기 때문에 코드상에 큰 차이는 없었지만 각각의 네트워크 환경을 만드는 과정에서 차이점을 확인할 수 있었습니다. C-Type USB 케이블만 있다면 쉽게 USB 통신이 가능하다는 걸 직접 예제를 작성하면서 많은 공부가 되었던 것 같습니다.
좋은 하루 보내세요:)
관련 글
'안드로이드 > 코틀린' 카테고리의 다른 글
안드로이드 UART 시리얼 통신 예제 다운로드 (0) | 2024.11.24 |
---|---|
안드로이드 블루투스 소켓 통신 예제 다운로드 (0) | 2024.11.23 |
안드로이드 WiFi 소켓 통신 (예제 다운로드) (0) | 2024.11.16 |
안드로이드 화면 전환: Navigation 기능 사용법 및 예제 (2) | 2024.07.13 |
코틀린 스레드 및 코루틴 중복 실행 방지 방법 (0) | 2024.05.14 |
댓글