JAVA

websocket:채팅

docc 2021. 7. 27. 17:16

websocket + 네트워크운영 이해

데이터 보낼 때 송수신 측에서 받는 방식

1) TCP/IP

수신측이 보내는 데이터는 패킷단위로 전송되어 송신측이 받는다.

핸드쉐이크 : 서로 데이터 유실이 없는지 확인하여 계속 소통하며 확인한다.

- 장점 : 안전하다.

- 단점 : 느리다.

2) UDP

핸드쉐이크가 없다.

- 장점 : 빠르다 (TV 같은 것, 빠르다)

- 단점 : 데이터가 몇몇 유실될 수 있다.(가끔 끊기는 이유)

* TCP/IP 기반의 프로토콜 종류
(1) http
: TCP 기반에서 더 추가하여 http 라는 규약이 생긴 것.
(2) smtp
(3) FTP
(4) 사용자가 직접 TCP/IP 기반의 프로토콜을 만들 수도 있다.

* 네트워크 운영 개념
client - server 소통 중
server
1) 서버 socket 준비
2) bind(80) : port 가 80 으로 시작하는 얘로 연결걸 때 본인이 나오겠다고 설정함
(톰캣이면 bind 가 8080)
3) listen (항상 대기)
4) accept ( client 가 연결을 바랄 때 응답 )
5) receive : 클라이언트 socket 을 전달받음
client :
1) 클라이언트 socket 준비
2) connect :서버에 연결을 시도:연결을 걸기만 할 뿐 받아들이는 건 못함
3) send : 서버가 accept 한 후 서버에 클라이언트 socket 을 전달
4) close : 통신끝나고 클라이언트가 통화 끝냄 ( 서버에 끝냈다고 전달 하는 것 )
socket?
의사소통을 주고받는 도구(전화기같은 느낌)
client 가 connect 걸어서 socket 으로 소통을 시도 -> acept 하여 server 에 있는 socket 이 반응
* 네트워크 운영 중 서버 안의 클라이언트 끼리 소통할 수 있는 방법 ( = 이게 채팅 )
같은 서버안이라도 서버와 클라이언트의 대화지 클라이언트끼리는 서로를 인식할 수가 없다.
아래에 예제 참고!
1번 방법 : 메세지를 보내는 클라이언트를 server 로 만들어 준다.
( 직접 server 로 만들어 위의 '네트워크 운영 개념' 대로 server가 하는 작업을 만들어줘야 한다.
하지만 아래 spring에서 실습에서 사용한 라이브러리가 저 과정을 자동으로 해줌 )
javascript 에서 작업
1) new WebSocket : 기본적으로 js 에 있는 객체 ( 그래서 브라우저도 알고 있다. )
이를 통해 클라이언트의 소켓들을 받아서 서로에게 마치 전달되는 것처럼(server처럼)만들어 줌
spring 에서 작업 ( 현재 서버는 spring 운영으로 가는 중 )
1) 'spring-websocket' library 의존성에 다운 ('2)' 의 클래스가 있는 라이브러리.)
+ 'jackson-databind' library ( ajax 통신할 때 다운받는것 // 이미 받았다. //없다면 아래 참고하여 다운! )

2) TextWebSocketHandler : 이 클래스를 상속받아 handleTextMessage() 메서드를 override 해준다.
= 메세지 처리기 ( 클라이언트에서 온 메시지 처리 )

3) WebSocketConfigurer: 이 인터페이스를 구현하여 registerWebSocketHandlers() 메서드를 override 하여 '2)' 설정한 클래스를 add
= 메세지 처리기 등록 ( spring 이 javascript 의 WebSocket 에 대응하여 응답하라고 알려주기 위해 등록 )

( 마치 resource 가 오면 WebMvcConfigurer를 상속받은 클래스로 addResourceHandlers() 메서드를 override 해서
해당 요청명과 폴더를 add 했듯, 과정이 똑같다.)

