본문 바로가기
CLASS/SPRINGBOOT

#11-1 / websocket - socket 통신

by hingu 2024. 8. 29.

[ socket ~ chat  구성] : 실제론 다 요렇게 만들어야함

  1. HTML => 제작 (채팅 접속에 대한 로그인 정보)
  2. Socket Server => 구성
  3. Socket UDP,TCP => Room 정보
  4. Client (HTML, JSP) => Server에서 전달한 값을 출력

✅ 해당 예제에선 server 구성 / Client <-> Client 채팅까지만 제작해볼거임

      ( 그 다음 : Room 생성 및 DTO를 이용해서 생성하는 방식 제작 해야함 )

 

pom.xml 체크 후 다운

 

chrome 접속 -> simple websocket client 검색 ->  Simple WebSocket Client - Chrome Web Store -> chrome에 추가

 

ws : websocket 약자 / url 입력 필수

 

 

- websocketconfig.java  : class

socket 서버 정보 및 접근 권한

package kr.co.sen.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;

//socket 서버 정보 및 접근권한
@Configuration //환경설정 어노테이션
@EnableWebSocket //websocket 사용시 적용되는 어노테이션
public class websocketconfig implements WebSocketConfigurer{
	//WebSocketConfigurer : socket에서 사용하는 interface - override 필쑤

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		//방이 1개만 있을 경우
		//registry.addHandler(sockethand(), "/chat").setAllowedOriginPatterns("*");
		
		//각각의 room을 생성할 경우
		registry.addHandler(sockethand(), "/chat/rooms/*")
		.addInterceptors(handshake())
		.setAllowedOriginPatterns("*");
	}
	
	//각 room마다 그룹을 생성하는 class를 로드하는 메소드
	@Bean //가져온다
	public HandshakeInterceptor handshake() {
		return new chathandshake();
	}
	
	//새로운 클라이언트가 접속시 새로운 class를 생성하는 메소드
	private WebSocketHandler sockethand(){
		
		return new sockethand();
	}
}

WebSocketConfigurer : socket에서 사용하는 interface - override 필쑤

 

- sockethand.java : class

socket으로 접속한 Client에 대한 메시지를 출력하는 class

package kr.co.sen.web;

import java.util.HashSet;
import java.util.Set;

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class sockethand extends TextWebSocketHandler{
	//TextWebSocketHandler : 문자로 메세지 전송하는 형태
	
	//Set 배열 (Socket 전용 배열) - 접속한 클라이언트 정보가 담기는 배열
	private final Set<WebSocketSession> sessions = new HashSet<>();
	
	//add() : 접속한 client에 대한 정보값을 배열로 저장
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		sessions.add(session);
		System.out.println("새 클라이언트와 연결되었습니다.");
	}
	
	//getPayload() : client가 입력한 메세지를 getter로 로드함
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		System.out.println(message.getPayload());
		
		//반복문 사용 => for each로 모든 client에게 메세지를 발송
		for(WebSocketSession con : sessions) {
			con.sendMessage(message);
		}
	}
	
	//client가 접속 종료시 해당 배열값의 내용을 삭제하면서 해제되도록 함.
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		sessions.remove(session);
		System.out.println("클라이언트가 퇴장했습니다.");
	}
}

 

- chathandshake.class

package kr.co.sen.web;

import java.util.Map;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

//room을 생성하는 class
public class chathandshake implements HandshakeInterceptor{
	//client가 room으로 입장하기 전 상황
	@Override
	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Map<String, Object> attributes) throws Exception {
		String path = request.getURI().getPath();
		System.out.println(path);
		
		String roomid = path.substring(path.lastIndexOf("/")+1);
		attributes.put("roomid", roomid);
		
		return true;
	}
	
	//client가 room으로 입장 후 상황
	@Override
	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Exception exception) {
		//Database로 채팅 내용을 저장(Nosql ===> 채팅내용 저장시에는 무조건 Nosql)
		
	}
}

 

 

 

✅ socket 실행 순서  -  interface , 라이브러리 class

 

[websocketconfig.java]

  1. WebSocketConfigurer (interface)  : Socket 서버 환경설정

  2. WebSocketConfigurer 요 파일 안의 WebsocketHandlers 메소드 실행 : Socket URL load

 

[sockethand.java]

  3. Socket 형태 : Text(문자) or Binary(파일)

  4. Memory에 Session 생성,삭제

 

