[Java]소켓 멀티 채팅
목차
소켓(Socket) 기초
- 소켓은 IP 정보와 포트 정보(포트 번호)로 이뤄진 클래스다.
- PC와 PC가 통신하려면 소켓을 통해(IO) 한다.

소켓은 데이터를 주고받아야 하므로, 각 소켓은 상대방의 정보를 알아야 한다.
1. Socket이 만들어지는 과정 (TCP 3-way HandShake)

서버 쪽에는 소켓 말고 서버 소켓이 만들어진다. (소켓과는 별개의 개념)
- 서버 소켓은 통신을 하기 위한 소켓이 아니라 서버 쪽에만 만들어진다.
- 클라이언트로부터 접속을 받기 위해서만 만들어진다.
- 서버 소켓을 만들려면 클라이언트가 접속할 수 있도록 포트 번호를 지정해줘야 한다.
- 즉, 서버 소켓은 클라이언트가 접속하기 위한 출입문 역할을 한다.
접속 과정
- 서버가 9000번 포트를 열고 대기한다.
- 클라이언트가 접속을 시도한다.
- 클라이언트는 서버로 갈 때 자기 정보를 가지고 간다.
- 그리고 포트가 열려서 나가야 한다. (클라이언트도 문을 열고 나간다.)
2. JAVA에서 Socket 만들기 (ServerSocket, Socket, import java.net.*)

- 클라이언트도 내부적으로 포트를 여는데, 몇 번이 열릴지는 모른다.
- 접속하지 않으면 자기 정보를 전혀 알 수 없다.
- TCP/IP에서는 클라이언트가 서버에 접속할 때 자기 정보를 반드시 넘겨준다. 그것이 IP 정보와 포트 번호다.
- 접속을 시도할 때 자신의 정보도 함께 넘어간다는 점이 중요하다.
클라이언트가 접속되면 서버는 어디서 왔는지, 몇 번 포트를 열고 오는지를 식별한다. 이 서버 소켓은 클라이언트의 접속을 대기한다.
서버 소켓이 클라이언트를 식별하면, 다른 쪽으로 통신용 소켓을 하나 만든다. (서버 소켓은 통신용이 아니므로, 통신을 위해 다른 포트로 연결한다.)
- 이때 9001번 등 어떤 포트가 열릴지는 모른다.
- 클라이언트를 식별하고 통신하기 위해 다른 쪽 포트를 열어준다.
- 이것을 **바인딩(binding, 연결시켜준다)**이라고 한다.
클라이언트를 식별하고 바인딩하는 것이 두 번째이며, 이로써 소켓이 완전히 만들어진다.
- 이 소켓은 데이터 통신이 가능해진다.
- 이 소켓은 클라이언트와 통신이 가능하며, 클라이언트의 정보를 알아야 하므로 클라이언트 정보가 들어간다.
서버에서 만들어진 소켓은 클라이언트 정보를 알고 있다.
서버에서 소켓이 만들어지면 서버의 정보를 클라이언트에게 넘겨준다. 서버의 정보를 클라이언트에 줘야 클라이언트도 그것을 통해 서버와 통신할 수 있다.
서버 소켓이 만들어지고 정보를 주면, 클라이언트의 소켓도 완성된다. 클라이언트도 서버 정보를 알고 있어야 한다.
이것이 3-way HandShake의 기본이다.
소켓과 클라이언트는 상대방의 정보를 알고 3-way HandShake를 한다.
만약 클라이언트가 하나 더 있으면? 똑같이 하나 더 추가하면 된다. 소켓 정보를 알고, 클라이언트 정보를 넘겨주며, 서버 정보를 받아와 통신한다.
정리: 서버 소켓 동작 순서
- 서버 소켓을 만들어, 대기할 수 있는 9000번 포트를 만들어준다.
- 서버 입장에서 포트가 열리면 접속 여부를 어떻게 알까? → 계속 체크한다. 이것이 서버 소켓의
accept메서드로, 서버에서 대기하며 블로킹(blocking) 상태로 접속을 대기한다. (블로킹 메서드라고도 한다.) - 접속하면 식별한다. (클라이언트 정보가 넘어오는데, 이를 통해 클라이언트를 식별하고 정상적인 요청이면 수락, 아니면 거부한다.)
- 통신하도록 다른 포트를 열어서 바인딩한다.
- 소켓에 클라이언트의 정보가 들어간다.
- 서버 쪽도 클라이언트에 정보를 넘겨줘야 클라이언트도 서버에 정보를 전달할 수 있다.
서버 입장에서 클라이언트가 여러 개 접속하면? 소켓 관리를 리스트든 해시맵이든 배열적인 요소에 담아둔다. 이 소켓 정보는 1번, 2번 식으로 저장해야 한다. (당연히 1:1일 때는 필요 없다.)
3. Socket을 이용한 멀티 채팅(Multi-Chatting) 만들기