WebSocket 개요

http 프로토콜이 아닌 ws 프로토콜로 두개의 프로토콜을 한 서버에서 다루는 건 힘들다. (서버 하나 = 프로젝트 하나)

채팅 전용 서버 만드는게 프로젝트 하나를 따로 만드는것과 같은 의미이다.

요청명만 설정해놓으면 다른 프로젝트에서 아래 예제의 프로토콜을 사용하여 (어차피 같은 ip:port 인 host 이다) 연결이 가능하다.

1. 의존성 다운

: spring-websocket 의 version 은 spring-webmvc API 와 똑같은 버전으로

    <!-- json --> // 있다면 다운 안받아도 됨
    <dependency>
	  <groupId>com.fasterxml.jackson.core</groupId>
	  <artifactId>jackson-databind</artifactId>
	  <version>2.10.1</version>
	</dependency>

	<!-- spring-websocket -->
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-websocket</artifactId>
	    <version>5.0.2.RELEASE</version>
	</dependency>
1) jackson-databind
이는 Spring 과 관련이 있는 것은 아니고 java 와 js 가 JSON 객체를 주고 받게 하는
@ResponseBody, @RequestBody 를 쓰려는 의미이기 때문에
spring 이 아니더라도 이걸 낀다. (다른 라이브러리도 존재하지만 해당 라이브러리가 가장 괜찮다.)
2) spring-websocket
이는 Spring 과 관련이 있으며 spring 에서 websocket 을 쓸 수 있게 하는 것이다.
해당 라이브러리가 JSON 객체를 주고 받기 때문에 ( js 의 websocket 을 받아야 한다 ) 위의 라이브러리가 필요한 것이다.

이 라이브러리가 jsp-spring 운영체제에서 내가 직접 했어야 할 위의 '네트워크 운영 개념' 의 server 내용을 알아서 작업해준다.
client 내용만 설정하면 된다.(jsp안 javascript 가 client 단. java는 중재자하는 중인 실제 server 단)
client 내용 중 close() 만 client창에서(jsp) 안해도 되고 server단에서 하면 된다.
( jdbc 라이브러리는 대기상태에 빠져있어서 직접 close() 해줘야 했다.)

2. 메세지처리기 만들기
TextWebSocketHandler 을 상속받는 클래스 생성

js 에서 들어오는 WebSocket 을 받는 handleTextMessage() 메서드를 override

@Component : @Configuration 들이 @ComponentScan 로 빈으로 등록하는 어노테이션 중 최상위
@ComponentScan 이 스캔하여 Bean 등록하는 어노테이션 :
@Component 과 이에 포함된 @Repository, @Service, @Controller
현재 쓰는 메세지처리기는 역할이 DAO(@Repository) 도 아니고 비지니스영역(@Service) 도 아니고 컨트롤(@Controller) 도 아니기 때문에
@Component 를 썼다.
//연결 및 메세지 처리기
@Component
public class MessageHandler extends TextWebSocketHandler{
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		//TextMessage payload=[ss], byteCount=2, last=true] 출력
		System.out.println(message);//TextMessage 객체로 이 객체의 toString 이 실행되어 위처럼 출력됨
		
		//session 의 sendMessage() 는 받은게 TextMessage 형이니 보낼 때도 TextMessage 형이다.
		//그래서 메세지 열만 TextMessage의 메서드인 getPayload() 로 String 형변환 후
		//이 메세지만 꺼낸 String 을 다시 TextMessage 형변환 하여 보낸다.
		String 메세지만 = message.getPayload();
		TextMessage 보낼message = new TextMessage(메세지만+"를 잘받았어");
		session.sendMessage(보낼message);
		
		//연결처리
		//메세지 처리
		
		//끊기 처리
	}
}

3. 메세지처리기 등록

WebSocketConfigurer 을 구현한 클래스 생성

