This chapter was intended to implment WebRTC, but we decide to learn Websocket first beacuse Websocket used in the signaling process.
I think lif is not going as expected. Anyway, let's try it.
WebSocket
Websocket is a protocol developed to provide full duplex in compatibility with half duplex HTTP protocol.
Unlike general Socket communication, it uses HTTP 80 Port, so there are no restrictions on firewalls and is usually called WebSocket.
HTTP protocol is used for access, and communication is then communicated using its own WebSocket protocol.
Socket vs WebSocket
we have something to watch out for : socket and web socket are different.
First of all, socket and web socket are similar in that they communicate through IP and port. In addition, both have similar characteristics of full duplex. But websocket operate on HTTP layers, so it's different from the layer on the TCP/IP socket!
Operating layer: Based on OSI layer 7, the socket is based on the Internet protocol (TCP/IP layer), so it is located in layer 4 (network layer) to which TCP and UDP belong, and the Websocket is based on HTTP, so it is located in layer 7(application layer).
Data format: Socket communication based on TCP is simply data transmission through a byte stream, so data consisting of bytes must be handled, but Websocket communication is based on layer 7, which is an application layer, so it deals with data in the form of a message.
Websocket Access Process
In order for a server and a client to communicate using a Websocket, they must first go through a Websocket connection process. The Websocket access process can be divided into TCP/IP access and the web socket opening handshake process. Because Websocket also operate on TCP/IP, servers and clients must have TCP/IP connections with each other before using the Websocket. After the TCP/IP connection is completed, the server and the client begin the websocket opening handshake process.
Building a Springboot Websocket server
I think we've learned enough about the Websocket, so let's understand more while creating a chat app.
Add Library
First, add 'spring webSocket' to 'build.gradle' to use webSocket in Spring
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.5'
id 'io.spring.dependency-management' version '1.1.0'
}
//...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-web'
// Add Websocket Library
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation 'org.projectlombok:lombok:1.18.22'
testImplementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
ChatDTO : Chat Message Info
I need a DTO to send and receive chat messages. Depending on the situation, there are two situations: entering the chat room and sending a message to the chat room, so ENTER (entering the chat room) and TALK (talking) are declared enum. The remaining member fields consist of chat room identification ID, message sender, and message.
ChatDTO.java
package com.example.webrtc.domain.webChat.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ChatDTO {
public enum MessageType{
Enter, TALK
}
private MessageType type;
private String roomId;
private String sender;
private String message;
private String time;
}
Websocket Handler
socket communication involves a 1:N relationship between the server and the client. That is, multiple clients may access one server. Therefore, the server needs a handler to handle messages sent by multiple clients. We will implement text-based chat, so we will inherit 'TextWebSocketHandler' and write it. It outputs the message received from the client to the Console-log and sends a welcome message to the client.
WebSocketHandler.java
package com.example.webrtc.global.config;
//...
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketHandler extends TextWebSocketHandler {
private final ObjectMapper mapper;
private final ChatService chatService;
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
String payload = message.getPayload();
log.info("payload {}", payload);
ChatDTO chatMessage = mapper.readValue(payload, ChatDTO.class);
log.info("session {}", chatMessage.toString());
ChatRoom room = chatService.findRoomById(chatMessage.getRoomId());
log.info("room {}", room.toString());
room.handleAction(session, chatMessage, chatService);
}
}
What is Payload?
Payload means the data being transmitted. When transmitting data, various elements such as header, META data, and error check bits are sent together to increase data transmission efficiency and stability. At this time, the payload means the data you want to send itself. For example, when you send and receive a parcel delivery, it is not a payload because the package is a payload and the invoice or box is an additional one.
Websocket Config
Create a Config to enable WebSocket using a handler. Use the '@EnableWebSocket' annotation to activate WebSocket. The endpoint for accessing WebSocket is set to 'ws/chat'. Add CORS: setAllowedOprigins(" * "); so that the domain can also be accessed from other servers.
WebSocketConfig.java
package com.example.webrtc.global.config;
//...
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/ws/chat")
.setAllowedOrigins("*");
}
}
ChatRoom
The chat room must have information about the clients in that chat room. The chat room must have a client-specific session for the client who entered the chat room. To do this, create a HashSet named sessions to store sessions on a per-client basis. Next, take the ID of the chat room and the name of the chat room as variables.
The method declares a total of two things.
- handleAction : A method for forwarding a message to a session (client) according to the Message Type. If the type is 'ENTER', display "Welcome" in the chat room, and if it is 'TALK', reflect the message that the client has occurred in the chat room as it is.
- sendMessage : A method that allows messages from handAction to be delivered to all sessions contained in sessions.
ChatRoom.java
package com.example.webrtc.domain.webChat.dto;
//...
@Data
public class ChatRoom {
private String roomId;
private String name;
private Set<WebSocketSession> sessions = new HashSet<>();
@Builder
public ChatRoom(String roomId,String name){
this.roomId = roomId;
this.name = name;
}
public void handleAction(WebSocketSession session, ChatDTO message, ChatService chatService){
if(message.getType().equals(ChatDTO.MessageType.Enter)){
sessions.add(session);
message.setMessage(message.getSender() + " 님이 입장하셨습니다.");
sendMessage(message,chatService);
}
}
private <T> void sendMessage(T message, ChatService chatService) {
sessions.parallelStream().forEach(session -> chatService.sendMessage(session,message));
}
}
As I write, I realize that Handler is a typical example of an observer pattern. Why didn't I know all this time?
ChatService
'createRoom()', 'findRoomById()' used here should be transferred to DAO as soon as they are actually connected to DB. We're still planning to make it without connecting to DB, so I put it in the service first. Because there is no connection with DB, the chat room information is saved in HashMap.
- CreateRoom : Set the chat room ID with the UUID value randomly generated through UUID, and create a chat room by setting the chat room name with NAME.
- sendMessage : Sends a message to the specified session.
ChatService.java
package com.example.webrtc.domain.webChat.service;
//...
@Slf4j
@Data
@Service
public class ChatService {
private final ObjectMapper mapper;
private Map<String, ChatRoom> chatRooms;
@PostConstruct
private void init(){
chatRooms = new LinkedHashMap<>();
}
public ChatRoom findRoomById(String roomId){
return chatRooms.get(roomId);
}
public ChatRoom createRoom(String name){
String roomId = UUID.randomUUID().toString();
ChatRoom room = ChatRoom.builder()
.roomId(roomId)
.name(name)
.build();
chatRooms.put(roomId,room);
return room;
}
public <T> void sendMessage(WebSocketSession session, T message){
try{
session.sendMessage(new TextMessage(mapper.writeValueAsString(message)));
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
}
ChatController
The creation and inquiry of the chat room will be implemented with REST api, so create a controller.
ChatController.java
package com.example.webrtc.domain.webChat.Controller;
//..
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatController {
private final ChatService chatService;
@PostMapping
public ChatRoom createRoom(@RequestParam String name){
return chatService.createRoom(name);
}
}
Finish
Done. Now let's run the application to create a room and chat based on roomId.
[References]
https://www.daddyprogrammer.org/post/4077/spring-websocket-chatting/
'Project' 카테고리의 다른 글
Project_온라인스터디_Chpater0?_Persistence Context(N+1 Test) (0) | 2023.06.30 |
---|---|
Project_온라인스터디_Chapter0?_UniqueConstraint (0) | 2023.06.28 |
Project_화상스터디_Chapter03_Spring으로만 화상채팅 만들기 (0) | 2023.05.01 |
Project_화상스터디_Chapter01_WebRTC (0) | 2023.03.25 |
Project_화상스터디_Chapter00_개요 & WebRTC (0) | 2023.03.24 |