미니 채팅프로그램 소켓통신 프로젝트(서버)

hyuko·2023년 2월 27일
0

서버와 서버스레드 클래스로 분리

  • 서버는 실행하는 용도로만 쓰고 서버스레드에서 클라이언트의 정보를 받아옵니다.

그리고 프로젝트당 하나의 main문만 있어야하기 때문에 싱글톤으로 제작해줍니다.

public class Server {
	//Main이 도는 순간 돌아가기 위해 server 를 싱글톤으로 제작한다.
	
	private static Server instance;
	private ServerSocket serverSocket;
	private ServerThread serverThread;
	private Socket socket;
	
	public static Server getInstance() {
		if(instance == null) {
			instance = new Server();
		}
		return instance;
	}
	
	private Server() {}
	
	public void start() {
		try {
			serverSocket = new ServerSocket(8889);
			System.out.println(" 서버 시작 ");
			
			while(true) {
				socket = serverSocket.accept();
				System.out.println(socket);
				boolean connected = socket.isConnected() && !socket.isClosed();
				if(connected) {
					serverThread = new ServerThread(socket);
					serverThread.start();
				}
				
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (StringIndexOutOfBoundsException e) {
			System.err.println("클라이언트 강제종료 되었다.");
		} finally {
			if(serverSocket != null) {
				try {
					
					serverSocket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(" 서버 종료 ");
		}
		
	}
}

위의 코드를 한번 보자면 ,
ServerSocket 을 열어주고 이 것을 열게되면 클라이언트의 Socket과
연결이 가능해집니다. 클라이언트의 소켓연결을 기다리기 위해서
socket = serverSocket.accept(); 를 실행해 줍니다.
연결이 될 때까지 while 문을 계속 돌게됩니다.
그리고 이 소켓이 연결되는 순간 ServerThread를 start해주게 됩니다.

@Getter
public class ServerThread extends Thread{
	@Getter
	private static List<ServerThread> socketList = new ArrayList<ServerThread>();
	private static List<Room> rooms = new ArrayList<>();
	
	@Getter
	private final Socket socket;
	private InputStream inputStream;
	private Gson gson;

	private String username;
	private Room room;

	private ServerUtil serverUtil;
	private boolean isRunning = true;

//	private String exitNickname;

	public ServerThread(Socket socket) {
		this.socket = socket;
		this.gson = new Gson();
		socketList.add(this);

	}



	@Override
	public void run() {
		try {
			inputStream = socket.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
			
			while(isRunning) {
				
				String request = reader.readLine();

				RequestDto<String> requestDto = gson.fromJson(request, RequestDto.class);
				OutputStream outputStream;
				PrintWriter writer;
				ResponseDto responseDto;

				switch (requestDto.getResource()) {
				case "join":
					JoinReqDto joinReqDto = gson.fromJson(requestDto.getBody(), JoinReqDto.class);
					username = joinReqDto.getNickname();
					reflashRoomList();
					break;

				case "message":
					MessageReqDto messageReqDto = gson.fromJson(requestDto.getBody(), MessageReqDto.class);
					String message = messageReqDto.getMessage();
					String toUser = messageReqDto.getToUser();
					
					MessageRespDto messageRespDto = new MessageRespDto(toUser, message);
					String messageJson = gson.toJson(messageRespDto);
					
					responseDto = new ResponseDto(requestDto.getResource(), "ok", messageJson);
					
					sendAll(responseDto, room.getUsers());
					break;

				case "createRoom":
					CreateRoomReqDto createRoomReqDto = gson.fromJson(requestDto.getBody(), CreateRoomReqDto.class);
					
					room = new Room(createRoomReqDto.getKingName().replaceAll(" ", ""), createRoomReqDto.getRoomName());
					room.getUsers().add(this);
					rooms.add(room);
					
					
					CreateRoomRespDto createRoomRespDto = new CreateRoomRespDto(createRoomReqDto.getRoomName(), createRoomReqDto.getKingName().replaceAll(" ", ""));
					responseDto = new ResponseDto(requestDto.getResource(), "ok", gson.toJson(createRoomRespDto));
					
					sendAll(responseDto, room.getUsers());
					
					reflashRoomList();

					break;

				case "joinRoom":
					JoinRoomReqDto joinRoomReqDto = gson.fromJson(requestDto.getBody(), JoinRoomReqDto.class);

					String roomName = joinRoomReqDto.getRoomName();
					rooms.forEach(room -> {
						if(("채팅방" + ": " + room.getRoomName()).equals(roomName)) {
							this.room = room;
						}
					});

					room.getUsers().add(this);
					
					String joinName = joinRoomReqDto.getJoinName();

					JoinRoomRespDto joinRoomRespDto = new JoinRoomRespDto(joinName.replaceAll(" ", ""), roomName);
					String joinRoomJson = gson.toJson(joinRoomRespDto);

					responseDto = new ResponseDto(requestDto.getResource(), "ok", joinRoomJson);
					sendAll(responseDto, room.getUsers());
					break;

				case "exitRoom":
					ExitReqDto exitReqDto = gson.fromJson(requestDto.getBody(), ExitReqDto.class);
					String exitNickname = exitReqDto.getNickname();
					
					deleteRoomList(exitNickname.replaceAll(" ", ""));
					
					break;
				case "exit":
					ForceQuitReqDto forceQuitReqDto = gson.fromJson(requestDto.getBody(), ForceQuitReqDto.class);
					
					deleteRoomList(forceQuitReqDto.getNickname().replaceAll(" ",""));
//					deleteRoomList(exitNickname);
					isRunning = false;
					PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
					out.println(gson.toJson(new ResponseDto("exit", "ok", null)));
					break;
				
				default:
					break;
				}
			}
		} catch (NullPointerException e) {
			
			System.err.println("클라이언트 강제종료!!!!");
		} catch (SocketException e) {
			System.out.println("소켓 문제 있다.");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (StringIndexOutOfBoundsException e) {
			System.out.println("강제종료 됬는데?");
		}
	}
	
	public void deleteRoomList(String exitNickname) {
		List<String> username = new ArrayList<>();
		Room roomToRemove = null;

		for (Room room : rooms) {
			username.add(exitNickname.replaceAll(" ",""));
			if(room.getKingName().equals(exitNickname.replaceAll(" ",""))) {
				roomToRemove = room;
				
				break;
			}else if (room.getUsers().contains(this)) {
				ExitRespDto exitRespDto = new ExitRespDto(exitNickname.replaceAll(" ",""));
				String exitString = gson.toJson(exitRespDto);
				ResponseDto responseDto = new ResponseDto("exitRoom", "ok",exitString);
				room.getUsers().remove(this);
				sendAll(responseDto, room.getUsers());
				reflashRoomList();
				return;
			}
		}
		if (roomToRemove != null) {
			ResponseDto responseDto = new ResponseDto("exitKingRoom", "ok", gson.toJson(username));
			rooms.remove(roomToRemove);
			sendAll(responseDto, room.getUsers());
			reflashRoomList();
			return;
		}
	}
	
	
	public void reflashRoomList() {
		List<String> roomNames = new ArrayList<>();
		rooms.forEach(room -> {
			roomNames.add("채팅방" + ": " + room.getRoomName());
		});
		ResponseDto responseDto = new ResponseDto("reflashRoom", "ok", gson.toJson(roomNames));
		
		sendAll(responseDto, socketList);
	}
	
	
	public void sendAll(ResponseDto responseDto, List<? extends ServerThread> threadList) {
		
		for (ServerThread s : threadList) {

			OutputStream outputStream;
			try {
				outputStream = s.getSocket().getOutputStream();

				PrintWriter writer = new PrintWriter(outputStream, true);

				writer.println(gson.toJson(responseDto));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
}

해당 스레드에서는 연결된 각각의 소켓에서 넘어오는 resource에 따라서
switch case문을 돌면서 맞는 값을 찾아 행동을 취하게 됩니다.
각각 조인 / 방생성 / 채팅메세지/ 나가기 등의 행동을 취하게 되는데
자주 쓰는 기능을 메소드로 빼서 기능에 맞는 일을 처리하게 해줍니다.

좀더 자세한 코드나 DTO Entity 부분들은 하단의 깃헙 링크에서 보실 수 있습니다

https://github.com/hyuk12/socket/tree/main/server

profile
백엔드 개발자 준비중

0개의 댓글