기록/TIL

2023/04/18 TIL

쥬냥냥 2023. 4. 19. 19:24
[MultiIt Backend] 

서론

오늘도 역시 에러와의 전쟁을 치뤘다.. 하나의 에러가 끝나면 다음의 에러가 나타나는 ㅎㅎ 역시 코딩은 에러와의 전쟁인 것인가! 그래도 어제 못하던 에러 하나를 잡아서 조금은 뿌듯하다(또 다른 에러가 생긴 것은 안비밀!) 에러를 잡을 때 강사님의 도움을 조금 받았었는데 그때 강사님이 이야기 해주길 개발환경을 통일 시 하는 것과, 트러블 슈팅을 따로 전체적으로 관리하는 것이 좋겠다 팁을 들었다. 나중에 한눈에 볼수 있도록 노션정리? 지금 솔직히 블로그 하나 쓰는 것도 나에겐 대단한 일이지만! 마지막에 내가 어떤식으로 난관을 헤쳐나갔는지 확인하기 위해 평일에는 짧게 메모를 해두고 주말에 모아서 노션에 정리하는 방식으로 할까 생각이 든다. 것보다 이번주 일요일이 정처기 시험인데, 토욜 역시 멘토링이 있어 조금 걱정이다. 정처기 시험도 아직 더 외워야하고(책 정독 1회 완료), 하지만 멘토링때 보여줄 수 있어야하니 프로젝트도 어느정도 진행이 되야할텐데 시간을 잘 조절해 둘 다 진행시키기에는 살짝 어려워서 걱정이다. 우선은 정규 시간에 최대한 프로젝트를 진행을 많이 하고, 나머지 시간에 정처기 공부하는 것이 베스트인데 오늘까지 에러가 계속 생겨서 조금 버벅여서 따로 프로젝트를 위해 1시간 정도라도 진행을 해야하나 고민이다.. 우선은 내가 할 수 있는 것을 열심히 해야겠고, 또한 진짜 앞으로는 뭐든간에 시간이 날때, 미리미리 하는 습관을 좀 들여야할 것 같다. 여유롭게 뭐든지 하다 보니 실질적으로 이렇게 시간이 촉박할 때 그전에 해두지 않은 것 때문에 시간이 남지 않으니... 좀 미리미리 하는 습관과 꾸준히 하는 습관을 기르기 위해 노력해야겠다.


오늘 하루 진행 내용

오늘 하루동안 총 3가지로 분류해 학습을 진행하였다.

에러잡기, 소켓 채팅 연습, 정처기 학습

1. 에러잡기

(1) mysql 인텔리제이 연동 안됨

: 어제부터 겪던 MySql이 연동되지 않고 맞게 설정해도 되지 않는 그 에러를 해결했다!!!! 인텔리제이 내부의 database로 연결하는 부분도 안되서 진짜 mysql에서 계정 관련 문젠가 싶어 계정 생성, 권한부여, 스키마생성을 다시해도 안되고, 찾다보니 intellij의 Data Sources and Drivers에서 기본으로 제공해주는 MySQL 연결 커넥터 connector-java가 버전이 다를때 생길 수 있다는 에러라해서 sql에서 버전확인 후 mysql connector-java가 버전 같은 걸로 연결해도 안되고, 다운그레이드해서 연동해도 안되서 진짜 멘탈이 나갔었다. ping 확인도 해보고, 한 결과 일단 우선 MariaDB는 연동되는걸 보고 아 이건 MySQL 문제다 생각해 과감히 재설치를 선택했다. 확인해보니 내가 노트북을 오래쓰다보니 되게 많이 뭐가 깔려있는데 그중 버전이 이것저것 겹쳐서 생긴 오류같았다.(MySQL 8.0.22, 8.0.37? 이런식으로 여러개가 있었음) 또한 지울때 레지스트리 내부도 다 지워야한다기에 찾아서 지웠다 (추후에 이 에러에 관련해서도 따로 정리해야겠다.). 지운 후 버전을 좀 낮춰 8.0.17로 다 connector까지 변경 시키니 그땐 dateTime 관련 에러가떠서 (SYSTEM으로 설정되어있었음 -> Asia/Seoul) 이를 서울로 변경해주니 다행이 잘 연동되서 해결했다.

 

(2) 타임리프 동작 시 스크립트 실행 안됨

: jsp로 소켓 연습을 하던걸 추후에 어처피 우리팀에서는 jsp보다 타임리프나 html+js를 사용하기로 이야기가 나왔어서 이를 타임리프로 변환했다. 근데 변환 후 실행하니 스크립트에 설정한 이벤트들과 함수들이 실행되지 않았다.

이를 찾아보니 이렇게 타임리프에서 함수, 이벤트를 사용시 비동기적 처리를 위한 ajoxjquery 스크립트를 추가해야 한다는 것을 찾았고 <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.js"> 를 추가 함으로써 해결했다.

 

(3) Enter 입력시  화면이 reload되는 오류

