일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- service
- 도커
- 채팅
- 팀프로젝트
- 프로젝트
- Spring
- axios
- 네티 클라이언트
- 스프링부트
- toyproject
- Kotlin
- JWT
- 배열
- Security
- 자바
- 네티 서버
- recoil
- controller
- Spring Boot
- 기초설정
- MySQL
- 백엔드 설정
- 클래스
- react
- springboot
- Repository
- netty
- 코틀린
- 자료형
- Java
- Today
- Total
hyuko
토이프로젝트 본문
windowBuilder를 사용하기 위한 초반 단계
우리는 STS4라는 IDE 를 통해 작업을 할 예정입니다.
WindowBuilder 라는 GUI프로그램을 쓰기위함입니다!
그렇기 위해서는 아래와 같이 추가해주어야 할 것이있습니다.
help 에 들어가 가장 하단의 Eclipse Marketplace를 들어가줍니다.
그 후에 window 로 검색하여 windowBuilder Current 를 찾아 인스톨 해주면 끝납니다!!
JFrame 과 JPanel
이렇게 윈도우 빌더를 다운을 받고난 후에
프로젝트에서 패키지 하나를 view로 만들고 그 안에
파일을 하나 추가 해주는데용!
Other를 클릭 해주고 난후 windowbuilder를 제대로
다운로드 했다면 나오는 화면이 있습니다.
여기서 JFrame을 선택해서 일반 클래스를 생성하는 것처럼 클래스 양식을 맞춰 생성 해줍니다!
여기서 JFrame 이란 하나의 틀을 만들어주는 형식입니다.
우리가 처음부터 틀과 안에 들어갈 구성요소를 다 따로 만들지는 않을 예정이고 하나의 틀안에 패널들을 추가하고 요소들을 추가해서 만들 예정입니다.
그리고 JPanel 을 용도에 맞게 추가할 예정입니다.
여기서 JPanel 은 눈에보이는 화면이라고 생각하시면 편할 것 같습니다.
Component 구조
하나의 프레임안에 메인이되는 패널을 생성하고
그 패널안에 세가지의 패널을 만드는 구조가 됩니다.
가장 상위의 패널인 mainPanel의 경우에는
카드 레이아웃을 쓰게 되는데 이 카드 레이아웃은
어떠한 기준을 주고 그 기준에 해당됬을 때
패널들을 카드처럼 꺼내서 보여줄 수 있는 레이아웃 입니다.
그리고 그 하위의 join, chattingList, chattingRoom 등은 absolute를 주면서 버튼이나
인풋창 리스트등이 자유롭게 추가 될 수 있는 구조를 만들어 주게 됩니다.
화면 계획
우리의 화면 계획은 카카오톡의 화면과 비슷하게
만들었습니다.
로그인 버튼의 경우에는 카카오톡 API 의 버튼을 사용하게 되었고 플러스 버튼이나 나가기 버튼등은
무료 이미지를 사용하여 만들었습니다.
클라이언트 로직
- 클라이언트에서 주어야할 정보는 닉네임 / 방의 제목
joinNickname.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (joinNickname.getText().isBlank()) {
JOptionPane.showMessageDialog(null, "please input nickname", "입장불가", JOptionPane.ERROR_MESSAGE);
} else {
String ip = "127.0.0.1";
int port = 8889;
try {
socket = new Socket(ip, port);
ClientRecive clientRecive = new ClientRecive(socket);
clientRecive.start();
nickname = joinNickname.getText() + " [" + socket.getLocalPort() + "]";
LoginReqDto loginReqDto = new LoginReqDto(nickname.replaceAll(" ",""));
String joinJson = gson.toJson(loginReqDto);
sendRequest("join", joinJson);
if (socket != null) {
CardLayout mainLayout = (CardLayout) mainPanel.getLayout();
mainLayout.show(mainPanel, "chattingList");
}
} catch (ConnectException ex) {
JOptionPane.showMessageDialog(null, "접속오류", "접속오류", JOptionPane.ERROR_MESSAGE);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
});
소켓이 생성되어 추가될 환경은 우리가 닉네임을
입력한 후 Enter키를 입력하거나 버튼을 눌렀을 경우
해당 서버의 ip주소와 port번호를 소켓에 주어서
서버와 연결되는 구조입니다.
이때 입력창에 입력되는 닉네임이 request 객체로 변환하여 Json 형태로 다시 바꾸어주고 통신을 하는 구조입니다.
이때 소켓이 연결되지 않았거나 입력하는 창에 아무것도 입력하지 않고 조인하려는 경우 등은 들어갈 수 없게 처리해주었고 닉네임들을 모두 입력이되는 당시에 바로 공백을 제거하여 넘겨주게 됩니다.
모든 통신의 형태는 클라이언트에서 정보를 넘겨주고
그 정보를 받은 서버는 저장을 하거나 로직 처리를 한후
리시브에 다시 넘겨주게 됩니다.
클라이언트의 클래스 다이어그램
서버와 서버스레드 클래스로 분리
- 서버는 실행하는 용도로만 쓰고 서버스레드에서 클라이언트의 정보를 받아옵니다.
그리고 프로젝트당 하나의 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 부분들은 하단의 깃헙 링크에서 보실 수 있습니다
'토이프로젝트 > 소켓통신프로젝트' 카테고리의 다른 글
소켓 통신 미니 채팅 프로그램 (0) | 2023.04.25 |
---|---|
소켓통신으로 채팅만들기 (0) | 2023.04.24 |
소켓통신과 스레드 (0) | 2023.04.24 |