TCP(Transmission Control Protocol) / IP(Internet Protocol)
인터넷 통신을 하기 위한 프로토콜 중에 하나인 것은 알겠으나, 여태 한 번도 TCP/IP 통신앱을 만들어 보지는 않았습니다.
최근에 주변장치들과 통신을 하는 앱을 구현해 줄 수 있는 지 물어보는 사용자가 있었습니다. 재택을 하루 종일 하고 있었다면, 자료를 찾아서 앱을 구현해 드리고 싶었으나,
요새는 저녁 시간밖에는 시간을 낼 수 없는 지라... 하루 저녁 자료를 찾아보니 택도 없는 일인 듯하여, 돌려보냈지요.
그리고 주말에 찾아 보았습니다.
이런저런 코드들을 살펴보다가 참고가 될 듯한 코드를 찾아 앱을 하나 구현해 보았습니다.
<uses-permission android:name="android.permission.INTERNET"/>
통신을 해야 하니 일단, manifest 파일에 internet 사용과 관련된 권한을 등록해 보았습니다.
Log.e("", "doTcpIpOpen ...")
GlobalScope.launch {
try{
socket = Socket( ipAddress, 3000)
doResponse()
} catch (e : IOException) {
Log.e("", "네트워크 응답 없음")
} catch (e : UnknownHostException) {
Log.e("", "알 수 없는 호스트 IP")
} catch (e : SecurityException) {
Log.e("", "보안 접속 오류 proxy 접속 거부")
} catch (e : IllegalArgumentException) {
Log.e("", "server port range 0 ~ 65535 ")
} catch (e : Exception) {
Log.e("", "error ${e.localizedMessage}")
}
Log.e("", "doTcpIpOpen end...")
}
다음은 소캣을 하나 생성하면서 접속할 서버와 연결을 시도해 보았습니다. 저 코드에서 말하는 ipAddress는 우리가 알고 있는 일반적인 ip 주소 (192.168.0.2 같은)입니다. 저는 PC에서 서버 측 구현을 python을 이용해서 echo 서버를 하나 만들어 놓고 테스트를 진행하였습니다.
아래 코드는 python 을 구동하는 server 코드 예제입니다. 다른 기능은 없고 수신된 값을 다시 보내는 echo 서버입니다.
import socket
from random import random
addr = ("0.0.0.0", 3000) # 포트번호 4444
with socket.socket() as s: # 소켓 할당
s.bind(addr) # 소켓 바인딩
s.listen() # client의 연결요청 대기
print("Server is started... 3000 ")
# ------------------------------------서버 개통과정--------------
conn, addr = s.accept() # client 연경 요청을 수락
print("accept {}:{}".format(addr[0], addr[1])) # 연결된 client 정보 출력
while (1): # 무한반복
data = conn.recv(1024) # client가 보낸 메시지를 data에 저장
if data.decode() == "finished": # data decoding 결과가 finished면
break # 반복문 탈출
respData = data.decode() + "..." + str(random())
conn.send(respData.encode()) # data를 그대로 client에게 전송
print(data.decode()) # 보낸 데이터 읽기
print("Server finished") # 서버 종료 알리기
print("SOCKET closed... END")
서버와 클라이언트 간의 통신을 하여야 하기 때문에 서버 측 ip와 포트를 열어 줍니다. 잠깐 본 기억이기는 하나, 서버 측에서 선언한 addr 0.0.0.0의 선언에도 주의가 필요해 보이기는 합니다. 0.0.0.0을 선언하는 경우는 클라이언트의 ip 대역은 전체 대역을 어디서나 접근이 가능해지지만, 다르게 선언하는 경우 그 접속 범주가 달라지는 것을 알 수 있었습니다.
아무튼 서버를 구동한 상태에서 안드로이드 클라이언트를 실행해 접속을 해 보면 서버 측에 접속이 되는 것을 볼 수 있었습니다.
안드로이드에서 소켓 통신을 하면서 주의할 부분은 thread로 감싸 주어야 한다는 것입니다. 안드로이드는 앱이 실행하는 동안에 대기상태가 되는 것을 싫어합니다. 그래서 thread을 이용해 통신을 해 주어야 하는 것 같습니다.
GlobalScope.launch {
try {
val byteArr = ByteArray(100)
while(true) {
val input = withContext(Dispatchers.IO) {
socket.getInputStream()
}
ip = socket.inetAddress.hostAddress as String
val byte = withContext(Dispatchers.IO) {
input.read(byteArr)
}
val data = String(byteArr, 0, byte, Charset.forName("UTF-8"))
Log.e("", "ip=$ip readData=$byte $data" )
}
} catch (e : IOException) {
Log.e("", "read error = ${e.localizedMessage}")
} catch (e : StringIndexOutOfBoundsException) {
Log.e("", "read error = ${e.localizedMessage}")
doClose()
}
}
서버 접속이 되었다면, 이제 데이터 수신을 위한 스레드를 하나 구동시켜 둡니다. 그러면 언제든지 수신되는 데이터를 받아서 표시할 수 있을 테니까요..
GlobalScope.launch {
try {
val writer = OutputStreamWriter(socket.getOutputStream())
if ("" == strMessage) {
writer.write("hello server")
} else {
writer.write(strMessage)
}
writer.flush()
} catch (e : IOException) {
Log.e("", "네트워크 응답 없음")
} catch (e : UnknownHostException) {
Log.e("", "알 수 없는 호스트 IP")
} catch (e : SecurityException) {
Log.e("", "보안 접속 오류 proxy 접속 거부")
} catch (e : IllegalArgumentException) {
Log.e("", "server port range 0 ~ 65535 ")
} catch (e : Exception) {
Log.e("", "error ${e.localizedMessage}")
}
}
이제 서버 측을 데이터를 보내 보겠습니다.
서버가 구동되어 있는 상황이라면 정상적으로 데이터를 주고받는 것을 볼 수 있습니다. 그렇지 않다고 하면 여러 종류의 에러(Exception)가 발생할 텐데요. 그 종류는 위에 기술된 정도일 듯합니다.
오늘은 기초 공부를 해 보는 것이니 이 정도 구현을 해 보도록 하겠습니다.
https://github.com/nari4169/TcpIpClientExam
오늘 구현했던 코드는 github에서 참고해 보세요. 다음에는 조금 더 정리된 글을 적어 보도록 하겠습니다.
'모바일 앱(안드로이드)' 카테고리의 다른 글
안드로이드 앱 만들기 : 에러 메시지 도 이제 GPT 에게서 답을 얻다. (6) | 2023.04.26 |
---|---|
안드로이드 앱 만들기 : AI을 활용한 코딩은 어디까지 ?(feat CodeGPT 활용해 보기) (2) | 2023.04.19 |
안드로이드 앱 만들기 : 데이터 보안정책에 대한 이야기 #2(광고ID도 해당 될까?) (10) | 2023.04.09 |
안드로이드 앱 만들기 : 데이터 보안 정책에 걸리다. (광고ID도 해당 될까?) (2) | 2023.04.08 |
안드로이드 앱 만들기 : Lock Task Mode (일종의 kiosk 모드) 만들어 보기 (4) | 2023.03.26 |