: 소켓을 통한 채팅 개발을 연습 단계다보니 데이터를 저장한 값을 불러오지 않고 임시로 사용자명을 입력해 테스트를 해보는데, 메시지 입력 후 enter 입력 시 메시지가 보내지는 것이 아니라 reload가 되 불편함을 겪었다. 인터넷을 검색해보니 이건 input의 특성 같은 것 때문에 아무런 설정 없이 enter를 누르면 reload가 되는 것을 알고 form안에 onsubmit="return false;"를 설정해 해결했다. (추후 정리 예정, 여러가지 방법이 있음)

 

이런 에러를 잡으며 단순 String 형식으로 소켓을 통한 실시간 채팅 하는 것을 서버와 클라이언트 둘다 연습해 구현해보았고 정규 시간이 끝난 후 정보처리기사 남은 부분을 다 풀어봐 정보처리기사 실기 (시나공) 책을 1회 완독하였다.

<진행 사항>

1.MySql 디비 연동 해결,  2. 소켓 텍스트 형식으로 Chating 연습, 정처기 학습


느낀점

크게 느낀점이라고 하기보단 그냥 에러를 보는 법과, 어제 느낀 것과 비슷하게 해결하는 방법 등의 중요성을 더 생각해볼 수 있었다. 지금은 통합하는 단계가 아니라 개인의 작업을 진행하다보니, 이런 전체적인 시스템의 구조, 비지니스로직, 어떤식으로 코드를 짜고 어떤식으로 실행되는지 같은 걸 좀 공부할 수 있고 그를 통해 크게 배워가는 것 같다. 특히 예전에 나같은 경우는 프로젝트를 진행 시 전체적인 코드를 이해하며 내가 스스로 짜보는 것이 아닌 약간 여러개의 코드를 단순히 조합해, 정확히 어떤 로직으로 이루어지는지 모른체 개발을 진행했다. 하지만 지금은 소켓 자체가 처음 배우는 것이기도하고 전체적인 실행 로직과, 그 안의 구조가 어떤 식으로 작용해 이런 기능이 구현이 가능하게 되는지를 아는 것에 대한 중요성을 깨달아 원리와 개념부터 찾아보며 코드를 구현하고 연습하다보니 확실히 관련부분 에러가 나면 캐치할 수 있고, 남의 코드를 보고 그대로 가져다 쓰는 것이 아닌, 스스로 코드를 구현할 수 있게 된것같다. 확실히 전체의 로직과 구조를 알아야 실제 기능을 개발할 때 효율적이고, 나만의 코드로 개발할 수 있다는 것을 느낄 수 있었다. 또한 팀원들이 다들 너무 열심히 각자의 부분을 맡아 진행하고, 순조롭게 흘러가는 것 같아서 나도 더 열심히 해야겠다는 의지를 다잡을 수 있었다.


배운점

 일단 위의 에러 잡기 부분에 적어둔 db 연동시 그 버전에 맞게 connector가 서로 일치해야하고, 타임리프에서 스크립트 사용시 필요한 선언, input은 엔터입력시 리로드 등 관련되서 여러가지를 배울 수 있었다.(이는 전부 추후에 정리해야 할 것 같다) 또한 전체적인 소켓을 통한 채팅 로직과 웹소켓의 개념에 대해 배울 수 있었다.


+ 개념 & 용어 

소켓을 통한 단순 text 채팅 (추후 제대로 정리)

1. 스프링 부트 프로젝트 설정 (gradle, thyleaf를 통해 뷰 구현)

//thyleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
//spring web
implementation 'org.springframework.boot:spring-boot-starter-web'
//websocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'

2. 채팅방 뷰를 보여줄 controller 생성

: thyleaf의 경우 String으로 넘겨주면 내부에서 알아서 찾아서 같은 이름의 html 파일 반환

@Controller
@RequiredArgsConstructor
public class ChatController {  
    @GetMapping("/chat")
    public String chat(){
        return "chat_ex";
    }  
}

3. SocketHandler 생성 (실제 작동할 내부 동작 구현체 : TextWebSocketHandler를 상속받음)

@Component
public class SocketHandler extends TextWebSocketHandler {
	HashMap<String, WebSocketSession> sessionMap = new HashMap<>(); //접속한 사용자 정보 저장할 맵 (세션값..)
	@Override
	public void handleTextMessage(WebSocketSession session, TextMessage message) {
		//메시지를 수신 시 실행 
        String msg = message.getPayload(); //메시지에 담긴 텍스트값 가져오기
		for(String key : sessionMap.keySet()) { 
			WebSocketSession wss = sessionMap.get(key); //맵에 저장된 모든 세션값을 가져와
			try {
				wss.sendMessage(new TextMessage(msg)); //그 세션값으로 메시지를 전송해줌
			}catch(Exception e) { 					   //즉 접속한 모든 유저에게 메세지 출력
				e.printStackTrace();
			}
		}
        }
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		//소켓 연결시 동작 
        super.afterConnectionEstablished(session); 
		sessionMap.put(session.getId(), session); //접속한 모든 사용자의 세션을 맵에 저장해둠
        }										  //세션값을 저장해둬야 실시간으로 메시지 전송 가능..
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		//소켓 종료시 동작 
        sessionMap.remove(session.getId()); ///종료시 해당되는 세션값을 맵에서 제거
		super.afterConnectionClosed(session, status); //제거해줘야 메시지 전송 안됨.. (닫기)
        }
}​

