이 글을 읽기전에 알아야 할 개념
자바 TCP 소켓프로그래밍을 통해 서버를 직접 만들어서 실시간 채팅을 구현 과정 및 겪었던 시행착오에 대해서
적겠습니다.
1.구현과정
1)자바 에코서버 예제 공부
https://wakestand.tistory.com/167
-이 예제를 공부하면서 TCP소켓 통신이 어떤 방식으로 작동하는지 데이터를 어떤 방식으로 주고받는지에 대해서 알게되었습니다.
2)단체 채팅 방 데이터 구조 설계
-그후에 단체채팅 방을 만들기위해서 데이터 구조를 설계했습니다.
* 조건
-채팅 방에 참여한 유저가 채팅을 보내면 채팅방에 참여한 유저들만 채팅을 받을 수 있다.
-다른 채팅방에 입장한 유저는 채팅을 받을 수 없다.
* 클래스 및 그림 설명
2-1)static ArrayList RoomManager<Room>
-생성한 채팅방을 관리하는 ArrayList
-타입은 Room
2-2)static ArrayList Sockets<User>
-Socket에 연결된 User객체를 관리하는 ArrayList
-타입은 User
2-3)Room(Class)
-생성한 채팅방 클래스
-방에 참여한 유저의 소켓을 관리하는 ArrayList가 있다
-채팅을 보낼 때 보내고 싶은 방에 채팅을 보내기 위해서는 채팅방을 찾을 수 있어야 하기 때문에
채팅방의 Room_idx 변수가 있다.
-Room_idx 변수는 auto_increment로 생성한 방의 idx 번호(pk) 값이다.
2-4)User(Class)
-소켓에 연결된 유저 클래스
-처음 유저가 소켓에 연결이 되었을 때 User 객체를 생성한다.
-생성자의 입력 값으로 접속한 유저의 소켓을 Socket socket 객체에 담는다
-user_idx에 유저의 auto_increment로 생성한 idx 번호(pk) 값을 변수에 담는다.
3)클라이언트와 서버의 소켓연결
// 클라이언트가 서버에 소켓 연결 후
// 서버에서 소켓(클라이언트와 연결된 소켓) 생성
Socket socketUser = serverSocket.accept();
System.out.println("접속한 클라이언트 정보 : " + "[" + socketUser.getInetAddress().getHostName() + "]");
// Thread 안에 클라이언트와 연결된 소켓을 생성자로 전달.
SocketThread clientSocket = new SocketThread(socketUser);
//클라이언트의 메세지를 받는 스레드 시작.
clientSocket.start();
-위의 코드는 Client가 소켓서버에 연결(accept) 되었을 때 SocketThread 객체를 생성하고 socket객체를 생성자에
입력한다.
Thread를 상속받은 SocketThread객체가 스레드를 시작한다.
-위의 그림은 Client마다 하나의 Thead가 생성된다는 점이 중요하다. 이 부분을 제대로 이해하지 못하면 많이 어렵다.
-데이터는 보낼 때는 OutputStream 받을 때는 InputStream을 사용한다.
4)단체 채팅방 구현 시나리오
4-1)경우의 수
-1)처음 유저가 소켓연결을 한 경우
if (!identify_flag) {
//서버에 소켓의 연결된 사용자의 idx번호를 보낸다.
printWriter.println(Util_KEY.user_idx)
printWriter.flush()
Log.e("보낸메세지", Util_KEY.user_idx)
//한 번 보내고 그 이후에는 보내지 않늗다.
identify_flag = true
//handler!!!!!
}
-위의 코드는 클라이언트(안드로이드)에서 서버에 유저 idx(pk) 번호를 보내는 코드이다.
-처음 소켓연결 시 클라이언트에서는 유저 idx(pk)번호를 보낸다.
-유저 idx 번호를 서버에 보내는 이유는 서버에 연결된 유저 소켓을 유저 idx번호로 구분하기 위해서이다.
if (!identify_flag) {
//소켓에 연결된 클라이언트의 인덱스번호 변수에 저장
user_idx = readValue;
//소켓에 연결된 클라이언트 리스트에 추가.
System.out.println("연결된 사용자 idx번호: " + readValue);
//처음 한 번만 실행하기 위해서 true
identify_flag = true;
continue;
}
-위의 코드는 서버 코드(자바)이고 클라이언트에서 보낸 유저 idx(pk)번호를 변수에 저장하는 코드이다.
-위의 코드에는 빠져있지만 클라이언트에서 보낸 유저 idx(pk)와 SokcetThread클래스를 생성할 때 생성자의 입력 값으로입력된 Socket 객체를 User객체를 생성하고 생성자에 입력한다.
-생성한 User객체를 ArrayList Sokcets<User>에 추가한다.
ex) User user = new User(socket, user_idx)
Sockets.add(user)
-2)채팅 방을 생성한 경우
-클라이언트(안드로이드)는 유저의 idx 번호(pk), 참여한 채팅방 idx번호(pk)를 서버에 전송한다.
-Server는 Room객체를 생성하고 클라이언트에서 보낸 유저가 참여한 채팅방 idx(pk) 번호를 생성자에 입력한다.
-유저의 idx 번호(pk)를 이용해서 ArrayList sockets<User>에서 (방 생성한 사람)유저 Socket객체를 찾는다.
-User 객체를 생성하고 찾은 (방 생성한 사람)유저 Socket객체와 유저 idx 번호를 생성자에 입력한다.
-Room객체의 ArrayList<User>에 User객체를 넣는다.
-Server는 ArrayList RoomManager에 Room객체를 추가한다.
-3)채팅을 보낸 경우
-클라이언트(안드로이드)에서 유저가 채팅을 보낸 채팅방의 idx번호(pk)와 입력한 채팅을 서버에 보낸다.
-채팅방의 idx(pk)번호로 채팅방을 ArrayList RoomManager에서 Room객체를 찾는다.
-Room객체에 ArrayList<User>에 추가된 User객체의 개수만큼 User객체의 Socket객체를 활용해서 입력한 채팅을
보낸다.
-4)채팅 방에 나간 경우
-클라이언트에서 채팅방의 idx번호(pk)와 유저의 idx 번호(pk) 값을 서버에 보낸다.
-서버에서는 채팅방의 idx번호로 유저가 채팅을 보낸 채팅방(Room)객체를 찾는다.
-채팅방을 찾고 유저 idx번호(pk)로 채팅방에 나가는 유저를 찾아서 삭제한다.
2.단체 채팅방 구현 시 시행착오
1)자료구조에 대해서 충분히 생각하지 않았다.
-위의 설계과정에서 사용한 자료구조는 ArrayList인데 HashMap을 사용하는 것이 속도면에서 더 적합했을 거라는 생각을 했다.
위의 블로그의 나온 테스트 결과를 보면 데이터가 많은 경우 ArrayList는 데이터 추가 할 때 HashMap보다 좋은 성능을 발휘하지만 값을 검색하거나, 삭제할 때는 HashMap은 Key, value 구조를 사용함으로 ArrayList보다 성능이 더 좋다.
만약에 자료구조에 있는 데이터의 검색이나 삭제가 자주 일어나지 않고 추가하는 작업이 많이 일어난다면 ArrayList가 좋겠지만 단채채팅방이나 소켓통신의 특성상 빈번하게 삭제나 검색이 이루어지기 때문에 HashMap이 더 적합하다는 생각을 했다.
2)Thread-Safe한 구조
-멀티스레드 환경에서의 구현이기 때문에 메서드 호출전에 동기화 락을 통해 Thread-Safe한 구조를 취해야 한다.
하지만 HashMap은 동기화락을 제공하지 않기 때문에 ConcurrentHashMap을 사용해야 할 것 같다.
'개발 시행착오 정리' 카테고리의 다른 글
[안드로이드] 리사이클러뷰 안에 리사이클러뷰 사용시 안에 있는 리사이클러뷰가 터치가 안될 때 (0) | 2023.01.05 |
---|---|
[안드로이드] 코틀린 ViewModel + LiveData 이벤트 발생 이슈 해결 (0) | 2022.04.27 |
운동친구 구하는 어플 기획에서 이상한 부분 (0) | 2022.02.14 |
[안드로이드] 코틀린 WebRTC 화상 통화 구현 진행 과정 및 시행착오 정리 (0) | 2022.02.08 |
[안드로이드] 이미지 여러 장 처리 시행착오 및 구현 과정 (1) | 2022.01.27 |
댓글