@Configuration
@EnableWebSocket
public class 웹소켓환경설정 implements WebSocketConfigurer{

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(new MessageHandler(),"/chat");
	}
}
@Controller 와의 차이점 :
WebSocket 이 돌아갈 TextWebSocketHandler 을 상속받은 클래스는 요청명이 존재해도 컨트롤과는 달리
위에 RequestMapping 을 하는게 아니고
WebSocketConfigurer 구현한 클래스에서 직접 매핑을 시켜줌.
등록은 @ComponentScan 이 아니고 WebSocketConfigurer 의 클래스에서 add 하여 사용한다.

4. web.xml 의 init-param 에 등록

	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextClass</param-name>
			<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>			
		</init-param>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				config.MvcConfig
				config.BeanConfig
				config.웹소켓환결설정
			</param-value>	
		</init-param>
		<load-on-startup>1</load-on-startup>
		<multipart-config>
	        <location>/upload</location>
	        <max-file-size>5242880</max-file-size><!--5MB-->
	        <max-request-size>20971520</max-request-size><!--20MB-->
	        <file-size-threshold>0</file-size-threshold>
	    </multipart-config>
	</servlet>

WebSocket 으로 채팅 구현하기

1. client 상황 만들기(jsp 만들기)

<script>
var 웹소캣;
var 별명;
function 연결하다(){
	//연결이 여기에 있으니 이 함수를 적용한 버튼부터 눌러야 웹소켓이 생성되고 session 으로 저장된다.
   웹소캣=new WebSocket("ws://localhost:8080/chat");
   웹소캣.onopen = function(){
	  let txt별명 = document.querySelector("#txt별명");
	  별명 = txt별명.value;
	  // json 문자열화 해서 보내기
      웹소캣.send(JSON.stringify({'별명':별명,'상태':'입장'}));
   }
   웹소캣.onmessage=function(result){
      let 받은메세지 = result.data;
      let pMessage = document.querySelector("#pmessage");
      pMessage.innerHTML=pMessage.innerHTML+"<br>"+받은메세지;
   }
   웹소캣.onclose=function(){
		웹소캣 = null;
   }
}


function 보내다(){
	let txtmessage = document.querySelector("#txtmessage");
	웹소캣.send(JSON.stringify({'별명':별명,'상태':'채팅',메세지:txtmessage.value}));
}

