[JAVA] TCP 소켓통신 채팅방 프로젝트 - 파일 데이터 전송 이론설명
[JAVA] TCP 소켓통신 채팅방 프로젝트 - 파일 데이터 전송 이론설명 — #JAVA소켓통신 #소켓통신 #소켓통신채팅방 #소켓통신파일 #소켓통신파일전송 #개발자의도구들 AI스쿨...
#JAVA소켓통신 #소켓통신 #소켓통신채팅방 #소켓통신파일 #소켓통신파일전송 #개발자의도구들
AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다
소켓 프로젝트
현재 소켓통신 프로젝트를 진행중입니다. 처음부터 순서대로 정리하는 것을 목표로 하려 했지만, 최대한 그날 공부한걸 글로 남기는 습관을 만들어보려고 합니다. (미루닌깐 끝도 없이 밀려요...)
혼자 공부한거라 정확하지 않은 내용이 있을 수 있으며, 잘못된 부분은 계속해서 개선해 나갈 예정입니다.
🎯 목적
- TCP 소켓 통신에 대해 이해하기
- 데이터 스트림
- 프로토콜
- TCP 소켓 통신을 사용하자
- 클라이언트에서 서버로 파일 전송을 해보기
파일 전송 구현하기
기본 이론
클라이언트 측에서 서버로 파일을 전송하기 위해서는 파일 데이터를 끊어서 전송해야합니다. 이유는 크게 네트워크 관점과 메모리 관점으로 나눌 수 있을것 같습니다.
우선 메모리 관점부터 보자면 FileStream이 파일 데이터를 읽어들여 byte\[\] 형태로 저장 후 전송합니다. 이때, 전체 파일 크기에 해당하는 byte\[\]를 할당하면 메모리 입장에서는 부담이 될 수 있습니다.
100GM를 한번에 할당할 수 없다 (24.03.23)
| 💡 100GB짜리 데이터를 보내면?(24.03.23)추가 예를들어 100GB짜리 데이터를 클라이언트에서 끊어서 보내지 않는다고 가정해 봅시다. 만약 서버에서 이를 읽어들이려면 100GM짜리 메모리가 필요한데, 현실적으로 100GM짜리 메모리를 사숑하는 PC는 별로 없습니다. 일반 가정집에서 이 데이터를 받으려 한다면 컴퓨터가 맛이 가겠죠? |
|---|
네트워크 관점입니다. 한번에 많은 크기의 byte\[\]를 보내버리면, 네트워크의 전체 입장에서는 대역폭을 크게 잡아먹어 비효율적으로 동작할 수 있겠습니다. 또한 중간에 네트워크가 끊기기라도 한다면, 처음부터 다시 보내야하는 경우도 생깁니다.
이를 예방하기 위해서 클라이언트 측에서 1. 데이터를 특정 크기로 끊어서 전송해야하며, 2. 각 데이터마다 seq넘버를 부여하여, 받는 쪽 클라이언트에서 재조합 할 수 있도록 만들어줍니다. 추가로 전송 오류를 대비하여 각 3. seq 패킷마다 ACK을 받아 오류검증을 하도록 할 수 있겠습니다.
3번까지 하려니 머리가 복잡해서 1, 2번 위주로 우선적으로 구현하였습니다.
프로토콜 규정
현재 file전송을 위한 프로토콜을 재정의 하였습니다. 기존의 bodyLength와 type 4 + 4 바이트에다가 idLength 4바이트를 추가 후, file의 조각 번호를 알리기 위해 4byte형태의 Seq를 또 넣었습니다..
idLength를 추가한 이유는 다음과 같습니다.
| 💡어디까지가 id인가 클라이언트 측에서 보내는 데이터는 어떤 것이든 바이트형태로 처리가 된다. 이때 바이트는 Stream을 통해 일련의 연속적인 형태로 전송이된다. 서버 측에서는 데이터가 바이트 형태로 들어오니 어떤 것이 파일이고, 어떤 것이 사용자 id인지 명확하게 구분할 필요가 있다. ex) 0011010010110011010101001010101010101000101010101010101 여기서 어디까지가 id이고 어디까지가 파일인가? id를 구분하기 위해서 기존에 명시된 8바이트(body길이 + type번호)를 제외하고 4바이트를 id길이로 추가로 주어 서버측에서 어디까지가 id byte인지 읽어들일 수 있도록 구현한다. \*Seq넘버도 마찬가지이다. |
|---|
+ 03.23 프로토콜 수정
클라이언트 측에서 분할된 파일 데이터를 저장하는데, 자료구조의 key값으로 fileName을 사용할 예정입니다. 이를 위해 4바이트 크기로 fileName을 고정하였습니다.
서버측에서 고려할 것
서버측에서 데이터를 받기만하면되는데 고려해야할 사항들이 몇가지 더 있습니다. 바로 서버측에서 데이터를 어떻게 처리할지 정해야한다는 것입니다.
- 서버에 저장 후 데이터를 합치고 다시 1MB단위로 잘라서 보낸다.
- 서버에 저장하지 않고 클라이언트 측으로 바로 보내고, 합치는건 클라이언트가 담당한다.
저장 후 보내기 (24.03.23)
처음에는 1번 방법으로 구현을 시도하였으나, 생각해보니 궂이 서버에 데이터를 저장하고 다시 쪼개서 보낼 필요가 없다고 느껴서 서버에서 온 데이터는 곧 바로 수신 스트림으로 데이터를 전송하였습니다.
바로 보내기(24.03.23)
서버측에 type 구분 후, FILE, FILE\_END의 데이터 타입 메세지가 올 경우에 해당하는 메서드를 구현해
야합니다.
해당 byte\[\]를 그대로 넘겨 receiverSocket으로 데이터를 넘겨주어야 합니다. 이를 위해 고려해야할건 다음과 같습니다.
- byte\[\]를 해독하여 id String을 가져와야한다.
- id String을 가져왔으면, idManager를 통해 등록된 id의 Socket을 가져온다.
- 해당 Socket에 해당하는 ClientHandler(입, 출력 스트림 담당)를 통해 데이터를 수신 클라이언트에게 보낸다.
수신 클라이언트 측
24.03.23 추가
수신 클라이언트가 서버에서 온 파일을 저장 하지 않고 바로 파일로 저장한다면, 원본 데이터와는 다른 데이터가 저장되겠죠? 따라서 일정 크기로 잘려서 전송된 데이터를 저장해 놓을 공간이 필요합니다.
저장을 위해 FileManger을 클라이언트 측에 붙여줄 겁니다. 하는 역할은 다음과 같습니다.
| 전송된 파일의 seq에 따른 byte\[\]를 저장해 둘 것파일 자체를 구분하기 위해 fileName에 따라 상위로 다시 구분 시킬 것특정 요청이 들어오면 저장된 데이터를 seq순으로 재 조립하여 내보낼 것 |
|---|
송신측에서 파일 전소이 끝나면, FILE\_END 메세지를 보낼 예정입니다. 이때 id와. fileName을 함께 보내어 해당 id의 fileManager에게 전달합니다.
수신측 클라이언트는 FILE\_END를 받게되면, 기존에 저장되어있던 데이터를 합쳐 지정된 경로에 파일을 저장합니다.
fileManager가 사용할 자료구조
| private final HashMap<String, TreeMap<Integer, byte\[\]>> fileMap = new HashMap<>(); |
|---|
| fileMap = { "fileName": { seq1(Integer} : byte\[\], seq2 : byte\[\], seq3: byte\[\] }} |
|---|
공통 모듈 FileProcessor
기능
1. file데이터 전용 header 붙이기
2. 파일 전용 header가 붙은 데이터 해독하기
클라이언트, 서버 양측 다 파일전송을 위해 동일한 프로토콜 형식으로 데이터를 보내야합니다. 이를 위해서 FileProcessor 클래스를 만들어 발신 패킷에 header를 붙여 보낼 수 있도록 구현하겠습니다.
또한, FileProcessor은 헤더가 붙은 데이터를 해독하여 id나 FileSeq, FileByte등을 구분 할 수 있도록 구현합니다. (24.03.18)
\* 기존 코드에 오류가 많아 이번글은 이론위주의 설명글로 변경하였습니다. (24.03.23)
다음글을 보시면 파일전송이 어떻게 이루어지는지 차근 차근 보실 수 있습니다.






