이 글에서는 파이썬 서버와 자바 클라이언트 간의 이미지, 텍스트 소켓 통신을 다루어보려고 한다.
1) 자바에서 파이썬으로 이미지 전송
2) 자바에서 파이썬으로 텍스트 전송
3) 파이썬에서 자바로 이미지 전송
4) 파이썬에서 자바로 텍스트 전송
총 4가지 경우를 구현해볼 것이다!
그 전에 우리 팀이 구현한 안드로이드 어플리케이션에 대해 간단히 소개하자면 다음과 같다.
1. 안드로이드로 알약 사진을 촬영한다.
2. 촬영한 알약을 모델 서버로 전송한다.
3. 알약이 모델에 입력되고 출력으로 알약 식별마크와 모양이 나온다.
4. 식별마크와 모양을 안드로이드로 전송한다.
5. 안드로이드에서 식별 마크와 모양이 일치하는 알약 리스트를 DB에서 검색한다.
6. 알약 리스트의 이미지 파일명들을 모델 서버로 전송한다.
7. 모델 서버에서 해당 이미지 파일들을 안드로이드로 전송한다.
8. 사용자가 한 알약을 선택하면 해당 알약 정보를 DB에서 검색한다.
9. 알약의 상세정보를 보여준다.
나는 이 중 2, 4, 6, 7 단계에서 사용된 TCP socket 통신 부분에 대해 설명하고자 한다.
프로젝트에서 내가 담당한 일들 중 socket 통신에 대해 글을 쓴 이유는 구글링을 해도 나오지 않아서 내가 직접 구현한 코드가 많기 때문이다! 파이썬-자바 socket 통신에 어려움을 겪는 사람들에게 도움이 되었으면 좋겠다ㅠㅠ
안드로이드에서 파이썬으로 이미지 전송하기
먼저 안드로이드 스튜디오에서 "사진을 촬영한 후 해당 이미지를 파이썬 서버로 전송하는 방법"에 대해 알아보겠다.
소켓통신의 흐름은 다음과 같다!
저 노란 박스 부분을 구현해보겠다.
1) 안드로이드 스튜디오에서 파이썬으로 바이트배열의 길이 전송
2) 안드로이드 스튜디오에서 파이썬으로 바이트배열 전송
3) 파이썬에어 안드로이드 스튜디오로 문자열 전송
먼저 안드로이드 스튜디오 코드부터 보자.
TCP connection을 thread 상에서 진행할 것이기 때문에 Handler를 선언해준다.
DataInputStream과 DataOutputStream을 사용할 것이고,
TCP connection을 위해 파이썬 서버의 IP와 port 번호를 지정해준다! IP는 String으로 port 번호는 Integer로 지정하자.
서버에 연결할 때 호출할 connect 함수를 선언했다.
먼저 handler의 객체를 생성하고, 이미지를 전송&텍스트를 받는 socket 통신을 구현한다!
이 코드에서는 볼 수 없지만... 저 rotatedBitmap는 안드로이드에서 촬영된 알약 이미지를 bitmap 형태로 담고 있다.
즉 bitmap을 소켓통신으로 전송 가능하도록 bytearray 형태로 전환한다.
다음으로는 TCP 통신을 위해 서버의 ip, port를 바인딩한 Socket 객체를 생성하여 connection을 생성한다.
소켓통신을 할 때는 무조건 try-catch 문 안에 선언해주어야 에러가 발생하지 않는다 주의하자!
그리고 data를 주고받기 위한 stream인 dos, dis 객체를 생성한다.
여기까지 에러 없이 완료되었으면 초기 설정은 끝이다!
이제 데이터를 주고받아보자!
1) bytearray의 길이를 전송하기 위해 dos.writeUTF을 사용한다.
왜 바로 integer를 안보내고 굳이 string 형으로 바꾸어 보낸 이유는 자바로 전송한 integer를 파이썬에서 제대로 읽어들이지 못했기 때문이다... 분명 간단히 해결하는 방법이 있겠지만 이번에는 string으로 전송을 해보자!
또한 write를 한 뒤 바로 flush를 해주어 buffer를 비워주자.
2) dos.write를 통해 bytearray를 전송한다.
이건 DataOutputStream의 기본 메서드를 활용하는 것이기 때문에 문제가 없다! 다만 이를 파이썬에서 받는 부분이 복잡할 뿐이다...
3) 직접 생성한 readString 함수를 사용하여 텍스트 읽기
이건 파이썬에서 이미지를 읽는 것과 같은 원리인데... dis.read를 사용하면 문자열을 끝까지 받아오지 못하는 문제가 발생해서 만들게된 함수이다.
함수 내부에서는 먼저 문자열의 길이를 받는다.
그리고 해당 길이의 bytearray를 생성한다. 이는 데이터손실 없이 정확한 길이의 문자열을 받기 위해서다.
그리고 readFully 함수를 사용하여 해당 길이의 bytearray를 받는다.
우리는 문자열을 받고 싶은거였으므로 꼭 UTF8로 디코딩을 해주어야한다.
이 세 단계를 거치면 안드로이드에서 촬영했던 알약의 이미지 파일 이름, 식별마크, 모양 정보를 얻어올 수 있다!
자 이번에는 각 단계를 파이썬 서버 코드에서 살펴보자!
마찬가지로 client의 요청을 받기 위해 서버의 IP, port 번호를 생성한 TCP socket에 바인딩해둔다.
그리고 client를 listen하기!
client가 해당 서버에 connection을 요청하면 server_sock.accept()가 이를 받아서 해당 client를 위한 socket을 생성한다. 그것이 바로 client_sock. 그리고 해당 client의 주소를 addr에 저장한다.
1) 먼저 안드로이드로부터 바이트이미지의 길이 전송받기
우리가 안드로이드에서 굳이 integer를 string으로 변환했으니까 여기서는 bytearray를 변환한 뒤 3번째 인덱스의 값부터 사용하여 디코딩을 해주어야한다.
안드로이드로부터 전송받은 bytearray는 b'\x00\x08...'의 형태로 맨 앞에 \x00를 제거해주지 않으면 정확한 값이 나오지 않았다. 저 \x00가 2byte를 차지하므로 꼭 [2:] 부분만 디코딩 해주기!
2) 안드로이드로부터 이미지 bytearray 전송받기
get_bytes_stream 함수는 이미지 바이트의 길이를 먼저 받고, 모든 byte를 다 읽어들일 때까지 while문을 반복하는 함수이다.
먼저 bytearray를 누적해서 받을 빈 bytearray인 buffer를 선언한다.
remain의 초기값은 이미지의 총 바이트 수로 바이트의 일부분을 받을 때마다 받은 만큼을 뺀다.
즉 remain이 0이될 때까지 while문으로 바이트를 받아들인다.
buffer의 길이가 이미지 바이트의 길이와 같아지면 다 받았다는 의미임으로 buffer를 return한다!
그 후 해당 이미지를 client 주소를 활용하여 저장했다! 같은 client가 여러번 이미지를 전송할 수 있으므로 while문을 돌 때마다 증가하는 idx 값도 함께 사용했다.
그럼 실제로 어플리케이션에서 잘 작동하는지 확인해보자!
먼저 어플을 실행한다.
알약을 촬영하고 검색 버튼을 누른다.
그러면 서버에 이미지가 도착한다!
이미지를 확인해보자
이미지가 서버에 잘 저장되었다! (파란 낙서는 낙서가 아니구... 우리 서버 주소를 가려보기 위한 노력...)
아직 모델을 연결하기 전이기 때문에 식별마크와 모양을 인식했다고 치고 이미지 파일 주소, 식별마크, 모양을 안드로이드로 전송한다.
잘 도착했다! 이제 모델을 연결하면 된다!!
끝이 아니고 이제 그 다음의 통신과정을 살펴보자!
안드로이드 스튜디오에서 식별마크와 모양을 받았으므로 이와 일치하는 알약 리스트를 DB에서 얻어온다!
(php로 얻어오는데 이 내용은 이 글에서는 다루지 않을 것이다...)
알약 리스트에는 알약 이름과 알약 이미지 파일 경로가 있다.
왜 알약 이름과 이미지 파일 경로만 받아오냐면
이렇게 먼저 listview로 리스트를 보여준 뒤 사용자가 알약을 선택하면 자세한 정보 받아와서 보여주려고 하기 때문이다.
DB에 이미지를 저장하는 것은 비효율적으므로 일단 이미지 파일 경로만 저장한 뒤 서버에서 이미지를 불러오려 한다!
즉 통신 과정은 노란 박스와 같다.
1) 안드로이드에서 파이썬으로 몇 장의 이미지를 전송받아야하는지 이미지 개수를 전송한다.
2) 이미지 파일 경로들을 전송한다.
3) 파이썬에서 각 이미지의 길이를 전송
4) 각 이미지의 bytearray를 안드로이드로 전송한다.
안드로이드 스튜디오의 코드부터 확인해보자.
변수 설정은 위의 경우와 같아서 생략하겠다!
이미지를 받아오는 thread인 getImages를 생성한다.
마찬가지로 서버의 ip, port로 TCP connection을 생성하고 data를 주고받을 stream인 dis, dos 객체를 생성한다.
jsonArray는 DB에서 php를 통해 검색 결과를 얻어온 것이다. 여기서 얻어온 알약 정보들을 꺼내서 사용할 예정이다.
우리는 listview에 알약 이름과 이미지를 넣으려고 했지만 하나씩 받아오자마자 listview adapter에 add하고 싶었지만 thread 상에서 listview를 다루는 것이 불가능했기 때문에 일단 img_list와 name_list에 다 저장해두려고 한다.
1) 안드로이드에서 파이썬으로 전송받아야하는 이미지 개수를 전송
알약 개수는 jsonArray의 길이가 같으므로 dos.write를 통해 integer를 전송한다.
매우 간단하다!
이제 알약 개수만큼 for문을 돌며 이미지 파일 경로를 전송하고 이미지를 받아오자
2) 이미지 파일 경로 전송
마찬가지로 dos.writeUTF를 사용하여 문자열을 전송한다.
3) 파이썬으로부터 이미지 bytearray 길이 수신
dis.readInt()를 통해 integer를 보낸다!
4) 파이썬에서 안드로이드로 이미지 bytearray 전송
이를 위해 InputStreamToByteArray() 함수를 생성했다! 이 부분은 거의 3주 동안 구글링을 했는데도 찾을 수 없었다ㅠㅠ
어떻게든 머리를 안쓰려고 발악하다가.. 결국 내가 코드를 짰다..^^...
함수의 틀은 https://evnt-hrzn.tistory.com/18 여기서 도움을 받았지만! 열심히 응용을 해보았다...
파이썬에서 bytearray를 받았을 때와 마찬가지로 이미지의 길이만큼의 bytearray를 생성한다.
이번에는 1024 byte씩 readFully 함수를 이용하여 받을 것이므로 길이를 1024로 나누어 loop 수를 계산한다.
이때 마지막으로 받을 때는 길이를 1024로 나눈 나머지 만큼만 딱 맞게 받는 것이 중요하다!
마지막 부분까지 받고나면 resbytes 배열에 이미지 전체가 담기게 된다!
이렇게 안드로이드에서 파이썬으로 이미지 파일명을 전송하고,
파이썬에서는 이미지의 길이와 전체 이미지를 전송하게 된다.
우리 어플에서 이 코드가 동작하는 모습을 살펴보자!
우리 어플에는 "이름으로 검색" 기능이 있는데 검색한 문자열을 이름에 포함한 알약을 DB에서 찾아준다!
80을 검색하면 80을 포함한 알약들을 보여줘야 한다.
그러면 DB에서 6개 알약 리스트를 안드로이드로 보내주고, 안드로이드는 서버에 6개의 알약 이미지를 요청한다!
위 사진은 서버에서 이미지를 하나씩 안드로이드로 보내주는 모습이다.
받은 이미지를 listview에 적용하면 다음과 같이 알약 리스트를 얻을 수 있다.
처음에는 너무 어려운 Socket 통신이었지만 원리를 이해했더니 간단한걸 너무 어렵게 생각했다는 사실을 알게됐다..
물론 내가 짠 코드보다 더 간단명료한 코드가 있겠지만! 파이썬 서버 - 자바 클라이언트 구조의 TCP socket 통신에 대한 정보가 많지 않으므로 내 블로그가 큰 도움이 됐으면 좋겠다!
TCP Socket 통신 관련 코드 (0) | 2022.02.26 |
---|---|
Python Server - Android Studio(Java) Client (TCP Socket)-(2) (0) | 2020.11.14 |
Python Server - Android Studio(Java) Client (TCP Socket)-(1) (0) | 2020.11.14 |
댓글 영역