function 나가다(){
	let txtmessage = document.querySelector("#txtmessage");
	웹소캣.send(JSON.stringify({'별명':별명,'상태':'퇴장'));
}
   

</script>

</head>
<body>
	별병
	<input type="text" id="txt별명"><br>
	<button onclick="연결하다()">연결</button><br>
	<button onclick="나가다()">나가기</button><br>
	메세지
	<input type="text" id="txtmessage"/><button onclick="보내다()">전송</button><br>
	
	<!-- p 태그처럼 <p></p> 로 닫히는 태그들만 innerHTML 이 있다. input 태그는 value 로 -->
	<p id="pmessage"></p>
</body>
ws : websocket 의 프로토콜 임을 알림(http 처럼) 이 통신규약으로 spring 의 TextWebSocketHandler 로 통신하고 알아서 close()함
WebSocket : client 측의 socket 으로 연결을 걸 수는 있어도 받을 수는 없다.
( 연결을 거는 것이 메세지를 보내 답장을 받는 것이 아니고 처음에 통신연결을 거는것을 말한다. )
spring 에서는 이 소켓을 session 이라고도 부른다 (HttpSession 과는 이름은 같은 session 이라도 다 다르다.)
소켓을 처음에 받으면 session 을 저장하여 소켓이 계속 server 안에 있게 해야한다 (아래 예제)
onopen : 연결이 되자마자 해당 함수 실행
send : 해당 웹소켓에 데이터 보냄
onmessage : 서버측에서 메세지가 오면(sendMessage()) 실행

2. json 객체를 받을 클래스 생성

public class 메세지Data {
	String 메세지;
	String 상태;
	String 별명;
	
	public String get메세지() {
		return 메세지;
	}
	public void set메세지(String 메세지) {
		this.메세지 = 메세지;
	}
	public String get상태() {
		return 상태;
	}
	public void set상태(String 상태) {
		this.상태 = 상태;
	}
	public String get별명() {
		return 별명;
	}
	public void set별명(String 별명) {
		this.별명 = 별명;
	}
	
}

3. jsp 에서 메시지와 별명 등을 받아서 출력해줄 클래스

//연결 및 메세지 처리기
@Component
public class MessageHandler extends TextWebSocketHandler{
	
	List<WebSocketSession> 연결자들 = new ArrayList<WebSocketSession>();
	
	//ws 프로토콜로 요청받을때마다 작동
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		
		String 메세지만 = message.getPayload();//json이 문자열화 된 것: "{별명:   ,상태:   ,}"

		ObjectMapper objectMapper = new ObjectMapper();
		// readValue() : 지금까지 ajax 통신값인 json문자열을 @RequestBody 붙은 객체의 멤버변수에 알아서 들어갔듯이 
		// json 문자열을 해당 클래스의 멤버변수에 넣어주는 메서드
		메세지Data 한메세지Data = objectMapper.readValue(메세지만, 메세지Data.class);
		
		TextMessage 보낼message = null;
		
		if(한메세지Data.get상태().equals("입장")) {
			연결자들.add(session);
			보낼message = new TextMessage(한메세지Data.get별명()+"님이 입장하였습니다.");
			
		}else if(한메세지Data.get상태().equals("채팅")) {
			보낼message = new TextMessage(한메세지Data.get별명()+": "+한메세지Data.get메세지());
			
		}else if(한메세지Data.get상태().equals("퇴장")) {
			보낼message = new TextMessage(한메세지Data.get별명()+"님이 퇴장하였습니다.");
			
		}
		
		for(WebSocketSession webSocketSession:연결자들) {
			webSocketSession.sendMessage(보낼message);
		}
		//위의 매개변수인 session 은 List<WebSocketSession> 연결자들 처럼 모든 세션이 아니라
		//이 요청을 한 본인만이다.
		if(한메세지Data.get상태().equals("퇴장")) {
			session.close();
			연결자들.remove(session);
		}
		
		//연결처리
		//메세지 처리
		
		//끊기 처리
	}
}

* 네트워크 형상관리
형상관리 : 소프트웨어 생명주기의 단계적 산출물에 대한 가시성과 추적가능성을 체계화하는 품질보증 활동이다.
( 어떤 프로그램이 수정될 때 버전을 업그레이드 시킨다는 뜻으로 보면 될 듯 )
1. 의미
- 형상항목 : 버전을 바꿔야 할 대상 ( 버전을 관리할 대상 )
- 베이스라인 : 기준선 (무조건 버전을 바꿔주는 것이 아닌 이 기준을 보며 판단)
- 형상관리위원회 : 형상관리를 하는 곳 ( 팀내에서 팀장일 수도 있고 ..등등 )
- CMDB(Configuration Management Database) : 버전이 어떻게 바뀌었는지 이력을 저장함 ( 히스토리 )
2. 프로세스
형상식별 -> 형상통제 -> 형상감사 -> 형상기록 의 절차를 가지고 있다.
- 형상식별 : 버전을 관리할 (변경할) 대상이 있구나 식별.
( 형상항목을 식별 )
- 형상통제 : 변경관리를 연계하여 수행해야 한다. 즉, 변경이 되었어도 그게 버전을 붙일만한 일인지 아직 그럴 정도의 변경이 아닌지 확인.
( 베이스라인이 쓰일 듯 하다. )
- 형상감사 : 변경사항을 확인하고 검토한다.
( 형상관리위원회가 체크할 거 같다. )
- 형상기록 : 형상관리한 히스토리를 남김
( CMDB가 쓰인다.)