更新:请检查我的最新编辑,它似乎只是停留在“正在连接...”
我正在使用 Spring Boot 创建一个 SocketJS(使用 STOMP)应用程序,每当我的应用程序启动时,我都会收到此错误:
caught Error: InvalidStateError: The connection has not been established yet
at SockJS.send (main.js:161:1)
at Client._transmit (stomp.js:159:1)
at Client.disconnect (stomp.js:341:1)
at ChatRoom.tsx:37:1
at safelyCallDestroy (react-dom.development.js:22932:1)
at commitHookEffectListUnmount (react-dom.development.js:23100:1)
at invokePassiveEffectUnmountInDEV (react-dom.development.js:25207:1)
at invokeEffectsInDev (react-dom.development.js:27351:1)
at commitDoubleInvokeEffectsInDEV (react-dom.development.js:27324:1)
at flushPassiveEffectsImpl (react-dom.development.js:27056:1)
S
然后,在后端我收到此错误:
WebSocketSession[2 current WS(2)-HttpStream(0)-HttpPoll(0), 2 total, 0 closed abnormally (0 connect failure, 0 send limit, 0 transport error)], stompSubProtocol[processed CONNECT(2)-CONNECTED(2)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 16, active threads = 0, queued tasks = 0, completed tasks = 18], outboundChannel[pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 2], sockJsScheduler[pool size = 8, active threads = 1, queued tasks = 3, completed tasks = 9]
2023-10-15T15:13:35.507+13:00 ERROR 10628 --- [nio-8080-exec-9] o.s.w.s.m.StompSubProtocolHandler : Error publishing SessionDisconnectEvent[sessionId=stpnlchb, CloseStatus[code=1000, reason=null]]
前端React代码:
import { Button, Flex, Input, Text } from "@chakra-ui/react";
import { Suspense, useEffect, useState } from "react";
import SockJS from "sockjs-client";
import { Client, Message, over } from "stompjs";
interface ChatMessage {
content: string
sender: string
type: 'CHAT' | 'JOIN' | 'LEAVE'
}
interface ChatRoomProps {
username: string
}
export default function ChatRoom(props: ChatRoomProps) {
const [loading, setIsLoading] = useState(true)
const [error, setError] = useState(false)
const [message, setMessage] = useState<string | null>(null)
const [messageStream, setMessageStream] = useState<ChatMessage[]>([])
const [stompClient, setStompClient] = useState<Client | null>(null)
useEffect(() => {
const socket = new SockJS('http://localhost:8080/ws')
const client: Client = over(socket)
client.connect({}, () => {
client.send('/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
client.subscribe('/topic/public', messageReceivedCallbackHandler)
setIsLoading(false)
}, errorCallbackHandler)
setStompClient(client)
return () => {
socket.close()
client.disconnect(() => {})
}
}, [])
function messageReceivedCallbackHandler(payload: Message): void {
const message: ChatMessage = JSON.parse(payload.body)
setMessageStream(prevMessageStream => [...prevMessageStream, message])
}
function errorCallbackHandler(): void {
setError(true)
}
function messageChangeHandler(e: React.ChangeEvent<HTMLInputElement>): void {
setMessage(e.target.value)
}
function sendClickHandler(): void {
if (message && stompClient) {
const chatMessage: ChatMessage = {
content: message,
sender: props.username,
type: 'CHAT'
}
stompClient.send('/chat.sendMessage', {}, JSON.stringify(chatMessage))
setMessage(null)
const inputElement = document.getElementById('msg-input') as HTMLInputElement
inputElement.value = ''
}
}
return (
<Flex
width='750px'
height='750px'
flexDirection='column'
gap='16px'
background='white'
alignItems='center'
padding='42px'
overflow='clip'
>
<Text fontSize='lg' fontWeight='bold'>Spring WebSocket Chat Demo - By Alibou</Text>
{error && <Text>Error connecting to server</Text>}
{loading ? <Text>Loading...</Text> : (
<Flex flexDirection='column' textAlign='center' overflowY='scroll' width='100%'>
{messageStream.map((message, index) => (
<>
{message.type === 'CHAT' && <Text key={index}><b>{message.sender}</b>: {message.content}</Text>}
{message.type === 'JOIN' && <Text key={index}><b>{message.sender}</b> joined</Text>}
{message.type === 'LEAVE' && <Text key={index}><b>{message.sender}</b> left</Text>}
</>
))}
</Flex>
)}
<Flex marginTop='auto' width='100%' gap='16px'>
<Input id='msg-input' placeholder='Type a message' marginTop='auto' onChange={messageChangeHandler} />
<Button colorScheme='blue' onClick={sendClickHandler}>Send</Button>
</Flex>
</Flex>
)
}
Java 后端:
package com.example.socket.controller;
import com.example.socket.controller.model.ChatMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
@Slf4j
public class ChatController {
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(
@Payload ChatMessage chatMessage,
SimpMessageHeaderAccessor headerAccessor
) {
Objects.requireNonNull(headerAccessor.getSessionAttributes()).put("username", chatMessage.getSender());
return chatMessage;
}
}
package com.example.socket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
package com.example.socket.config;
import com.example.socket.controller.model.ChatMessage;
import com.example.socket.controller.model.MessageType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import java.util.Objects;
@Component
@Slf4j
public class WebSocketEventListener {
private final SimpMessageSendingOperations messageTemplate;
@Autowired
public WebSocketEventListener(SimpMessageSendingOperations messageTemplate) {
this.messageTemplate = messageTemplate;
}
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("username").toString();
log.info(STR."User disconnected: \{username}");
ChatMessage chatMessage = new ChatMessage();
chatMessage.setType(MessageType.LEAVE);
chatMessage.setSender(username);
messageTemplate.convertAndSend("/topic/public", chatMessage);
}
}
我不知道为什么会发生这种情况。我尝试解决这个问题有一段时间了,但我不知道问题的根源是什么。我怀疑问题出在前端的某个地方。
更新:
将代码重构为如下所示:
useEffect(() => {
const socket = new SockJS('http://localhost:8080/ws')
socket.onopen = () => {
const client = over(socket)
setStompClient(client)
client.connect({}, () => {
client.send('/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
client.subscribe('/topic/public', messageReceivedCallbackHandler)
setIsLoading(false)
}, errorCallbackHandler)
}
return () => {
socket.close()
stompClient?.disconnect(() => {})
}
}, [])
它似乎只是停留在“正在连接...”,这很奇怪。
堆栈跟踪:
2023-10-19T17:23:06.840+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.authenticator.AuthenticatorBase : Security checking request GET /ws/463/evl1y24y/websocket
2023-10-19T17:23:06.840+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.catalina.realm.RealmBase : No applicable constraints defined
2023-10-19T17:23:06.840+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.authenticator.AuthenticatorBase : Not subject to any constraint
2023-10-19T17:23:06.841+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.util.http.Parameters : Set encoding to UTF-8
2023-10-19T17:23:06.841+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : GET "/ws/463/evl1y24y/websocket", parameters={}
2023-10-19T17:23:06.841+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.w.s.s.s.WebSocketHandlerMapping : Mapped to org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler@6b350309
2023-10-19T17:23:06.842+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.w.s.s.t.h.DefaultSockJsService : Processing transport request: GET http://localhost:8080/ws/463/evl1y24y/websocket
2023-10-19T17:23:06.938+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : findClass(org.apache.tomcat.util.collections.LocalStrings)
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : --> Returning ClassNotFoundException
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : getResource(org/apache/tomcat/util/collections/LocalStrings.properties)
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : Delegating to parent classloader jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : --> Resource not found, returning null
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : findClass(org.apache.tomcat.util.collections.LocalStrings_en)
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : --> Returning ClassNotFoundException
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : getResource(org/apache/tomcat/util/collections/LocalStrings_en.properties)
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : Delegating to parent classloader jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : --> Resource not found, returning null
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : findClass(org.apache.tomcat.util.collections.LocalStrings_en_NZ)
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : --> Returning ClassNotFoundException
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : getResource(org/apache/tomcat/util/collections/LocalStrings_en_NZ.properties)
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : Delegating to parent classloader jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
2023-10-19T17:23:06.941+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase : --> Resource not found, returning null
2023-10-19T17:23:06.962+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed 101 SWITCHING_PROTOCOLS
2023-10-19T17:23:06.964+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.apache.coyote.http11.Http11Processor : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Status in: [OPEN_READ], State out: [UPGRADING]
2023-10-19T17:23:07.001+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.websocket.WsSession : Created WebSocket session [0]
2023-10-19T17:23:07.006+13:00 DEBUG 26253 --- [nio-8080-exec-3] s.w.s.h.LoggingWebSocketHandlerDecorator : New WebSocketServerSockJsSession[id=evl1y24y]
2023-10-19T17:23:07.027+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.t.websocket.server.WsFrameServer : wsFrameServer.onDataAvailable
2023-10-19T17:23:07.028+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.tomcat.util.net.SocketWrapperBase : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Read from buffer: [0]
2023-10-19T17:23:07.028+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.util.net.NioEndpoint : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Read direct from socket: [0]
2023-10-19T17:23:07.028+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.h.u.UpgradeProcessorInternal : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Status in: [OPEN_READ], State out: [UPGRADED]
2023-10-19T17:23:07.029+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.util.net.NioEndpoint : Registered read interest for [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]]
2023-10-19T17:23:44.841+13:00 DEBUG 26253 --- [alina-utility-1] o.apache.catalina.session.ManagerBase : Start expire sessions StandardManager at 1697689424841 sessioncount 0
2023-10-19T17:23:44.842+13:00 DEBUG 26253 --- [alina-utility-1] o.apache.catalina.session.ManagerBase : End expire sessions StandardManager processingTime 1 expired sessions: 0
您正在配置
registry.setApplicationDestinationPrefixes("/app");
这意味着主题必须包含前缀 /app
。
而不是
client.send('/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
stompClient.send('/chat.sendMessage', {}, JSON.stringify(chatMessage))
试试这个
client.send('/app/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
stompClient.send('/app/chat.sendMessage', {}, JSON.stringify(chatMessage))