4. WebSocketConfig 생성 (생성한 구현체를 빈으로 등록 -> 등록해줘야 스프링에서 사용가능)

@Configuration //빈 등록하기 위한 애노테이션
@EnableWebSocket //Websocket을 활성화
public class WebSocketConfig implements WebSocketConfigurer{
    @Autowired
	SocketHandler socketHandler; //실 구현체
	
	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		//해당 endpoint로 handshake 이루어짐
        registry.addHandler(socketHandler, "/chating"); 
        //WebSocketHandlerRegistry에 WebSocketHandler의 구현체를 등록
        //등록된 Handler는 특정 endpoint("/chating")로 handshake를 완료한 후 맺어진 connection을 관리
	}
}

5. chat_ex.html 생성 (실제 작동할 뷰로 나는 타임리프를 이용해 구현했다.) -> 코드가 너무 길어 함수로직만 설명

<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.js"/> <!--타임리프에서 스크립트 실행을 위해 추가-->

<script th:inline="javascript"> 
    /*<![CDATA[*/
    var ws;

    function wsOpen(){	//소켓 연결
        ws = new WebSocket("ws://"+location.host+"/chating"); //endpoint 위에 chating으로 설정해둠
        wsEvt();
    }
    function wsEvt(){	//메시지 화면에 띄여주기
        ws.onopen = function(data){ //소켓이 열리면 초기화 세팅}
		ws.onmessage = function(data) {
			var msg = data.data; //들어온 데이터 변수 저장
			if(msg != null && msg.trim() != ''){ //만약 메시지가 null이거나 빈칸으로만 존재하지 않다면
                                                 //들어온 메시지가 있다면
				$("#chat").append("<p>" + msg + "</p>"); //메시지를 화면에 출력
			}
		}
		document.addEventListener("keypress", function(e){ //엔터입력시 모든사람에게 send
			if(e.keyCode == 13){ //enter press, 13이 엔터의 keyCode
				send();
			}
		});
    }
    function chatName(){ //사용자 이름 받기 (임의로 입력하게 설정)
        var userName = $("#userName").val(); //username input 값 가져오기 = 입력한 이름
        if(userName==null || userName.trim()===""){ //입력이 null, 빈칸이라면
            alert("사용자 이름을 입력해주세요."); //입력안됬으니 알림
            $("#userName").focus(); //input칸에 초첨 깜빡 거림
        }else {
            wsOpen(); //사용자이름을 받았다면(user 존재) 소켓 열어줌..
            $("#yourName").hide();
            $("#yourMsg").show();
        }
    }

    function send(){ //메시지 전송
        var username = $("#userName").val(); //입력받은 사용자이름 가져오기
		var msg = $("#chat").val(); //입력받은 메시지 내용 가져오기
		ws.send(username+" : "+msg); //이름: 메시지 내용 -> 형태로 연결된 모든 세션 사용자에게 전달
		$('#chat').val("");
    }
    /*]]>*/
</script>​

! 타임리프에서 스크립트 사용을 위해선 아래와 같은 형식으로 묶어줘야함

<script th:inline="javascript"> 
    /*<![CDATA[*/
    스크립트 내용
    /*]]>*/
</script>

과정을 통해 간단히 텍스트 실시간 채팅을 구현해봤다.


웹 소켓(Web Socket)

: 두 프로그램 간의 메시지 교환을 위한 통신 방법 중 하나

  • 현재 인터넷 환경(HTML5)에서 많이 사용
  • 웹 소켓을 지원하는 브라우저의 경우 웹 소켓 프로토콜을 지원
  • W3C와 IETF에 의해 자리잡은 표준 프로토콜 중 하나

웹 소켓 통신 과정

Tcp/Ip 접속 요청  → Tcp/Ip 접속 수락  → 웹소켓 열기 핸드쉐이크 요청 → 웹소켓 열기 핸드쉐이크  수락  [연결 시작]  → 웹소켓 데이터 양방향 송, 수신  [통신]  → close (한쪽에서 통신 종료 요청) = [연결닫힘]

 

웹 소켓의 특징

 1. 양방향 통신(Full-Duplex)

  • 데이터 송수신을 동시에 처리할 수 있는 통신 방법
  • 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있음
  • 통상적인 Http 통신은 Client가 요청을 보내는 경우에만 Server가 응답을 하는 단방향 통신

2. 실시간 네트워킹(Real Time-Networking)

  • 웹 환경에서 연속된 데이터를 빠르게 노출
  • ex) 채팅, 주식, 비디오 데이터웹소