멀티 채팅을 위해선
서버에서는 대화하는 사람의 이름을 저장하고
모두에게 메세지를 전송하는 작업을 해야된다
그래서 이름은 map을 통해 socket과 함께 이름을 저장하도록하여 중복을 피하도록하며
(이 때 같은 타이밍에 같은 이름을 작성할 수도 있으므로 동기화처리를 필수로 한다)
메세지 수신용 스레드를 따로 만들어서 클라이언트에서 받은 메세지를 서버에서 모두에게 메세지를 출력하도록 한다
Server
- 서버 시작 메서드
- 클라이언트에게 메서지를 전송하는 ' 메서드 '
(전체에게 전송해야하므로 clientMap에 저장된 데이터 개수만큼 반복 처리하여 DateoutputStream을 처리) - 클라이언트에게 받은 메세지를 전체에게 보내는 스레드 클래스
(처음 시작할 때 이름 데이터 중복검사를 한 후 ' 메서드 '를 이용하여 클라이언트에게 메세지를 전송)
package kr.or.ddit.basic.tcp;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class TcpMultiChatServer {
// 접속할 클라이언트 정보를 저장할 Map객체변수 선언
// ==> key값 : 접속한 사람 이름, value값 : 접속한 Client의 Socket객체
private Map<String, Socket> clientMap;
// 생성자
public TcpMultiChatServer() {
// cilentMap을 동기화 처리가 가능하게 생성한다.
clientMap = Collections.synchronizedMap(new HashMap<String, Socket>());
}
public static void main(String[] args) {
new TcpMultiChatServer().chatStart();
}
private void chatStart() {
ServerSocket server = null;
Socket socket = null;
try {
server = new ServerSocket(7777);
System.out.println("서버가 시작되었습니다");
while(true) {
socket = server.accept(); // 클라이언트의 접속을 기다린다
// 접속한 클라이언트 정보 출력해보기
System.out.println("["+socket.getInetAddress()+" : "+socket.getLocalPort()+"]에서 접속했습니다..");
// 스레드 처리
ServerReceiver th = new ServerReceiver(socket);
th.start();
}
} catch (Exception e) {
// TODO: handle exception
}
}
// clientMap에 저장된 전체 사용자에게 메세지를 보내는 메서드
private void sendToAll(String msg) {
// clinetMap에 저장된 데이터 개수만큼 반복 처리한다
for(String name : clientMap.keySet()) {
try {
// key값과 한 짝으로 저장된 Socket객체를 이용하여 출력용 스트림 객체를 생성한다
DataOutputStream dout = new DataOutputStream(clientMap.get(name).getOutputStream());
dout.writeUTF(msg);
} catch (Exception e) {
// TODO: handle exception
}
}
}
// 서버에서 클라이언트로 메세지를 전송하는 Thread를 작성한다
class ServerReceiver extends Thread{
private Socket socket;
private DataOutputStream dout;
private DataInputStream din;
// 생성자
public ServerReceiver(Socket socket) {
this.socket = socket;
try {
// 소켓을 이용하여 스트림 객체생성
dout = new DataOutputStream(this.socket.getOutputStream());
din = new DataInputStream(this.socket.getInputStream());
}catch (Exception e) {
// TODO: handle exception
}
}
// 한 클라이언트가 접속이 완료되면 이 클라이언트는 처음에 '대화명'을 입력받아서 전송한다
// 서버는 '대화명' 수신하고 이 '대화명'이 중복되는 지 여부를 검사하여 검사 결과를
// 클라이언트에게 보내준다 (대화명이 중복되지 않을 때 까지 반복한다)
// 대화명이 중복되지 않으면 해당 대화명과 Socket정보를 Map에 추가한다
@Override
public void run() {
String name = "";
try {
// 대화명이 중복되지 않을 때 까지 반복..
while(true) {
name= din.readUTF(); // 대화명 수신하기
// 대화명 중복 검사
if(clientMap.containsKey(name)) {
// 중복될 때
dout.writeUTF("대화명중복");
} else {
// 중복 X
dout.writeUTF("사용가능");
break; // 반복문 탈출
}
}
// 지금 접속한 사람을 기존에 접속되어있는 사람들에게 알려준다.
sendToAll("["+name+"] 님이 대화방에 입장했습니다");
// 대화명과 클라이언트의 Socket객체를 Map에 추가
clientMap.put(name, socket);
System.out.println("현재 접속 자 수 : "+ clientMap.size() + "명");
// 한 클라이언트가 보내온 메세지를 받아서 다른 모든 접속자에게 전송한다.
while(din!=null) {
sendToAll(din.readUTF());
}
} catch (Exception e) {
// TODO: handle exception
} finally {
// 이 finally 영역이 실행된다는 것은 클라이언트의 접속이 종료되었다는 것을 의미한다
sendToAll("["+name+"] 님이 접속을 종료했습니다");
// Map에서 해당 자료를 삭제한다.
clientMap.remove(name);
System.out.println("["+socket.getInetAddress()+" : "+socket.getLocalPort()+"] 에서 접속종료했습니다..");
System.out.println();
System.out.println("현재 접속 자 수 : "+ clientMap.size() + "명");
System.out.println();
}
}
}
}
Client
- 클라이언트 시작 메서드
- 메세지 전송용 스레드 클래스
(전송 시 이름이 필요하므로 생성자에서 이름 중복확인) - 메세지 출력용 스레드 클래스
package kr.or.ddit.basic.tcp;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;
public class TcpMultiChatClient {
public static void main(String[] args) {
new TcpMultiChatClient().clientChatStart();
}
// 시작 메서드
private void clientChatStart() {
Socket socket = null;
try {
socket = new Socket("localhost", 7777);
System.out.println("서버에 접속되었습니다");
// 스레드처리
ClientSender s = new ClientSender(socket);
ClientReceiver r = new ClientReceiver(socket);
s.start();
r.start();
} catch (Exception e) {
// TODO: handle exception
}
}
// 메세지 전송용 스레드
class ClientSender extends Thread{
private Socket socket;
private DataOutputStream dout;
private DataInputStream din;
private String name;
private Scanner scan;
// 생성자
public ClientSender(Socket socket) {
this.socket = socket;
scan = new Scanner(System.in);
try {
din = new DataInputStream(this.socket.getInputStream());
dout = new DataOutputStream(this.socket.getOutputStream());
if(din!=null) {
// 대화명을 입력해서 서버로 전송하고 서버에서
// 대화명 중복 여부를 검사한 결과를 받아오는 작업을
// 대화명이 중복되지 않을 때 까지 반복한다.
while(true) {
System.out.print("대화명 입력 >> ");
String name = scan.nextLine();
dout.writeUTF(name);
// 대화명의 중복 여부 검사 결과를 받는다
String feedback = din.readUTF();
// 중복 여부 확인하기
if(feedback.equals("대화명중복")) {
// 대화명이 중복될 때
System.out.println(name + "은(는) 중복되는 대화명입니다.");
System.out.println("다른 대화명을 입력하세요..");
} else {
// 대화명이 중복되지 않을 때
this.name = name;
System.out.println(name+" 대화명으로 대화방에 입장했습니다...");
break;
}
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
@Override
public void run() {
try {
while(dout!=null) {
// 키보드로 입력한 메세지를 서버로 전송한다.
dout.writeUTF("["+name+"] : "+ scan.nextLine());
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
// 메시지 수신용 스레드
class ClientReceiver extends Thread{
private Socket socket;
private DataInputStream din;
// 생성자
public ClientReceiver(Socket socket) {
this.socket = socket;
try {
din = new DataInputStream(this.socket.getInputStream());
} catch (Exception e) {
// TODO: handle exception
}
}
@Override
public void run() {
while(din!=null) {
try {
// 서버로 부터 받은 메세지를 화면에 출력한다
System.out.println(din.readUTF());
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
}
'JAVA > HIGH JAVA' 카테고리의 다른 글
[JAVA] JDBC (0) | 2024.05.13 |
---|---|
[JAVA] 네트워킹 - UDP (0) | 2024.05.10 |
과제 - POI 라이브러리 (0) | 2024.05.10 |
5/9 Homework - 네트워킹을 이용한 파일 복사 (0) | 2024.05.09 |
[JAVA] 네트워킹 - 소켓 프로그래밍 (0) | 2024.05.09 |