I have written a client-server programme using netty IO in which I am able to send a message to the server and server responds back but unfortunately, any of the listener methods in my Client Handler class is not getting invoked ie.. response is not received at the client level back. This is the piece of code used in my client to send a request message to the server.
ChannelFuture cf =this.ctx.channel().writeAndFlush(Unpooled.copiedBuffer(getEchoISOMessage(), CharsetUtil.ISO_8859_1));
this.ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
cf.awaitUninterruptibly();
if (!cf.isSuccess()) {
System.out.println("Send failed: " + cf.cause());
}
Below is the code used to receive any response message from the server.
#Override
public void ChannelRead(ChannelHandlerContext ctx, ByteBuf in)
{
System.out.println("Received a response from Server..");
String responseMessage="";
responseMessage=in.toString(CharsetUtil.ISO_8859_1);
System.out.println("Received data as echo response"+responseMessage+"from "+ctx.channel().remoteAddress());
}
But this method is not getting invoked at all.
What does your handler extend from? It should be an inbound handler if it is to receive a response from the server. Are you sure your code compiles with the #Override annotation? The callback method in ChannelInboundHanlder is public void void channelRead(ChannelHandlerContext ctx, Object msg). Make sure your annotate with #Override and make sure the code compiles. Then your client will/should receive the response from the server. If it doesn't help, consider sharing the full code.
Related
Quick disclaimer, I am very new to gRPC and RPC in general, so please have patience
I have two gRPC servers running on the same java application, Service A and Service B.
Service A creates multiple clients of Service B which then synchronously makes calls to the various instances of Service B
The server
Service A has a rpc call defined by the .proto file as
rpc notifyPeers(NotifyPeersRequest) returns (NotifyPeersResponse);
the server side implementation,
#Override
public void notifyPeers(NotifyPeersRequest request, StreamObserver<NotifyPeersResponse> responseObserver) {
logger.debug("gRPC 'notifyPeers' request received");
String host = request.getHost();
for (PeerClient c : clients.values()) {
c.addPeer(host); // <---- this call
}
NotifyPeersResponse response = NotifyPeersResponse.newBuilder()
.setResult(result)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
The list of peers, clients are built up in previous rpc calls.
ManagedChannel channel = ManagedChannelBuilder.forTarget(peer).usePlaintext().build();
ClientB client = new ClientB(channel);
clients.put(peer, client);
The client
rpc addPeer(AddPeerRequest) returns (AddPeerResponse);rpc addPeer(AddPeerRequest) returns (AddPeerResponse);
the server side implementation,
#Override
public void addPeer(AddPeerRequest addPeerRequest, StreamObserver<AddPeerResponse> responseObserver) {
logger.info("gRPC 'addPeer' request received");
boolean result = peer.addPeer(host);
AddPeerResponse response = AddPeerResponse.newBuilder()
.setResponse(result)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
the client side implementation,
public boolean addPeer(String host) {
AddPeerRequest request = AddPeerRequest.newBuilder().setHost(host).build();
logger.info("Sending 'addPeer' request");
AddPeerResponse response = blockingStub.addPeer(request);
return response.getResponse();
}
When I run this application, and an RPC is made to Service A and the client connection is created that calls addPeer, an ambiguous exception is thrown, io.grpc.StatusRuntimeException: UNKNOWN which then causes the JVM to shut down. I have no idea how to fix this, or whether it is even possible to create an gRPC client connection within a gRPC server
for all of my gRPC server implementations I'm using blocking stubs.
<grpc.version>1.16.1</grpc.version>
<java.version>1.8</java.version>
I've pretty much hit a brick wall, so any information will be appreciated
The UNKNOWN message is an exception on the server side that was not passed to the client.
You probably need to increase the log level on the server to try to find the root cause.
In this post here ,
creating the channel like below, enable it to see a more meaningful error message:
ManagedChannel channel = NettyChannelBuilder.forAddress( host, port )
.protocolNegotiator(ProtocolNegotiators.serverPlaintext() )
If A and B are in the same application have you considered making direct function calls or at least using the InProcessChannelBuilder and InProcessServerBuilder?
As mentioned elsewhere, in the current setup you can try increasing the log level on the server side (in B) to see the source of the exception.
I am new to CometD.I have a written a basic CometD Server in java and simple CometD Client.I am getting Successful response from postman for /meta/handshake,/meta/connect,/meta/subscribe channels. But when i start using my cometD java client(which i reused from the https://protect-us.mimecast.com/s/vLH6CNk58of1Ow1GsmVz4u?domain=github.com), handshake is failing with the below message.
Failing {supportedConnectionTypes=[long-polling],
channel=/meta/handshake, id=22, version=1.0}
I am using cometdVersion - '4.0.0',jettyVersion - '9.4.0.v20161208', springbootVersion - '1.5.14.RELEASE' in my code.
I have done a dynamic servlet registration to AnnotationCometDServlet and added /notifications as mapping.
I have created a channel as below in bayeuxServer configuration class.
bayeuxServerImpl.createChannelIfAbsent("/updates",
(ServerChannel.Initializer) channel -> channel.addAuthorizer(GrantAuthorizer.GRANT_ALL));
In client code, i have used /notifications as the defaulturl and channel as /updates
#Service("cometListener")
#Slf4j
public class BayeuxListener implements BayeuxServer.SessionListener {
#Inject
private BayeuxServer bayeuxServer;
#Session
private ServerSession serverSession;
#Configure({"/updates**,/notifications**"})
protected void configureChannel(ConfigurableServerChannel channel) {
channel.addAuthorizer(GrantAuthorizer.GRANT_ALL);
channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
channel.setPersistent(true);
}
#Listener("/meta/*")
public void monitorMeta(ServerSession session, ServerMessage message) {
log.info("monitoring meta"+message.toString()+"channel "+message.getChannel()+"session id "+session.getId());
}
#Listener("/meta/subscribe")
public void monitorSubscribe(ServerSession session, ServerMessage message) {
log.info("Monitored Subscribe from " + session + " for " + message.get(Message.SUBSCRIPTION_FIELD));
}
#Listener("/meta/unsubscribe")
public void monitorUnsubscribe(ServerSession session, ServerMessage message) {
log.info("Monitored Unsubscribe from " + session + " for " + message.get(Message.SUBSCRIPTION_FIELD));
}
#Listener("/updates")
public void handlesrgUpdates(ServerSession client, ServerMessage message) {
ServerSession cilentSession = bayeuxServer.getSession(client.getId());
client.deliver(cilentSession,"/updates", "Received message back from client");
}
}
You have a strange combination of CometD version, Jetty version and Spring Boot version. I recommend that you stick with the default versioning declared in the CometD POM, i.e. CometD 4.0.2, Jetty 9.4.14 and Spring Boot 2.0.6.
The handshake failure you mention is not complete or it is not a failed handshake reply. This is because handshake replies have the successful field, and what you mention {supportedConnectionTypes=[long-polling], channel=/meta/handshake, id=22, version=1.0} looks like the handshake request. As such it's difficult to say what the problem is, because the failure reason is typically reported in the handshake reply.
If you have dynamically registered the CometD Servlet under the /notifications Servlet mapping, then the client should have a URL that ends with /notifications.
Note that the Servlet mapping /notifications and the CometD channel /notifications are two different things and are not related - they just happen to have the same name.
Your code is mostly fine, but contains a few errors.
#Configure({"/updates**,/notifications**"})
protected void configureChannel(ConfigurableServerChannel channel) {
channel.addAuthorizer(GrantAuthorizer.GRANT_ALL);
channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
channel.setPersistent(true);
}
The code above must instead be:
#Configure({"/updates/**,/notifications/**"})
protected void configureChannel(ConfigurableServerChannel channel) {
channel.addAuthorizer(GrantAuthorizer.GRANT_ALL);
channel.setPersistent(true);
}
Note that the channel globbing must be after a /.
There is no need to GRANT_PUBLISH after a GRANT_ALL, which include GRANT_PUBLISH.
The configuration method should be public, not protected.
#Listener("/updates")
public void handlesrgUpdates(ServerSession client, ServerMessage message) {
ServerSession cilentSession = bayeuxServer.getSession(client.getId());
client.deliver(cilentSession,"/updates", "Received message back from client");
}
There is no need to retrieve clientSession from bayeuxServer because it is already been passed as parameter client to the method.
The method can be better implemented as:
#Listener("/updates")
public void handlesrgUpdates(ServerSession client, ServerMessage message) {
client.deliver(serverSession, "/updates", "Received message back from client");
}
Note how the "sender" of the message is the serverSession reference that has been injected as a field of the class.
The code above is still possibly wrong.
Because /updates is a broadcast channel, if the client is subscribed to the /updates channel, when the client publishes a message to the /updates channel it will receive it back from the server (because the client is subscribed to the /updates channel) and the code above will also send another message to the client on the /updates channel via deliver(), so the client will receive two different messages on the /updates channel.
This may be what you want, but most of the times it's not.
Please have a read at the difference between broadcast channels and service channels.
Update the question with the details of your handshake failure, and use consistent versioning for CometD, Jetty and Spring Boot.
This is my current setup for an Undertow websocket server:
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(path()
.addPrefixPath("/", websocket((exchange, channel) -> {
channel.getReceiveSetter().set(new AbstractReceiveListener() {
#Override
protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) {
final String messageData = message.getData();
for (WebSocketChannel session : channel.getPeerConnections()) {
WebSockets.sendText(messageData, session, null);
}
}
});
channel.resumeReceives();
}))).build();
This is copied from one of their demo files. I believe onFullTextMessage here broadcasts any messages it receives to all clients.
I want to be able to trigger this behavior on demand. So instead of having to receive a message and using an event handler to send out more messages, I want to be able to arbitrarily do server.send() and send a message to all connected clients.
I've looked around and haven't found anything that seems capable of achieving this. I don't relish the idea of tracking all WebSocketChannels and sending to each client manually. Surely, there's a method somewhere that I've overlooked?
I'd appreciate some pointers if it's not something that's just not possible!
You can broadcast the message to all the clients on the channel by getting all the connections to this canal and sending the message :
channel.getPeerConnections().forEach(connection -> {
WebSockets.sendText(messageData, connection, null);
});
I am trying to send PongMessage to a server from web-socket client (heart-beat message you may say).
In server, I have written a method like this inside a class Annotated with #ServerEndpoint :
#OnMessage
public void onPong(PongMessage pongMessage, Session session) {
LOGGER.log(Level.INFO, " -- Got Hit Yaay!! -- ");
try {
session.getBasicRemote().sendText(PONG_RECEIVED);
} catch (IOException e) {
e.printStackTrace();
}
}
PongMessage Accepts ByteBuffer according to an oracle documentation. I tried generating a bytebuffer in server and sending exact same generated bytebuffer value from socket client. Server still throws an exception saying it Cannot decode a string.
I am not able to hit onPong() method. Is there any format for pongmessage that client can send to a websocket server so that onPong() method gets invoked? What am I doing wrong here? Any Ideas ?
I have found an old post that Ping/Pong messages should be handled by the browser. If that is still true (i'm not sure), then you should be moving to a "custom" solution: don't use PongMessage. Receive some JSON defined by you.
#OnOpen
public void onOpen(Session session) {
session.getBasicRemote.sendText("Echo me");
session.addMessageHandler(new MessageHandler.Whole<String>() {
public void onMessage(String arg0) {
System.out.println(arg0);
}
});
}
I send a message, and it's echoed back to me. Well what if I wanted to send another message?
How would I do that?
The reason I ask is because I'm logging in to a server, and they send me back a message stating if I'm successfully logged in or not.
After I've been successfully logged in, I want to send another message querying the server about different information I require.
WebSocketContainer.connectToServer(...) method returns Session object. When client is connected, you can use this to send messages (see Session.getBasicRemote().sendText(...) or Session.getBasicRemote().sendBinary(...)).