Notice
Recent Posts
Recent Comments
Link
«   2025/08   »
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
31
Tags
more
Archives
Today
Total
관리 메뉴

개(발)린이

소켓통신(1) 본문

Spring

소켓통신(1)

불도정 2023. 5. 16. 14:54

이번엔 스프링 이용 소켓통신을 구현해보겠다.

 

먼저 pom.xml에서 dependency를 추가해준다.

 

<!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket -->
      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-websocket</artifactId>
         <version>${org.springframework-version}</version>
      </dependency>


      <!-- jackson bind -->
      <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
      <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
         <version>2.9.8</version>
      </dependency>

websocket을 추가해주고

jackson은 무엇인가 하면 Java Object를 JSON으로 변환하거나 JSON을 Java Object로 변환하는데 사용할 수 있는 라이브러리 이다. 

스트림 방식이므로 속도가 빠르며 유연하며 다양한 third party 데이터 타입을 지원하며 annotation 방식으로 메타 데이터를 기술할 수 있으므로 JSON 의 약점중 하나인 문서화와 데이터 validation 문제를 해결할 수 있는 장점이 있다.

 

다음은 mybatis-config.xml에 가보자

<typeAlias type="패키지.vo.ChatRoom" alias="chatRoom"/>
<typeAlias type="패키지.vo.ChatRoomJoin" alias="chatRoomJoin"/>
<typeAlias type="패키지.vo.ChatMessage" alias="chatMessage"/>
<mapper resource="/mappers/chatting-mapper.xml"/>

 

typeAlias와 mapper를 추가해주자

 

다음은 servlet context에 가보자

 

servlet-context.xml의 Namespaces이다

websocket에 체크를 한 뒤 저장!

다음 source탭에 와서 bean 등록을 하자.

<!-- Websocket 요청 시 핸들러 클래스와 연결하기 -->
	<beans:bean id="chatHandler" class="edu.kh.comm.chat.model.websocket.ChatWebsocketHandler"/>
	
	<websocket:handlers>
		<!-- 웹소켓 요청(주소)을 처리할 bean 지정 -->
		<websocket:mapping handler="chatHandler" path="/chat"/>
	
		<websocket:handshake-interceptors>
		<!-- 
			interceptor : http 통신에서 req, resp 가로채는 역할
			
			handshake-interceptors:
				요청 관련 데이터 중 HttpSession(로그인 정보, 채팅방 번호)을 가로채서
				WebSocketSession에 넣어주는 역할
		 -->
			<beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
		</websocket:handshake-interceptors>
		
		<!-- SockJS 라이브러리를 이용해서 만들어진 웹소켓 객체임을 인식 -->
		<websocket:sockjs></websocket:sockjs>
	</websocket:handlers>

 

패키지이다.

 

코드를 보면

 

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.comm.chat.model.service.ChatService;
import edu.kh.comm.chat.model.vo.ChatMessage;
import edu.kh.comm.chat.model.vo.ChatRoom;
import edu.kh.comm.chat.model.vo.ChatRoomJoin;
import edu.kh.comm.member.model.vo.Member;

@Controller
@SessionAttributes({"loginMember", "chatRoomNo"})
public class ChattingController {
	
	@Autowired
	private ChatService service;

	// 채팅방 목록 조회
	@GetMapping("/chat/roomList")
	public String chattingList(Model model) {
		
		List<ChatRoom> chatRoomList = service.selectChatRoomList();
		
		model.addAttribute("chatRoomList", chatRoomList);
		
		return "chat/chatRoomList";
	}
	
	
	// 채팅방 만들기
	@PostMapping("/chat/openChatRoom")
	public String openChatRoom(@ModelAttribute("loginMember") Member loginMember, Model model, 
								ChatRoom room, RedirectAttributes ra) {
		
		room.setMemberNo(loginMember.getMemberNo());
		
		int chatRoomNo = service.openChatRoom(room);
		
		String path = "redirect:/chat/";
		
		if(chatRoomNo > 0) {
			path += "room/" + chatRoomNo;
		}else {
			path += "roomList";
			ra.addFlashAttribute("message","채팅방 만들기 실패");
		}
		
		return path;
	}
	
	
	// 채팅방 입장
	@GetMapping("/chat/room/{chatRoomNo}")
	public String joinChatRoom(@ModelAttribute("loginMember") Member loginMember, Model model,
								@PathVariable("chatRoomNo") int chatRoomNo, 
								ChatRoomJoin join,
								RedirectAttributes ra) {
		
		join.setMemberNo(loginMember.getMemberNo());
		List<ChatMessage> list = service.joinChatRoom(join);
		
		if(list != null) {
			model.addAttribute("list", list);
			model.addAttribute("chatRoomNo", chatRoomNo); // session에 올림
			return "chat/chatRoom";
		}else {
			ra.addFlashAttribute("message","채팅방이 존재하지 않습니다.");
			return "redirect:../roomList";
		}
		
	}
	
	// 채팅방 나가기
	@GetMapping("/chat/exit")
	@ResponseBody
	public int exitChatRoom(@ModelAttribute("loginMember") Member loginMember,
							ChatRoomJoin join) {
		join.setMemberNo(loginMember.getMemberNo());
		
		return service.exitChatRoom(join);
	}
	
}

이 컨트롤러에서 @RequestMapping을 사용하지 않은 이유는 소켓 통신은 HTTP 프로토콜을 사용하지 않기 때문에 기존의 @RequestMapping이나 HTTP 요청 메소드 매핑 어노테이션들을 사용할 필요가 없다.

 

다음은 ChatService 인터페이스이다.

import java.util.List;

import edu.kh.comm.chat.model.vo.ChatMessage;
import edu.kh.comm.chat.model.vo.ChatRoom;
import edu.kh.comm.chat.model.vo.ChatRoomJoin;