[ URL 별도로 Session 운영시 - chathandshake.java ]  ex) ws:localhost:801/chat/room/1

  5. Handshakeinterceptor(interface)
      ❓ interceptor : 메모리와 서버간의 반복되는 코드에서 누락 관리 (많이 줄여줌)

 

  6. session을 생성 [chat-room.java] - 여러개의 session을 생성 및 방번호를 기억

  7. session 넘버링 [roomgen.java - static 이용하여 url 및 방 고유 id를 생성]

  8. session 사용가능하게 websocketconfig.java 로 return [ roomrepo.java ]

     - 방 리스트 및 생성된 고유 방 id를 가져오는 역할

 

  👀 여러개의 session을 생성 및 방번호를 기억  

- websocketconfig.java  : class

package kr.co.sen.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;

//socket 서버 정보 및 접근권한
@Configuration //환경설정 어노테이션
@EnableWebSocket //websocket 사용시 적용되는 어노테이션
public class websocketconfig implements WebSocketConfigurer{
	//WebSocketConfigurer : socket에서 사용하는 interface - override 필쑤

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		//방이 1개만 있을 경우
		//registry.addHandler(sockethand(), "/chat").setAllowedOriginPatterns("*");
		
		//각각의 room을 생성할 경우 ( ex) ws:localhost:8081/chat/rooms/1)
		registry.addHandler(sockethand(), "/chat/rooms/{roomId}").setAllowedOriginPatterns("*");
	}
	
	//각 room마다 그룹을 생성하는 class를 로드하는 메소드
	//@Bean //가져온다
	public HandshakeInterceptor handshake() {
		return new chathandshake();
	}
	
	//새로운 클라이언트가 접속시 새로운 class를 생성하는 메소드 (room이 여러개일 경우)
	@Bean
	public WebSocketHandler sockethand(){
		
		return new sockethand();
	}
}

 

 

- sockethand.java

package kr.co.sen.web;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class sockethand extends TextWebSocketHandler{
	//TextWebSocketHandler : 문자로 메세지 전송하는 형태
	
	//Set 배열 (Socket 전용 배열) - 접속한 클라이언트 정보가 담기는 배열(단일 session)
	//private final Set<WebSocketSession> sessions = new HashSet<>();
	
	@Autowired
	private roomrepo roomrepo;
	
	//ws로 접속시 해당 세션의 uri에 있는 마지막 번호를 원시배열로 생성하여 class배열로 전환
	private Long getRoomid(WebSocketSession session) {
		String uri = Objects.requireNonNull(session.getUri().toString());
		
		String parts[] = uri.split("/");
		String roomId = parts[parts.length-1];
		
		return Long.parseLong(roomId);
	}
	
	//add() : 접속한 client에 대한 정보값을 배열로 저장
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		//sessions.add(session);
		Long roomId = getRoomid(session);
		roomrepo.room(roomId).sessions().add(session);
		System.out.println("새 클라이언트와 연결되었습니다.");
	}
	
	//getPayload() : client가 입력한 메세지를 getter로 로드함
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		System.out.println(message.getPayload());
		
		//반복문 사용 => for each로 모든 client에게 메세지를 발송
		Long roomId = getRoomid(session);
		chat_room room = roomrepo.room(roomId);
		
		//for(WebSocketSession con : sessions) { //방 1개일때
		for(WebSocketSession con : room.sessions()) {
			//방이 여러개 있을 경우 해당 방에서만 채팅 내용 출력
			con.sendMessage(message);
		}
	}
	
	//client가 접속 종료시 해당 배열값의 내용을 삭제하면서 해제되도록 함.
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		//sessions.remove(session);
		Long roomId = getRoomid(session);
		roomrepo.room(roomId).sessions().remove(session);
		System.out.println("클라이언트가 퇴장했습니다.");
	}
}

 

- chathandshake.class

얜 똑같음

package kr.co.sen.web;

import java.util.Map;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

//room을 생성하는 class
public class chathandshake implements HandshakeInterceptor{
	//client가 room으로 입장하기 전 상황
	@Override
	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Map<String, Object> attributes) throws Exception {
		String path = request.getURI().getPath();
		System.out.println(path);
		
		String roomid = path.substring(path.lastIndexOf("/")+1);
		attributes.put("roomid", roomid);
		
		return true;
	}
	
	//client가 room으로 입장 후 상황
	@Override
	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
			Exception exception) {
		//Database로 채팅 내용을 저장(Nosql ===> 채팅내용 저장시에는 무조건 Nosql)
		
	}
}

 

 

- chat_room.java - class

session으로 방을 생성하는 class

package kr.co.sen.web;

import java.util.HashSet;
import java.util.Set;

import org.springframework.web.socket.WebSocketSession;

//session으로 방을 생성하는 class
public class chat_room {
	private Long id; //session id 예시)1234567784556445454
	private String name; //방 주소 URL 이름
	
