I am trying to use Netty and Protocol Buffers (and encryption but this does not affect this issue). The server is written in Java using Netty and clients are supposed to be written in C and in Java. Here is the Java server side code.
Application class:
@SpringBootApplication
public class Application {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
TcpServer tcpServer = context.getBean(TcpServer.class);
tcpServer.start();
}
@Autowired
private SomethingChannelInitializer somethingChannelInitializer;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public ServerBootstrap bootstrap() {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup(), workerGroup()).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(somethingChannelInitializer);
Map<ChannelOption<?>, Object> tcpChannelOptions = tcpChannelOptions();
Set<ChannelOption<?>> keySet = tcpChannelOptions.keySet();
for (ChannelOption option : keySet) {
b.option(option, tcpChannelOptions.get(option));
}
return b;
}
@Bean(name = "tcpChannelOptions")
public Map<ChannelOption<?>, Object> tcpChannelOptions() {
Map<ChannelOption<?>, Object> options = new HashMap<ChannelOption<?>, Object>();
options.put(ChannelOption.SO_KEEPALIVE, true);
options.put(ChannelOption.SO_BACKLOG, 3);
return options;
}
@Bean(destroyMethod = "shutdownGracefully")
public NioEventLoopGroup bossGroup() {
return new NioEventLoopGroup(2);
}
@Bean(destroyMethod = "shutdownGracefully")
public NioEventLoopGroup workerGroup() {
return new NioEventLoopGroup(2);
}
@Bean
public InetSocketAddress tcpPort() {
return new InetSocketAddress(12888);
}
}
SomethingChannelInitializer class:
@Component
public class SomethingChannelInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private ChannelInboundHandlerAdapter somethingServerHandler;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// SSL stuff
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(ProtocolMessage.OneRequest.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(somethingServerHandler);
}
}
SomethingServerHandler class:
@Component
@Sharable
public class SomethingServerHandler extends ChannelInboundHandlerAdapter {
private static Logger logger = LoggerFactory.getLogger(SomethingServerHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.debug("read ...");
ProtocolMessage.OneRequest req = (ProtocolMessage.OneRequest) msg;
switch (req.getType()) {
case LOGIN:
logger.debug("{}, {}", req.getLoginRequest().getLogin(), req.getLoginRequest().getPassword());
break;
case REGISTER:
logger.debug("{}, {}", req.getRegistrationRequest().getEmail(), req.getRegistrationRequest().getPassword());
break;
default:
break;
}
ProtocolMessage.RegistrationResponse registrationResponse = ProtocolMessage.RegistrationResponse.newBuilder().setStatus("got it").build();
ProtocolMessage.OneResponse rsp = ProtocolMessage.OneResponse.newBuilder().setRegistrationResponse(registrationResponse).build();
ctx.writeAndFlush(rsp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error(cause.getMessage(), cause);
//ctx.close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx){
}
}
ProtocolMessage.proto:
package com.company.model;
message LoginRequest {
required string login = 1;
required string password = 2;
}
message RegistrationRequest {
required string login = 1;
required string email = 2;
required string password = 3;
}
message RegistrationResponse {
required string status = 1;
}
message OneRequest {
enum Type { LOGIN = 1; REGISTER = 2; }
required Type type = 1;
oneof request {
LoginRequest loginRequest = 2;
RegistrationRequest registrationRequest = 3;
}
}
message OneResponse {
oneof response {
RegistrationResponse registrationResponse = 1;
}
}
C client:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "ProtocolMessage.pb-c.h"
#define HERR(source) (fprintf(stderr,"%s(%d) at %s:%d\n",source,h_errno,__FILE__,__LINE__),\
exit(EXIT_FAILURE))
int main(int argc, char **argv) {
// SSL initialization, making new connection etc.
Com__Company__Model__LoginRequest login = COM__COMPANY__MODEL__LOGIN_REQUEST__INIT;
login.login="sample_login";
login.password="secret_password";
Com__Company__Model__OneRequest req = COM__COMPANY__MODEL__ONE_REQUEST__INIT;
req.loginrequest=&login;
req.type=COM__COMPANY__MODEL__ONE_REQUEST__TYPE__LOGIN;
unsigned len = com__company__model__one_request__get_packed_size(&req);
void *buf = malloc(len);
com__company__model__one_request__pack(&req, buf);
SSL_write(clientssl, buf, len);
printf("SSL server sent %d\n", len);
SSL_shutdown(clientssl);
close(clientsocketfd);
SSL_free(clientssl);
SSL_CTX_free(ssl_client_ctx);
return 0;
}
Server log:
2017-09-04 18:14:37.209 DEBUG 63166 --- [ntLoopGroup-3-1] io.netty.handler.ssl.SslHandler : [id: 0x445a7c98, L:/127.0.0.1:12888 - R:/127.0.0.1:50688] HANDSHAKEN: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
2017-09-04 18:14:37.218 ERROR 63166 --- [ntLoopGroup-3-1] p.o.g.handlers.SomethingServerHandler : com.google.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
io.netty.handler.codec.DecoderException: com.google.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:98) ~[netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:297) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:413) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1273) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1084) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138) [netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_91]
Caused by: com.google.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
at com.google.protobuf.InvalidProtocolBufferException.invalidTag(InvalidProtocolBufferException.java:89) ~[protobuf-java-2.6.1.jar!/:na]
at com.google.protobuf.CodedInputStream.readTag(CodedInputStream.java:158) ~[protobuf-java-2.6.1.jar!/:na]
at com.company.model.ProtocolMessage$OneRequest.<init>(ProtocolMessage.java:2037) ~[classes!/:0.0.1-SNAPSHOT]
at com.company.model.ProtocolMessage$OneRequest.<init>(ProtocolMessage.java:2000) ~[classes!/:0.0.1-SNAPSHOT]
at com.company.model.ProtocolMessage$OneRequest$1.parsePartialFrom(ProtocolMessage.java:2116) ~[classes!/:0.0.1-SNAPSHOT]
at com.company.model.ProtocolMessage$OneRequest$1.parsePartialFrom(ProtocolMessage.java:2111) ~[classes!/:0.0.1-SNAPSHOT]
at com.google.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:137) ~[protobuf-java-2.6.1.jar!/:na]
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:168) ~[protobuf-java-2.6.1.jar!/:na]
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:174) ~[protobuf-java-2.6.1.jar!/:na]
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:49) ~[protobuf-java-2.6.1.jar!/:na]
at io.netty.handler.codec.protobuf.ProtobufDecoder.decode(ProtobufDecoder.java:121) ~[netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.protobuf.ProtobufDecoder.decode(ProtobufDecoder.java:64) ~[netty-all-4.1.13.Final.jar!/:4.1.13.Final]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:88) ~[netty-all-4.1.13.Final.jar!/:4.1.13.Final]
... 30 common frames omitted
protobuf-java 2.6.1
protobuf-c 1.0.2
libprotoc 2.6.1
Basically this is some example code taken from the net and modified a bit. Don't look at hardcoded values or other imperfections. This is only for learning purposes. I can communicate with the server using Java client without any issues. However, when I try to send the same message from C client the server immediately throws an exception. I have read something about delimiting but I am not sure how to handle that. I have also tried sending the length of a message as int first, and then the actual message from C client but it doesn't help either. Encryption is not an issue here. If I disable it I will get the same results.
What am I missing here? Is this possible at all to communicate with C clients?
Try to use LengthFieldBasedFrameDecoder instead of ProtobufVarint32FrameDecoder, and send package lengh first:
You need to specify byte order BIG_ENDIAN or LITTLE_ENDIAN as you whant.