개(발)린이
소켓통신(1) 본문
이번엔 스프링 이용 소켓통신을 구현해보겠다.
먼저 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에 가보자
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 |