public interface ChatService {

	/** 채팅 목록 조회
	 * @return chatRoomList
	 */
	List<ChatRoom> selectChatRoomList();

	/** 채팅방 만들기
	 * @param room
	 * @return chatRoomNo
	 */
	int openChatRoom(ChatRoom room);

	
	/** 채팅방 입장 + 채팅 메세지 목록 조회
	 * @param join
	 * @return list
	 */
	List<ChatMessage> joinChatRoom(ChatRoomJoin join);

	
	/** 채팅 메세지 삽입
	 * @param cm
	 * @return result
	 */
	int insertMessage(ChatMessage cm);

	/** 채팅방 나가기
	 * @param join
	 * @return result
	 */
	int exitChatRoom(ChatRoomJoin join);

}

 

ChatServiceImpl

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import edu.kh.comm.chat.model.dao.ChatDAO;
import edu.kh.comm.chat.model.vo.ChatMessage;
import edu.kh.comm.chat.model.vo.ChatRoom;
import edu.kh.comm.chat.model.vo.ChatRoomJoin;
import edu.kh.comm.common.Util;

@Service
public class ChatServiceImpl implements ChatService{

	@Autowired
	private ChatDAO dao;

	// 채팅 목록 조회
	@Override
	public List<ChatRoom> selectChatRoomList() {
		return dao.selectChatRoomList();
	}

	// 채팅방 만들기
	@Override
	public int openChatRoom(ChatRoom room) {
		return dao.openChatRoom(room);
	}

	
	// 채팅방 입장 + 내용 얻어오기
	@Override
	public List<ChatMessage> joinChatRoom(ChatRoomJoin join) {

		// 현재 회원이 해당 채팅방에 참여하고 있는지 확인
		int result = dao.joinCheck(join);
		
		if(result == 0) { // 참여하고 있지 않은 경우 참여
			dao.joinChatRoom(join);
		}
		
		// 채팅 메세지 목록 조회
		return dao.selectChatMessage(join.getChatRoomNo());
	}

	
	// 채팅 메세지 삽입
	@Override
	public int insertMessage(ChatMessage cm) {
		
//		cm.setMessage(Util.XSSHandling(cm.getMessage()));
		cm.setMessage(Util.newLineHandling(cm.getMessage()));
		
		return dao.insertMessage(cm);
	}

	
	// 채팅방 나가기
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int exitChatRoom(ChatRoomJoin join) {
		
		// 채팅방 나가기
		int result = dao.exitChatRoom(join);
		
		if(result > 0) { // 채팅방 나가기 성공 시
			
			// 현재 방에 몇명이 있나 확인
			int cnt = dao.countChatRoomMember(join.getChatRoomNo());
			
			// 0명일 경우 방 닫기
			if(cnt == 0) {
				result = dao.closeChatRoom(join.getChatRoomNo());
			}
			
		}
		
		return result;
	}
	
}

VO 3개

 

1번 ChatMessage

import java.sql.Date;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@ToString
public class ChatMessage {
	private int cmNo;
	private String message;
	private Date createDate;
	private int chatRoomNo;
	private int memberNo;
	private String memberEmail;
	private String memberNickname;
}

2번 ChatRoom

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@ToString
public class ChatRoom {
	private int chatRoomNo;
	private String title;
	private String status;
	private int memberNo;
	private String memberNickname;
	private int cnt; // 참여자 수
}

3번 ChatRoomJoin

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@ToString
public class ChatRoomJoin {
	private int memberNo;
	private int chatRoomNo;
}

다음은 ChatWebsocketHandler이다

 

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 ChatWebsocketHandler extends TextWebSocketHandler {
		
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        
        	System.out.println(session.getId() + " 연결됨");
        }
        
        @Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {}

	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {}
        
    }

spring을 이용해 제작중인데 상속을 받는 모습을 볼 수 있다. pojo기반에 위배를 한다고 볼 수 있는데

사실 Spring에서 제공하는 WebSocketHandler 인터페이스를 상속받아 WebSocketHandler 클래스를 만든것이라 위배되지

않는다고 한다...

어쨌든 그렇다한다.

 

여기서 WebSocketHandler 인터페이스란? 웹소켓을 위한 메소드를 지원하는 인터페이스이다

=>  WebSocketHandler 인터페이스를 상속받은 클래스를 이용해 웹소켓 기능을 구현하려고 하는 것이다.

 

** WebSocketHandler 주요 메소드를 보자면

    - void handlerMessage(WebSocketSession session, WebSocketMessage message)
       => 클라이언트로부터 메세지가 도착하면 실행
        
    -  void afterConnectionEstablished(WebSocketSession session)
        => 클라이언트와 연결이 완료되고, 통신할 준비가 되면 실행

    -  void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
        => 클라이언트와 연결이 종료되면 실행

    -  void handleTransportError(WebSocketSession session, Throwable exception)
        => 메세지 전송중 에러가 발생하면 실행 

 

※ TextWebSocketHandler 는 WebSocketHandler 인터페이스를 상속받아 구현한 텍스트 메세지 전용 웹소켓 핸들러 클래스이다.

 

     - handlerTextMessage(WebSocketSession session, TextMessage message)
        => 클라이언트로부터 텍스트 메세지를 받았을때 실행

 

주요 메소드중 3개를 오버라이딩 하였다.

 

다음에 계속...

'Spring' 카테고리의 다른 글

AOP(2)  (0) 2023.05.16
AOP  (0) 2023.05.12
Spring(게시글 작성)-Controller  (0) 2023.05.12
Spring(scheduling)  (0) 2023.05.11
Spring (게시글 조회수)  (0) 2023.05.07