- HashMap의 키에 아이디나 이름을 넣고, 값으로 메시지를 보낸다.
- 소켓에서 클라이언트가 메시지를 받으면 모든 클라이언트에게 정보를 뿌려줘야(브로드캐스팅) 하므로, Output(출력 스트림)이 있어야 한다. 소켓 정보의 출력 스트림을 해시맵에 저장한다.
- 서버 입장에서는 응답하기 위한 출력 스트림이 있으면 된다.
- 이 해시맵 안에 있는 것을 반복문으로 모든 클라이언트에게 뿌려주는 것이 브로드캐스팅이다.
- 마지막에 클라이언트가 quit을 누르면 소켓이 close된다. 해시맵에서 빠지면 서버 창으로 뿌려지게 한다.

- ‘박’이라는 사람이 입장하면, 해시맵의 키에는 ‘박’, 값에는 출력 스트림이 저장된다.
- ‘리’도 마찬가지로 해시맵에 들어간다.
- 서버 입장에서는 소켓이 접속하면 스레드를 만든다.
- 해시맵의 정보를 읽어와 반복문으로 모든 항목에 브로드캐스팅한다.
- 클라이언트는 sender로 보낼 때 소켓 정보가 있어야 한다. (서버에 전송하기 위한 작업)
- receiver는 수신해서 도스 창에 출력한다. 이것도 소켓이 있어야 수신할 수 있다.

- 서버를 먼저 구동하고 해시맵 객체를 만든다.
- 클라이언트를 해시맵에 담는다. 브로드캐스팅할 때 동기화가 되지 않으므로, 다른 클래스와 맞춰주기 위해 동기화된(synchronized) 해시맵으로 맞춰준다.

- 서버에는 HashMap이 있는데, 키에는 ‘박’, 값에는 output(출력 스트림)이 들어간다.
- 서버 입장에서 클라이언트가 입장하면 스레드를 만든다. 이 해시맵 정보를 읽어와 열거형으로 반복문을 써서 모든 항목에 브로드캐스팅(방송)하여 쫙 뿌린다. 모든 클라이언트에게 다 뿌려주게 된다.
- 클라이언트는 sender를 통해 보낸다. 소켓 쪽에 name을 통해 보낸다.
- receiver는 받을 때 필요하다. 서버에서 요청을 받아오면 출력한다.
서버가 하는 일
- 서버가 시작되면 클라이언트 접속을 계속 기다린다.
accept로 기다리다가 소켓이 만들어지면, 서버에서 만들어졌지만 IP 주소나 포트 번호를 가져온다.- 서버는 클라이언트로부터 이름과 소켓을 만든다. 이 소켓 정보를 통해 클라이언트에서 정보가 날아오면 스레드를 실행하여 receiver가 받고 run한다.
- 이 스레드는 생성자에서 소켓을 받아 소켓 정보로 입출력 스트림을 만든다.
- 입출력 IO를 만들고 run 메서드를 만들어 실행한다.
- 해시맵에 이미 같은 객체(‘박’)가 있으면 입장하면 안 된다. 소켓을 생성했더라도 커넥션을 끊어버린다.
- 같은 사용자가 없으면
sendToAll로 접속된 모든 클라이언트에게 보낸다. 해시맵에서 모든 키값을 가져와, 해당 키의 출력 스트림을 가져와 클라이언트에게 뿌려준다. - 그 사람을
clientPut하면 출력 스트림이 추가된다. - while문을 통해 입력을 받는다.
- 붙어 있는 클라이언트가 read하고 전체에 브로드캐스팅한다.
여기까지가 서버가 주로 하는 일이다.
클라이언트가 하는 일
- 소켓을 만들고, 키보드로부터 입력을 받아 소켓으로 전달한다.
- 소켓 정보를 스레드로 시작한다.
- 보내는 스레드는 서버가 있어야 보낸다.
- 보내는 스레드는 출력 스트림(Output), 받는 스레드는 입력 스트림(Input)으로 받기만 하면 된다.
- while문으로 입력을 계속한다. 그것을
readUTF로 받아 출력하면 된다. - 즉, 클라이언트는 보내는 스레드와 받는 스레드 두 개를 만들면 된다.