그리고 프로젝트당 하나의 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 부분들은 하단의 깃헙 링크에서 보실 수 있습니다