	//복수 형태의 session을 생성하는 역할
	private final Set<WebSocketSession> sessions = new HashSet<>();
	
	
	//즉시실행 메소드 : id 생성 및 방 주소 url을 생성[roomgen.java와 함께 핸들링]
	public static chat_room create(String name) { 
		chat_room room = new chat_room();
		room.id = roomgen.createId(); //session id
		room.name = name; //사용자가 접속한 url을 받음
		
		return room;
	}
	
	//getter
	public Long id() {
		
		return id;
	}
	public Set<WebSocketSession> sessions(){
		
		return sessions;
	}
	
}

 

 

- roomgen.java - class

방 고유 id를 생성하는 class

package kr.co.sen.web;

//방 고유 id를 생성하는 class
public class roomgen {
	private static Long id = 0L;
	public static Long createId() {
		id += 1;
		
		return id;
	}
}

 

 

- roomrepo.java  

package kr.co.sen.web;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.stereotype.Repository;

@Repository
public class roomrepo {
	private final Map<Long, chat_room> rooms;
	
	public roomrepo() { //즉시실행
		//방번호 리스트를 출력할 때 사용
		rooms = Stream.of(chat_room.create("room1 채팅"),chat_room.create("room2 채팅"))
				.collect(Collectors.toMap(chat_room::id, room->room));
	}
	
	// 고유 방 id getter 형태로 입력시키는 메소드
	public chat_room room(Long id) {
		return rooms.get(id);
	}

}

repository를 썼다 => Autowired를 쓴다 (sockethand.java에서 씀)

 

 

====> 방 구분되어 채팅올라감

 

🔽 web으로 채팅  ? 

 

web으로 GET 형태로 채팅을 참여하는 형태 구조의  Controller

@Controller
public class web_controller2 {
        @Autowired
	userDB_dao userDB_dao; 
    
	@GetMapping("/chat/rooms/{roomID}")
	public ResponseEntity<List<websocketconfig>> roomin(@PathVariable Long roomid){
		//사용정보 : 사용자 id, 사용자 이름, 사용자 전화번호
		//gettersetter로 때리면 됨
		
		userDB_dao.setUname("홍길동"); //머 요런식으로
		return null;
	}
	
}

 

 

websocket + javascript 연결방식

websocket.html

<body>
	웹 소켓 주소 : <input type="text" id="socket_uri">
	<input type="button" value="소켓 연결" onclick="connect_socket()">
</body>

<script>
	function connect_socket(){
		var uri = document.getElementById("socket_uri");
		
		//input에 ws://localhost:8081/chat/rooms/2 입력
		var socket = new WebSocket(uri.value); 
		
		socket.onopen = function(e){
			socket.send("test");
			console.log("서버 오픈!!")
		}
	}

</script>

 

 

 

 

 

 


 

 

 

 

요 밑에는 걍 참고용..^^.. 지우진 말쟈

 

  👀 websocket  

- websocketHandler.java   : websocket을 open하는 class

package kr.co.sen.web;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class websocketHandler extends TextWebSocketHandler{
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		String payload = message.getPayload(); //해당 텍스트 내용을 인자값으로 받아서 문자화
		//socket 서버 접속시 환영 메세지 출력
		TextMessage textmessage = new TextMessage("Welcome Chatting Server~ ㅇㅅaㅇ");
		session.sendMessage(textmessage);
	}
}

 

 

 

- websocketConfig.java  : websocket 환경설정하는 class

package kr.co.sen.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

import lombok.RequiredArgsConstructor;

@Configuration //socket통신 환경설정
@EnableWebSocketMessageBroker //websocket 활성화 부분
public class websocketConfig implements WebSocketMessageBrokerConfigurer{
	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker("/sub"); //클라이언트가 요청하는 경로
		config.setApplicationDestinationPrefixes("/pub"); //메세지가 도착하는 경로
	}
	
	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/ws-stomp").setAllowedOriginPatterns("*").withSockJS();
	}

}

 

 

- chatroom.java  : class

package kr.co.sen.web;

import java.util.UUID;

public class chatroom { //채팅방을 생성하는 DTO
	private String roomid;
	private String name;
	
	//채팅방 이름을 랜덤하게 생성하는 메소드
	public static chatroom create(String name) {
		chatroom cr = new chatroom();
		cr.roomid = UUID.randomUUID().toString();
		
		return cr;
	}
}

 

 

-chatcontroller.java  : controller

package kr.co.sen.web;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class chatcontroller { //채팅 소켓 통신 전용 컨트롤러
	private final SimpMessageSendingOperations messaging;
	
	//BroadCasting 기능을 수행하는 Mapping : socket 전용 Mapping (메세지 핸들링 전용)
	@MessageMapping("chat/message")
	public void message() {
		messaging.convertAndSend("/sub/chat/room");
	}
}

 

 

- chatroomrepo.java  : class

/**/

 

 

 

 

하나두 모르겠따 ^^!